From 0d7461528388b72df39899fc3d7e01aa0711b84e Mon Sep 17 00:00:00 2001 From: Daniele Tria Date: Tue, 25 Jun 2024 16:03:23 +0200 Subject: [PATCH 1/3] feat: add latest reference and current uuids to modelOut dto, edit tests --- api/app/db/dao/reference_dataset_dao.py | 16 ++++++- api/app/main.py | 6 ++- api/app/models/model_dto.py | 10 +++- api/app/services/model_service.py | 52 +++++++++++++++++++-- api/tests/dao/reference_dataset_dao_test.py | 51 +++++++++++++++----- api/tests/services/model_service_test.py | 34 ++++++++++++-- 6 files changed, 148 insertions(+), 21 deletions(-) diff --git a/api/app/db/dao/reference_dataset_dao.py b/api/app/db/dao/reference_dataset_dao.py index 0cf82cc3..2bd06d58 100644 --- a/api/app/db/dao/reference_dataset_dao.py +++ b/api/app/db/dao/reference_dataset_dao.py @@ -25,12 +25,24 @@ def insert_reference_dataset( return reference_dataset def get_reference_dataset_by_model_uuid( - self, uuid: UUID + self, model_uuid: UUID ) -> Optional[ReferenceDataset]: with self.db.begin_session() as session: return ( session.query(ReferenceDataset) - .where(ReferenceDataset.model_uuid == uuid) + .where(ReferenceDataset.model_uuid == model_uuid) + .one_or_none() + ) + + def get_latest_reference_dataset_by_model_uuid( + self, model_uuid: UUID + ) -> Optional[ReferenceDataset]: + with self.db.begin_session() as session: + return ( + session.query(ReferenceDataset) + .order_by(desc(ReferenceDataset.date)) + .where(ReferenceDataset.model_uuid == model_uuid) + .limit(1) .one_or_none() ) diff --git a/api/app/main.py b/api/app/main.py index 0badac26..2d10f89c 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -52,7 +52,11 @@ current_dataset_dao = CurrentDatasetDAO(database) current_dataset_metrics_dao = CurrentDatasetMetricsDAO(database) -model_service = ModelService(model_dao) +model_service = ModelService( + model_dao=model_dao, + reference_dataset_dao=reference_dataset_dao, + current_dataset_dao=current_dataset_dao, +) s3_config = get_config().s3_config if s3_config.s3_endpoint_url is not None: diff --git a/api/app/models/model_dto.py b/api/app/models/model_dto.py index f3a68ec5..85cd3738 100644 --- a/api/app/models/model_dto.py +++ b/api/app/models/model_dto.py @@ -98,13 +98,19 @@ class ModelOut(BaseModel): algorithm: Optional[str] created_at: str updated_at: str + latest_reference_uuid: Optional[UUID] + latest_current_uuid: Optional[UUID] model_config = ConfigDict( populate_by_name=True, alias_generator=to_camel, protected_namespaces=() ) @staticmethod - def from_model(model: Model): + def from_model( + model: Model, + latest_reference_uuid: Optional[UUID] = None, + latest_current_uuid: Optional[UUID] = None, + ): return ModelOut( uuid=model.uuid, name=model.name, @@ -120,4 +126,6 @@ def from_model(model: Model): algorithm=model.algorithm, created_at=str(model.created_at), updated_at=str(model.updated_at), + latest_reference_uuid=latest_reference_uuid, + latest_current_uuid=latest_current_uuid, ) diff --git a/api/app/services/model_service.py b/api/app/services/model_service.py index e213d5a6..07c39e2b 100644 --- a/api/app/services/model_service.py +++ b/api/app/services/model_service.py @@ -3,7 +3,9 @@ from fastapi_pagination import Page, Params +from app.db.dao.current_dataset_dao import CurrentDatasetDAO from app.db.dao.model_dao import ModelDAO +from app.db.dao.reference_dataset_dao import ReferenceDatasetDAO from app.db.tables.model_table import Model from app.models.exceptions import ModelInternalError, ModelNotFoundError from app.models.model_dto import ModelIn, ModelOut @@ -11,8 +13,15 @@ class ModelService: - def __init__(self, model_dao: ModelDAO): + def __init__( + self, + model_dao: ModelDAO, + reference_dataset_dao: ReferenceDatasetDAO, + current_dataset_dao: CurrentDatasetDAO, + ): self.model_dao = model_dao + self.rd_dao = reference_dataset_dao + self.cd_dao = current_dataset_dao def create_model(self, model_in: ModelIn) -> ModelOut: try: @@ -26,7 +35,14 @@ def create_model(self, model_in: ModelIn) -> ModelOut: def get_model_by_uuid(self, model_uuid: UUID) -> Optional[ModelOut]: model = self.check_and_get_model(model_uuid) - return ModelOut.from_model(model) + latest_reference_uuid, latest_current_uuid = self.get_latest_dataset_uuids( + model_uuid + ) + return ModelOut.from_model( + model=model, + latest_reference_uuid=latest_reference_uuid, + latest_current_uuid=latest_current_uuid, + ) def delete_model(self, model_uuid: UUID) -> Optional[ModelOut]: model = self.check_and_get_model(model_uuid) @@ -42,7 +58,18 @@ def get_all_models( models: Page[Model] = self.model_dao.get_all( params=params, order=order, sort=sort ) - _items = [ModelOut.from_model(model) for model in models.items] + + _items = [] + for model in models.items: + latest_reference_uuid, latest_current_uuid = self.get_latest_dataset_uuids( + model.uuid + ) + model_out = ModelOut.from_model( + model=model, + latest_reference_uuid=latest_reference_uuid, + latest_current_uuid=latest_current_uuid, + ) + _items.append(model_out) return Page.create(items=_items, params=params, total=models.total) @@ -51,3 +78,22 @@ def check_and_get_model(self, model_uuid: UUID) -> Model: if not model: raise ModelNotFoundError(f'Model {model_uuid} not found') return model + + def get_latest_dataset_uuids( + self, model_uuid: UUID + ) -> (Optional[UUID], Optional[UUID]): + latest_reference_dataset = ( + self.rd_dao.get_latest_reference_dataset_by_model_uuid(model_uuid) + ) + latest_current_dataset = self.cd_dao.get_latest_current_dataset_by_model_uuid( + model_uuid + ) + + latest_reference_uuid = ( + latest_reference_dataset.uuid if latest_reference_dataset else None + ) + latest_current_uuid = ( + latest_current_dataset.uuid if latest_current_dataset else None + ) + + return latest_reference_uuid, latest_current_uuid diff --git a/api/tests/dao/reference_dataset_dao_test.py b/api/tests/dao/reference_dataset_dao_test.py index 259d3f7b..458f2c12 100644 --- a/api/tests/dao/reference_dataset_dao_test.py +++ b/api/tests/dao/reference_dataset_dao_test.py @@ -14,7 +14,7 @@ class ReferenceDatasetDAOTest(DatabaseIntegration): @classmethod def setUpClass(cls): super().setUpClass() - cls.f_reference_dataset_dao = ReferenceDatasetDAO(cls.db) + cls.reference_dataset_dao = ReferenceDatasetDAO(cls.db) cls.model_dao = ModelDAO(cls.db) def test_insert_reference_dataset_upload_result(self): @@ -26,7 +26,7 @@ def test_insert_reference_dataset_upload_result(self): date=datetime.datetime.now(tz=datetime.UTC), ) - inserted = self.f_reference_dataset_dao.insert_reference_dataset(to_insert) + inserted = self.reference_dataset_dao.insert_reference_dataset(to_insert) assert inserted == to_insert def test_get_reference_dataset_by_model_uuid(self): @@ -38,14 +38,45 @@ def test_get_reference_dataset_by_model_uuid(self): date=datetime.datetime.now(tz=datetime.UTC), ) - inserted = self.f_reference_dataset_dao.insert_reference_dataset(to_insert) - retrieved = self.f_reference_dataset_dao.get_reference_dataset_by_model_uuid( + inserted = self.reference_dataset_dao.insert_reference_dataset(to_insert) + retrieved = self.reference_dataset_dao.get_reference_dataset_by_model_uuid( inserted.model_uuid ) assert inserted.uuid == retrieved.uuid assert inserted.model_uuid == retrieved.model_uuid assert inserted.path == retrieved.path + def test_get_latest_reference_dataset_by_model_uuid(self): + model = self.model_dao.insert(db_mock.get_sample_model()) + reference_one = ReferenceDataset( + uuid=uuid4(), + model_uuid=model.uuid, + path='frank_file.csv', + date=datetime.datetime.now(tz=datetime.UTC), + ) + + self.reference_dataset_dao.insert_reference_dataset(reference_one) + + reference_two = ReferenceDataset( + uuid=uuid4(), + model_uuid=model.uuid, + path='frank_file.csv', + date=datetime.datetime.now(tz=datetime.UTC), + ) + + inserted_two = self.reference_dataset_dao.insert_reference_dataset( + reference_two + ) + + retrieved = ( + self.reference_dataset_dao.get_latest_reference_dataset_by_model_uuid( + model.uuid + ) + ) + assert inserted_two.uuid == retrieved.uuid + assert inserted_two.model_uuid == retrieved.model_uuid + assert inserted_two.path == retrieved.path + def test_get_all_reference_datasets_by_model_uuid(self): model = self.model_dao.insert(db_mock.get_sample_model()) reference_upload_1 = ReferenceDataset( @@ -66,20 +97,18 @@ def test_get_all_reference_datasets_by_model_uuid(self): path='frank_file.csv', date=datetime.datetime.now(tz=datetime.UTC), ) - inserted_1 = self.f_reference_dataset_dao.insert_reference_dataset( + inserted_1 = self.reference_dataset_dao.insert_reference_dataset( reference_upload_1 ) - inserted_2 = self.f_reference_dataset_dao.insert_reference_dataset( + inserted_2 = self.reference_dataset_dao.insert_reference_dataset( reference_upload_2 ) - inserted_3 = self.f_reference_dataset_dao.insert_reference_dataset( + inserted_3 = self.reference_dataset_dao.insert_reference_dataset( reference_upload_3 ) - retrieved = ( - self.f_reference_dataset_dao.get_all_reference_datasets_by_model_uuid( - model.uuid, Params(page=1, size=10) - ) + retrieved = self.reference_dataset_dao.get_all_reference_datasets_by_model_uuid( + model.uuid, Params(page=1, size=10) ) assert inserted_1.uuid == retrieved.items[0].uuid diff --git a/api/tests/services/model_service_test.py b/api/tests/services/model_service_test.py index 72e55cb1..66fce9cf 100644 --- a/api/tests/services/model_service_test.py +++ b/api/tests/services/model_service_test.py @@ -5,7 +5,9 @@ from fastapi_pagination import Page, Params import pytest +from app.db.dao.current_dataset_dao import CurrentDatasetDAO from app.db.dao.model_dao import ModelDAO +from app.db.dao.reference_dataset_dao import ReferenceDatasetDAO from app.models.exceptions import ModelNotFoundError from app.models.model_dto import ModelOut from app.models.model_order import OrderType @@ -17,8 +19,14 @@ class ModelServiceTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.model_dao: ModelDAO = MagicMock(spec_set=ModelDAO) - cls.model_service = ModelService(cls.model_dao) - cls.mocks = [cls.model_dao] + cls.rd_dao: ReferenceDatasetDAO = MagicMock(spec_set=ReferenceDatasetDAO) + cls.cd_dao: CurrentDatasetDAO = MagicMock(spec_set=CurrentDatasetDAO) + cls.model_service = ModelService( + model_dao=cls.model_dao, + reference_dataset_dao=cls.rd_dao, + current_dataset_dao=cls.cd_dao, + ) + cls.mocks = [cls.model_dao, cls.rd_dao, cls.cd_dao] def test_create_model_ok(self): model = db_mock.get_sample_model() @@ -31,11 +39,25 @@ def test_create_model_ok(self): def test_get_model_by_uuid_ok(self): model = db_mock.get_sample_model() + reference_dataset = db_mock.get_sample_reference_dataset(model_uuid=model.uuid) + current_dataset = db_mock.get_sample_current_dataset(model_uuid=model.uuid) self.model_dao.get_by_uuid = MagicMock(return_value=model) + self.rd_dao.get_latest_reference_dataset_by_model_uuid = MagicMock( + return_value=reference_dataset + ) + self.cd_dao.get_latest_current_dataset_by_model_uuid = MagicMock( + return_value=current_dataset + ) res = self.model_service.get_model_by_uuid(model_uuid) self.model_dao.get_by_uuid.assert_called_once() + self.rd_dao.get_latest_reference_dataset_by_model_uuid.assert_called_once() + self.cd_dao.get_latest_current_dataset_by_model_uuid.assert_called_once() - assert res == ModelOut.from_model(model) + assert res == ModelOut.from_model( + model=model, + latest_reference_uuid=reference_dataset.uuid, + latest_current_uuid=current_dataset.uuid, + ) def test_get_model_by_uuid_not_found(self): self.model_dao.get_by_uuid = MagicMock(return_value=None) @@ -66,6 +88,12 @@ def test_get_all_models_ok(self): sort=None, ) self.model_dao.get_all = MagicMock(return_value=page) + self.rd_dao.get_latest_reference_dataset_by_model_uuid = MagicMock( + return_value=None + ) + self.cd_dao.get_latest_current_dataset_by_model_uuid = MagicMock( + return_value=None + ) result = self.model_service.get_all_models( params=Params(page=1, size=10), order=OrderType.ASC, sort=None From eb475d6fa035cde8433a1d25d530141878da41bc Mon Sep 17 00:00:00 2001 From: Daniele Tria Date: Tue, 25 Jun 2024 16:16:17 +0200 Subject: [PATCH 2/3] feat: align with main, add get latest dataset method to get all models api --- api/app/services/model_service.py | 13 ++++++++++++- api/tests/services/model_service_test.py | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/api/app/services/model_service.py b/api/app/services/model_service.py index 6645f097..4e7ed4e0 100644 --- a/api/app/services/model_service.py +++ b/api/app/services/model_service.py @@ -53,7 +53,18 @@ def get_all_models( self, ) -> List[ModelOut]: models = self.model_dao.get_all() - return [ModelOut.from_model(model) for model in models] + model_out_list = [] + for model in models: + latest_reference_uuid, latest_current_uuid = self.get_latest_dataset_uuids( + model.uuid + ) + model_out = ModelOut.from_model( + model=model, + latest_reference_uuid=latest_reference_uuid, + latest_current_uuid=latest_current_uuid, + ) + model_out_list.append(model_out) + return model_out_list def get_all_models_paginated( self, diff --git a/api/tests/services/model_service_test.py b/api/tests/services/model_service_test.py index ff2ff648..1564c195 100644 --- a/api/tests/services/model_service_test.py +++ b/api/tests/services/model_service_test.py @@ -109,5 +109,27 @@ def test_get_all_models_paginated_ok(self): assert result.items[1].name == 'model2' assert result.items[2].name == 'model3' + def test_get_all_models_ok(self): + model1 = db_mock.get_sample_model(id=1, uuid=uuid.uuid4(), name='model1') + model2 = db_mock.get_sample_model(id=2, uuid=uuid.uuid4(), name='model2') + model3 = db_mock.get_sample_model(id=3, uuid=uuid.uuid4(), name='model3') + sample_models = [model1, model2, model3] + self.model_dao.get_all = MagicMock(return_value=sample_models) + self.rd_dao.get_latest_reference_dataset_by_model_uuid = MagicMock( + return_value=None + ) + self.cd_dao.get_latest_current_dataset_by_model_uuid = MagicMock( + return_value=None + ) + + result = self.model_service.get_all_models() + + self.model_dao.get_all.assert_called_once() + + assert len(result) == 3 + assert result[0].name == 'model1' + assert result[1].name == 'model2' + assert result[2].name == 'model3' + model_uuid = db_mock.MODEL_UUID From 24fa036fae8c8e6e143f3f1961747d99f989bc06 Mon Sep 17 00:00:00 2001 From: Daniele Tria Date: Tue, 25 Jun 2024 18:19:19 +0200 Subject: [PATCH 3/3] feat: add latest reference and current uuids to model definition --- .../models/model_definition.py | 2 ++ sdk/tests/apis/model_test.py | 16 ++++++++++++++++ sdk/tests/client_test.py | 10 +++++++++- sdk/tests/models/model_definition_test.py | 8 +++++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/sdk/radicalbit_platform_sdk/models/model_definition.py b/sdk/radicalbit_platform_sdk/models/model_definition.py index eb47445c..3fb6b4ba 100644 --- a/sdk/radicalbit_platform_sdk/models/model_definition.py +++ b/sdk/radicalbit_platform_sdk/models/model_definition.py @@ -68,5 +68,7 @@ class ModelDefinition(BaseModelDefinition): uuid: uuid_lib.UUID = Field(default_factory=lambda: uuid_lib.uuid4()) created_at: str = Field(alias='createdAt') updated_at: str = Field(alias='updatedAt') + latest_reference_uuid: Optional[uuid_lib.UUID] = Field(alias='latestReferenceUuid') + latest_current_uuid: Optional[uuid_lib.UUID] = Field(alias='latestCurrentUuid') model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel) diff --git a/sdk/tests/apis/model_test.py b/sdk/tests/apis/model_test.py index fd93c8b2..a33adc83 100644 --- a/sdk/tests/apis/model_test.py +++ b/sdk/tests/apis/model_test.py @@ -43,6 +43,8 @@ def test_delete_model(self): timestamp=column_def, created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=uuid.uuid4(), + latest_current_uuid=uuid.uuid4(), ), ) responses.add( @@ -80,6 +82,8 @@ def test_load_reference_dataset_without_object_name(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=uuid.uuid4(), + latest_current_uuid=uuid.uuid4(), ), ) response = ReferenceFileUpload( @@ -125,6 +129,8 @@ def test_load_reference_dataset_with_different_separator(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=uuid.uuid4(), + latest_current_uuid=uuid.uuid4(), ), ) response = ReferenceFileUpload( @@ -170,6 +176,8 @@ def test_load_reference_dataset_with_object_name(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=uuid.uuid4(), + latest_current_uuid=uuid.uuid4(), ), ) response = ReferenceFileUpload( @@ -206,6 +214,8 @@ def test_load_reference_dataset_wrong_headers(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=None, + latest_current_uuid=None, ), ) with pytest.raises(ClientError): @@ -239,6 +249,8 @@ def test_load_current_dataset_without_object_name(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=uuid.uuid4(), + latest_current_uuid=uuid.uuid4(), ), ) response = CurrentFileUpload( @@ -290,6 +302,8 @@ def test_load_current_dataset_with_object_name(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=uuid.uuid4(), + latest_current_uuid=uuid.uuid4(), ), ) response = CurrentFileUpload( @@ -332,6 +346,8 @@ def test_load_current_dataset_wrong_headers(self): timestamp=ColumnDefinition(name='created_at', type='str'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=None, + latest_current_uuid=None, ), ) with pytest.raises(ClientError): diff --git a/sdk/tests/client_test.py b/sdk/tests/client_test.py index c07d28be..1cf8fb24 100644 --- a/sdk/tests/client_test.py +++ b/sdk/tests/client_test.py @@ -39,6 +39,8 @@ def test_get_model(self): timestamp_name = 'when' timestamp_type = 'str' ts = str(time.time()) + latest_reference_uuid = uuid.uuid4() + latest_current_uuid = uuid.uuid4() json_string = f"""{{ "uuid": "{str(model_id)}", "name": "{name}", @@ -75,7 +77,9 @@ def test_get_model(self): "algorithm": "{algorithm}", "frameworks": "{frameworks}", "createdAt": "{ts}", - "updatedAt": "{ts}" + "updatedAt": "{ts}", + "latestReferenceUuid": "{str(latest_reference_uuid)}", + "latestCurrentUuid": "{str(latest_current_uuid)}" }}""" responses.add( method=responses.GET, @@ -152,6 +156,8 @@ def test_create_model(self): timestamp=model.timestamp, created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=None, + latest_current_uuid=None, ) responses.add( method=responses.POST, @@ -193,6 +199,8 @@ def test_search_models(self): timestamp=ColumnDefinition(name='tst_column', type='string'), created_at=str(time.time()), updated_at=str(time.time()), + latest_reference_uuid=None, + latest_current_uuid=None, ) responses.add( diff --git a/sdk/tests/models/model_definition_test.py b/sdk/tests/models/model_definition_test.py index c7a9a48b..7d904ff0 100644 --- a/sdk/tests/models/model_definition_test.py +++ b/sdk/tests/models/model_definition_test.py @@ -30,6 +30,8 @@ def test_model_definition_from_json(self): timestamp_name = 'when' timestamp_type = 'str' ts = str(time.time()) + latest_reference_uuid = uuid.uuid4() + latest_current_uuid = uuid.uuid4() json_string = f"""{{ "uuid": "{str(id)}", "name": "{name}", @@ -66,7 +68,9 @@ def test_model_definition_from_json(self): "algorithm": "{algorithm}", "frameworks": "{frameworks}", "createdAt": "{ts}", - "updatedAt": "{ts}" + "updatedAt": "{ts}", + "latestReferenceUuid": "{str(latest_reference_uuid)}", + "latestCurrentUuid": "{str(latest_current_uuid)}" }}""" model_definition = ModelDefinition.model_validate(json.loads(json_string)) assert model_definition.uuid == id @@ -79,6 +83,8 @@ def test_model_definition_from_json(self): assert model_definition.frameworks == frameworks assert model_definition.created_at == ts assert model_definition.updated_at == ts + assert model_definition.latest_reference_uuid == latest_reference_uuid + assert model_definition.latest_current_uuid == latest_current_uuid assert len(model_definition.features) == 1 assert model_definition.features[0].name == feature_name assert model_definition.features[0].type == feature_type