Skip to content

Commit

Permalink
Feat 226 update slims (#232)
Browse files Browse the repository at this point in the history
* reference data table models

* Updates slims mappings, response handling with no validation
  • Loading branch information
mekhlakapoor authored May 16, 2024
1 parent 1867366 commit 4fe1a93
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/aind_metadata_service/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class StatusCodes(Enum):
INVALID_DATA = 406
NO_DATA_FOUND = 404
MULTI_STATUS = 207
UNPROCESSIBLE_ENTITY = 422


class AindMetadataServiceClient:
Expand Down
44 changes: 27 additions & 17 deletions src/aind_metadata_service/response_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,42 +90,52 @@ def no_data_found_error_response(cls):
message="No Data Found.",
)

def _map_data_response(
self, pickled: bool = False
def _map_data_response( # noqa: C901
self, pickled: bool = False, validate: bool = True
) -> Union[Response, JSONResponse]:
"""Map ModelResponse with StatusCodes.DB_RESPONDED to a JSONResponse.
Perform validations."""
Perform validations, bypasses validation if flag is set to False."""
if len(self.aind_models) == 0:
status_code = StatusCodes.NO_DATA_FOUND.value
content_data = None
message = "No Data Found."
elif len(self.aind_models) == 1:
aind_model = self.aind_models[0]
# TODO: fix validation error catching
validation_error = None
try:
aind_model.__class__.model_validate(aind_model.model_dump())
except ValidationError as e:
validation_error = repr(e)
if pickled:
content_data = aind_model
else:
content_data = jsonable_encoder(
json.loads(aind_model.model_dump_json())
)
if validation_error:
status_code = StatusCodes.INVALID_DATA.value
message = f"Validation Errors: {validation_error}"
if validate:
validation_error = None
try:
aind_model.__class__.model_validate(
aind_model.model_dump()
)
except ValidationError as e:
validation_error = repr(e)
except (AttributeError, ValueError, KeyError) as oe:
validation_error = repr(oe)
if validation_error:
status_code = StatusCodes.INVALID_DATA.value
message = f"Validation Errors: {validation_error}"
else:
status_code = StatusCodes.VALID_DATA.value
message = "Valid Model."
# if validate flag is False
else:
status_code = StatusCodes.VALID_DATA.value
message = "Valid Model."
status_code = StatusCodes.UNPROCESSIBLE_ENTITY.value
message = (
"Valid Request Format. Models have not been validated."
)

if self.status_code == StatusCodes.MULTI_STATUS:
status_code = self.status_code.value
message = (
"There was an error retrieving records from one or more of"
" the databases."
)

else:
status_code = StatusCodes.MULTIPLE_RESPONSES.value
message = "Multiple Items Found."
Expand All @@ -148,7 +158,7 @@ def _map_data_response(
content=({"message": message, "data": content_data}),
)

def map_to_json_response(self) -> JSONResponse:
def map_to_json_response(self, validate: bool = True) -> JSONResponse:
"""Map a ModelResponse to a JSONResponse."""
if self.status_code == StatusCodes.CONNECTION_ERROR:
response = JSONResponse(
Expand All @@ -161,7 +171,7 @@ def map_to_json_response(self) -> JSONResponse:
content=({"message": self.message, "data": None}),
)
else:
response = self._map_data_response()
response = self._map_data_response(validate=validate)
return response

def map_to_pickled_response(self) -> Response:
Expand Down
4 changes: 2 additions & 2 deletions src/aind_metadata_service/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ async def retrieve_instrument(instrument_id, pickle: bool = False):
if pickle:
return model_response.map_to_pickled_response()
else:
return model_response.map_to_json_response()
return model_response.map_to_json_response(validate=False)


@app.get("/rig/{rig_id}")
Expand All @@ -132,7 +132,7 @@ async def retrieve_rig(rig_id, pickle: bool = False):
if pickle:
return model_response.map_to_pickled_response()
else:
return model_response.map_to_json_response()
return model_response.map_to_json_response(validate=False)


@app.get("/protocols/{protocol_name}")
Expand Down
4 changes: 2 additions & 2 deletions src/aind_metadata_service/slims/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def get_instrument_model_response(self, input_id) -> ModelResponse:
"""
try:
response = self.get_record_response(
"Instrument", equals("nstr_name", input_id)
"ReferenceDataRecord", equals("rdrc_name", input_id)
)
if response.status_code == 200:
models = self.extract_instrument_models_from_response(response)
Expand All @@ -213,7 +213,7 @@ def get_rig_model_response(self, input_id) -> ModelResponse:
"""
try:
response = self.get_record_response(
"Instrument", equals("nstr_name", input_id)
"ReferenceDataRecord", equals("rdrc_name", input_id)
)
if response.status_code == 200:
models = self.extract_rig_models_from_response(response)
Expand Down
49 changes: 49 additions & 0 deletions src/aind_metadata_service/slims/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,52 @@ class AttachmentTableRow(SlimsTableRow):
attm_file_filesize: Optional[int] = Field(None, title="File size")
grps_groupName: Optional[str] = Field(None, title="Group name")
attm_createdBy: Optional[str] = Field(None, title="Created by")


class ReferenceDataTableRow(SlimsTableRow):
"""A record pulled from slims Reference Data"""

rdty_name: Optional[str] = Field(None, title="Name")
rdty_uniqueIdentifier: Optional[str] = Field(
None, title="Unique identifier"
)
rdty_description: Optional[str] = Field(None, title="Description")
rdty_active: Optional[bool] = Field(None, title="Active")
rdty_fk_functionality: Optional[str] = Field(None, title="Submodule")
rdty_createdBy: Optional[str] = Field(None, title="Created by")
rdty_createdOn: Optional[int] = Field(
None, title="Created on", description="Timestamp in millis"
)
rdty_modifiedBy: Optional[int] = Field(
None, title="Modified by", description="Timestamp in millis"
)
attachmentCount: Optional[int] = Field(None, title="attachmentCount")
rdty_pk: Optional[int] = Field(None, title="rdty_pk")


class ReferenceDataRecordTableRow(SlimsTableRow):
"""A record pulled from slims Reference Data Record"""

rdrc_name: Optional[str] = Field(None, title="Name")
rdrc_uniqueIdentifier: Optional[str] = Field(
None, title="Unique Identifier"
)
rdrc_active: Optional[bool] = Field(None, title="Active")
rdrc_fk_user: Optional[str] = Field(None, title="User")
rdrc_fk_group: Optional[str] = Field(None, title="Group")
rdrc_cf_fk_rigInstrument: Optional[str] = Field(None, title="Instrument")
rdrc_cf_jsonAttachment: Optional[str] = Field(
None, title="JSON Attachment"
)
rdrc_fk_referenceDataType: Optional[str] = Field(
None, title="Reference Data Type"
)
rdrc_createdOn: Optional[int] = Field(
None, title="Created on", description="Timestamp in millis"
)
rdrc_modifiedBy: Optional[str] = Field(None, title="Modified by")
rdrc_modifiedOn: Optional[int] = Field(
None, title="Modified On", description="Timestamp in millis"
)
attachmentCount: Optional[int] = Field(None, title="attachmentCount")
rdrc_pk: Optional[int] = Field(None, title="rdrc_pk")
65 changes: 65 additions & 0 deletions tests/test_response_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pickle
import unittest
from pathlib import Path
from unittest.mock import MagicMock, patch

from aind_data_schema.core.procedures import Procedures
from aind_data_schema.core.subject import BreedingInfo, Species, Subject
Expand Down Expand Up @@ -174,6 +175,70 @@ def test_invalid_model(self):
pickle.loads(actual_pickled.body),
)

@patch("aind_metadata_service.response_handler.json.loads")
@patch("aind_metadata_service.response_handler.Subject.model_validate")
def test_key_error(self, mock_model_validate, mock_json_loads):
"""Test model_response with valid model."""
key_error = KeyError("Mocked Key Error")
mock_model_validate.side_effect = key_error
mock_json_loads.return_value = {"invalid_key": "value"}
aind_model = MagicMock(spec=Subject)
aind_model.model_dump.return_value = "{}"
model_response = ModelResponse(
status_code=StatusCodes.DB_RESPONDED, aind_models=[aind_model]
)
actual_json = model_response.map_to_json_response()
expected_json = JSONResponse(
status_code=406,
content=(
{
"message": f"Validation Errors: KeyError({key_error})",
"data": {"invalid_key": "value"},
}
),
)
self.assertEqual(
actual_json.status_code, StatusCodes.INVALID_DATA.value
)
self.assertEqual(actual_json.body, expected_json.body)

def test_no_validation(self):
"""Test model_response when validation is turned off."""
model = Subject.model_construct()
model_response = ModelResponse(
status_code=StatusCodes.DB_RESPONDED, aind_models=[model]
)
actual_json = model_response.map_to_json_response(validate=False)
actual_pickled = model_response.map_to_pickled_response()
model_json = jsonable_encoder(model)
expected_json = JSONResponse(
status_code=422,
content=(
{
"message": "Valid Request Format. "
"Models have not been validated.",
"data": model_json,
}
),
)
expected_pickled = Response(
status_code=406,
content=pickle.dumps(model),
media_type="application/octet-stream",
)

self.assertEqual(StatusCodes.DB_RESPONDED, model_response.status_code)
self.assertEqual(expected_json.status_code, actual_json.status_code)
self.assertEqual(expected_json.body, actual_json.body)

self.assertEqual(
expected_pickled.status_code, actual_pickled.status_code
)
self.assertEqual(
pickle.loads(expected_pickled.body),
pickle.loads(actual_pickled.body),
)

def test_multiple_items_response(self):
"""Test multiple item response"""
models = [sp_valid_model, sp_valid_model]
Expand Down

0 comments on commit 4fe1a93

Please sign in to comment.