From 2f1f783cc592ef4dafc3bf15e57de1ddaaea8dc7 Mon Sep 17 00:00:00 2001 From: jtyoung84 <104453205+jtyoung84@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:58:48 -0800 Subject: [PATCH 1/3] feat: adds bool to allow for partial rig matches (#335) --- src/aind_metadata_service/server.py | 12 +++-- src/aind_metadata_service/slims/client.py | 47 ++++++++++++++---- tests/slims/test_client.py | 58 +++++++++++++++++++++++ 3 files changed, 103 insertions(+), 14 deletions(-) diff --git a/src/aind_metadata_service/server.py b/src/aind_metadata_service/server.py index a8cde99..4e07999 100644 --- a/src/aind_metadata_service/server.py +++ b/src/aind_metadata_service/server.py @@ -115,16 +115,20 @@ async def retrieve_bergamo_session(job_settings: BergamoJobSettings): @app.get("/instrument/{instrument_id}") -async def retrieve_instrument(instrument_id): +async def retrieve_instrument(instrument_id, partial_match=False): """Retrieves instrument from slims""" - model_response = slims_client.get_instrument_model_response(instrument_id) + model_response = slims_client.get_instrument_model_response( + instrument_id, partial_match=partial_match + ) return model_response.map_to_json_response(validate=False) @app.get("/rig/{rig_id}") -async def retrieve_rig(rig_id): +async def retrieve_rig(rig_id, partial_match=False): """Retrieves rig from slims""" - model_response = slims_client.get_rig_model_response(rig_id) + model_response = slims_client.get_rig_model_response( + rig_id, partial_match=partial_match + ) return model_response.map_to_json_response(validate=False) diff --git a/src/aind_metadata_service/slims/client.py b/src/aind_metadata_service/slims/client.py index e0b38bb..9d4d0b4 100644 --- a/src/aind_metadata_service/slims/client.py +++ b/src/aind_metadata_service/slims/client.py @@ -15,6 +15,7 @@ from pydantic import Extra, Field, SecretStr from pydantic_settings import BaseSettings from requests.models import Response +from slims import criteria from slims.criteria import equals from aind_metadata_service.client import StatusCodes @@ -55,16 +56,29 @@ def _is_json_file(file: Response) -> bool: """Checks whether file is a json.""" return file.headers.get("Content-Type", "") == "application/json" - def get_instrument_model_response(self, input_id) -> ModelResponse: + def get_instrument_model_response( + self, input_id: str, partial_match: bool = False + ) -> ModelResponse: """ Fetches a response from SLIMS, extracts Instrument models from the response and creates a ModelResponse with said models. """ try: - inst = self.client.fetch_model(SlimsInstrumentRdrc, name=input_id) - attachment = self.client.fetch_attachment( - inst, equals("name", input_id) - ) + if partial_match: + inst = self.client.fetch_model( + SlimsInstrumentRdrc, + criteria.contains("name", input_id), + ) + attachment = self.client.fetch_attachment( + inst, + ) + else: + inst = self.client.fetch_model( + SlimsInstrumentRdrc, name=input_id + ) + attachment = self.client.fetch_attachment( + inst, equals("name", input_id) + ) if attachment: attachment_response = self.client.fetch_attachment_content( attachment @@ -91,16 +105,29 @@ def get_instrument_model_response(self, input_id) -> ModelResponse: logging.error(repr(e)) return ModelResponse.internal_server_error_response() - def get_rig_model_response(self, input_id) -> ModelResponse: + def get_rig_model_response( + self, input_id: str, partial_match: bool = False + ) -> ModelResponse: """ Fetches a response from SLIMS, extracts Rig models from the response and creates a ModelResponse with said models. """ try: - inst = self.client.fetch_model(SlimsInstrumentRdrc, name=input_id) - attachment = self.client.fetch_attachment( - inst, equals("name", input_id) - ) + if partial_match: + inst = self.client.fetch_model( + SlimsInstrumentRdrc, + criteria.contains("name", input_id), + ) + attachment = self.client.fetch_attachment( + inst, + ) + else: + inst = self.client.fetch_model( + SlimsInstrumentRdrc, name=input_id + ) + attachment = self.client.fetch_attachment( + inst, equals("name", input_id) + ) if attachment: attachment_response = self.client.fetch_attachment_content( attachment diff --git a/tests/slims/test_client.py b/tests/slims/test_client.py index 6c4b1d8..dfdb178 100644 --- a/tests/slims/test_client.py +++ b/tests/slims/test_client.py @@ -98,6 +98,37 @@ def test_get_instrument_model_response_success(self, mock_is_json_file): SlimsInstrumentRdrc, name="test_id" ) + @patch( + "aind_metadata_service.slims.client.SlimsHandler._is_json_file", + ) + @patch("slims.criteria.contains") + def test_get_instrument_model_response_success_partial_match( + self, mock_contains: MagicMock, mock_is_json_file: MagicMock + ): + """Test successful response from get_instrument_model_response when + partial_match is set to True""" + mock_inst = MagicMock() + mock_is_json_file.return_value = True + self.mock_client.fetch_model.return_value = mock_inst + mock_attachment = MagicMock() + self.mock_client.fetch_attachment.return_value = mock_attachment + mock_response = MagicMock(spec=Response) + mock_response.status_code = 200 + mock_response.json.return_value = {} + self.mock_client.fetch_attachment_content.return_value = mock_response + + with patch( + "aind_data_schema.core.instrument.Instrument.model_construct" + ) as mock_construct: + mock_construct.return_value = MagicMock(spec=Instrument) + response = self.handler.get_instrument_model_response( + "test_id", partial_match=True + ) + + self.assertEqual(response.status_code, StatusCodes.DB_RESPONDED) + mock_construct.assert_called_once_with(**mock_response.json()) + mock_contains.assert_called_once_with("name", "test_id") + def test_get_instrument_model_response_invalid_response(self): """Test response when the content is not valid response.""" mock_inst = MagicMock() @@ -193,6 +224,33 @@ def test_get_rig_model_response_success(self): SlimsInstrumentRdrc, name="test_id" ) + @patch("slims.criteria.contains") + def test_get_rig_model_response_success_partial_match( + self, mock_contains: MagicMock + ): + """Test successful response from get_rig_model_response when + partial_match is set to True.""" + mock_inst = MagicMock() + self.mock_client.fetch_model.return_value = mock_inst + mock_attachment = MagicMock() + self.mock_client.fetch_attachment.return_value = mock_attachment + mock_response = MagicMock(spec=Response) + mock_response.status_code = 200 + mock_response.json.return_value = {} + self.mock_client.fetch_attachment_content.return_value = mock_response + + with patch( + "aind_data_schema.core.rig.Rig.model_construct" + ) as mock_construct: + mock_construct.return_value = MagicMock(spec=Rig) + response = self.handler.get_rig_model_response( + "test_id", partial_match=True + ) + + self.assertEqual(response.status_code, StatusCodes.DB_RESPONDED) + mock_construct.assert_called_once_with(**mock_response.json()) + mock_contains.assert_called_once_with("name", "test_id") + def test_get_rig_model_response_not_found(self): """Test when SlimsRecordNotFound is raised.""" self.mock_client.fetch_model.side_effect = SlimsRecordNotFound From da8515ced5e3019031d436fcbdc46ee7d47efee1 Mon Sep 17 00:00:00 2001 From: jtyoung84 <104453205+jtyoung84@users.noreply.github.com> Date: Mon, 10 Feb 2025 10:51:38 -0800 Subject: [PATCH 2/3] feat: updates project name list (#343) --- src/aind_metadata_service/smartsheet/funding/mapping.py | 2 -- tests/smartsheet/test_funding.py | 1 - 2 files changed, 3 deletions(-) diff --git a/src/aind_metadata_service/smartsheet/funding/mapping.py b/src/aind_metadata_service/smartsheet/funding/mapping.py index cecdc36..9ed6923 100644 --- a/src/aind_metadata_service/smartsheet/funding/mapping.py +++ b/src/aind_metadata_service/smartsheet/funding/mapping.py @@ -164,8 +164,6 @@ def get_project_names(self) -> JSONResponse: if project_name is not None and subproject_name is None: project_names.add(project_name) elif project_name is not None and subproject_name is not None: - # Support legacy input. - project_names.add(project_name) project_names.add(f"{project_name} - {subproject_name}") return JSONResponse( status_code=200, diff --git a/tests/smartsheet/test_funding.py b/tests/smartsheet/test_funding.py index ff5e2fc..16fb09d 100644 --- a/tests/smartsheet/test_funding.py +++ b/tests/smartsheet/test_funding.py @@ -235,7 +235,6 @@ def test_get_project_names_success(self, mock_get_sheet: MagicMock): expected_response = { "message": "Success", "data": [ - "Discovery-Neuromodulator circuit dynamics during " "foraging", ( "Discovery-Neuromodulator circuit dynamics during foraging" " - Subproject 1 Electrophysiological Recordings from NM " From 7e09e65c8dd6864d7ea4a8a1ccdea8c8080e3a25 Mon Sep 17 00:00:00 2001 From: jtyoung84 <104453205+jtyoung84@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:22:14 -0800 Subject: [PATCH 3/3] release v0.19.2 --- src/aind_metadata_service/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aind_metadata_service/__init__.py b/src/aind_metadata_service/__init__.py index da8eeb8..ba48503 100644 --- a/src/aind_metadata_service/__init__.py +++ b/src/aind_metadata_service/__init__.py @@ -1,3 +1,3 @@ """REST service to retrieve metadata from databases.""" -__version__ = "0.19.1" +__version__ = "0.19.2"