diff --git a/api/src/opentrons/calibration_storage/ot2/models/v1.py b/api/src/opentrons/calibration_storage/ot2/models/v1.py index 98f7dadca1cc..e8170c74e334 100644 --- a/api/src/opentrons/calibration_storage/ot2/models/v1.py +++ b/api/src/opentrons/calibration_storage/ot2/models/v1.py @@ -32,8 +32,8 @@ class TipLengthModel(BaseModel): default_factory=CalibrationStatus, description="The status of the calibration data.", ) - uri: typing.Union[LabwareUri, Literal[""]] = Field( - ..., description="The tiprack URI associated with the tip length data." + definitionHash: str = Field( + ..., description="The tiprack hash associated with the tip length data." ) @validator("tipLength") diff --git a/api/src/opentrons/calibration_storage/ot2/tip_length.py b/api/src/opentrons/calibration_storage/ot2/tip_length.py index eca8f723f09f..95a829894de0 100644 --- a/api/src/opentrons/calibration_storage/ot2/tip_length.py +++ b/api/src/opentrons/calibration_storage/ot2/tip_length.py @@ -7,6 +7,7 @@ from opentrons import config from .. import file_operators as io, helpers, types as local_types +from opentrons_shared_data.pipette.dev_types import LabwareUri from opentrons.protocols.api_support.constants import OPENTRONS_NAMESPACE from opentrons.util.helpers import utc_now @@ -22,9 +23,9 @@ # Get Tip Length Calibration -def _conver_tip_length_model_to_dict( - to_dict: typing.Dict[str, v1.TipLengthModel] -) -> typing.Dict[str, typing.Any]: +def _convert_tip_length_model_to_dict( + to_dict: typing.Dict[LabwareUri, v1.TipLengthModel] +) -> typing.Dict[LabwareUri, typing.Any]: # This is a workaround since pydantic doesn't have a nice way to # add encoders when converting to a dict. dict_of_tip_lengths = {} @@ -35,17 +36,21 @@ def _conver_tip_length_model_to_dict( def tip_lengths_for_pipette( pipette_id: str, -) -> typing.Dict[str, v1.TipLengthModel]: +) -> typing.Dict[LabwareUri, v1.TipLengthModel]: tip_lengths = {} try: tip_length_filepath = config.get_tip_length_cal_path() / f"{pipette_id}.json" all_tip_lengths_for_pipette = io.read_cal_file(tip_length_filepath) - for tiprack, data in all_tip_lengths_for_pipette.items(): + for tiprack_uri, data in all_tip_lengths_for_pipette.items(): + # check if tiprack hash + if len(tiprack_uri.split("/")) == 1: + data["definitionHash"] = tiprack_uri + tiprack_uri = data.pop("uri") try: - tip_lengths[tiprack] = v1.TipLengthModel(**data) + tip_lengths[LabwareUri(tiprack_uri)] = v1.TipLengthModel(**data) except (json.JSONDecodeError, ValidationError): log.warning( - f"Tip length calibration is malformed for {tiprack} on {pipette_id}" + f"Tip length calibration is malformed for {tiprack_uri} on {pipette_id}" ) pass return tip_lengths @@ -64,10 +69,10 @@ def load_tip_length_calibration( :param pip_id: pipette you are using :param definition: full definition of the tiprack """ - labware_hash = helpers.hash_labware_def(definition) + labware_uri = helpers.uri_from_definition(definition) load_name = definition["parameters"]["loadName"] try: - return tip_lengths_for_pipette(pip_id)[labware_hash] + return tip_lengths_for_pipette(pip_id)[labware_uri] except KeyError as e: raise local_types.TipLengthCalNotFound( f"Tip length of {load_name} has not been " @@ -89,16 +94,16 @@ def get_all_tip_length_calibrations() -> typing.List[v1.TipLengthCalibration]: if filepath.stem == "index": continue tip_lengths = tip_lengths_for_pipette(filepath.stem) - for tiprack_hash, tip_length in tip_lengths.items(): + for tiprack_uri, tip_length in tip_lengths.items(): all_tip_lengths_available.append( v1.TipLengthCalibration( pipette=filepath.stem, - tiprack=tiprack_hash, + tiprack=tip_length.definitionHash, tipLength=tip_length.tipLength, lastModified=tip_length.lastModified, source=tip_length.source, status=tip_length.status, - uri=tip_length.uri, + uri=tiprack_uri, ) ) return all_tip_lengths_available @@ -129,7 +134,7 @@ def get_custom_tiprack_definition_for_tlc(labware_uri: str) -> "LabwareDefinitio # Delete Tip Length Calibration -def delete_tip_length_calibration(tiprack: str, pipette_id: str) -> None: +def delete_tip_length_calibration(tiprack: LabwareUri, pipette_id: str) -> None: """ Delete tip length calibration based on tiprack hash and pipette serial number @@ -144,7 +149,7 @@ def delete_tip_length_calibration(tiprack: str, pipette_id: str) -> None: del tip_lengths[tiprack] tip_length_dir = config.get_tip_length_cal_path() if tip_lengths: - dict_of_tip_lengths = _conver_tip_length_model_to_dict(tip_lengths) + dict_of_tip_lengths = _convert_tip_length_model_to_dict(tip_lengths) io.save_to_file(tip_length_dir, pipette_id, dict_of_tip_lengths) else: io.delete_file(tip_length_dir / f"{pipette_id}.json") @@ -176,7 +181,7 @@ def create_tip_length_data( cal_status: typing.Optional[ typing.Union[local_types.CalibrationStatus, v1.CalibrationStatus] ] = None, -) -> typing.Dict[str, v1.TipLengthModel]: +) -> typing.Dict[LabwareUri, v1.TipLengthModel]: """ Function to correctly format tip length data. @@ -197,13 +202,13 @@ def create_tip_length_data( lastModified=utc_now(), source=local_types.SourceType.user, status=cal_status_model, - uri=labware_uri, + definitionHash=labware_hash, ) if not definition.get("namespace") == OPENTRONS_NAMESPACE: _save_custom_tiprack_definition(labware_uri, definition) - data = {labware_hash: tip_length_data} + data = {labware_uri: tip_length_data} return data @@ -220,7 +225,7 @@ def _save_custom_tiprack_definition( def save_tip_length_calibration( pip_id: str, - tip_length_cal: typing.Dict[str, v1.TipLengthModel], + tip_length_cal: typing.Dict[LabwareUri, v1.TipLengthModel], ) -> None: """ Function used to save tip length calibration to file. @@ -235,5 +240,5 @@ def save_tip_length_calibration( all_tip_lengths.update(tip_length_cal) - dict_of_tip_lengths = _conver_tip_length_model_to_dict(all_tip_lengths) + dict_of_tip_lengths = _convert_tip_length_model_to_dict(all_tip_lengths) io.save_to_file(tip_length_dir_path, pip_id, dict_of_tip_lengths) diff --git a/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py b/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py index 29900d68a6d0..3ebb0d07678f 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py @@ -133,15 +133,15 @@ def load_tip_length_for_pipette( # TODO (lc 09-26-2022) We shouldn't have to do a hash twice. We should figure out what # information we actually need from the labware definition and pass it into # the `load_tip_length_calibration` function. - tiprack_hash = helpers.hash_labware_def(tiprack) + tiprack_uri = helpers.uri_from_definition(tiprack) return TipLengthCalibration( tip_length=tip_length_data.tipLength, source=tip_length_data.source, pipette=pipette_id, - tiprack=tiprack_hash, + tiprack=tip_length_data.definitionHash, last_modified=tip_length_data.lastModified, - uri=tip_length_data.uri, + uri=tiprack_uri, status=types.CalibrationStatus( markedAt=tip_length_data.status.markedAt, markedBad=tip_length_data.status.markedBad, diff --git a/api/tests/opentrons/calibration_storage/test_tip_length_ot2.py b/api/tests/opentrons/calibration_storage/test_tip_length_ot2.py index 93a208e00713..3d158cd44319 100644 --- a/api/tests/opentrons/calibration_storage/test_tip_length_ot2.py +++ b/api/tests/opentrons/calibration_storage/test_tip_length_ot2.py @@ -1,5 +1,5 @@ import pytest -from typing import cast, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from opentrons.calibration_storage import ( types as cs_types, @@ -18,7 +18,6 @@ if TYPE_CHECKING: from opentrons_shared_data.labware.dev_types import LabwareDefinition - from opentrons_shared_data.pipette.dev_types import LabwareUri @pytest.fixture @@ -48,13 +47,13 @@ def test_save_tip_length_calibration( """ assert tip_lengths_for_pipette("pip1") == {} assert tip_lengths_for_pipette("pip2") == {} - tip_rack_hash = helpers.hash_labware_def(minimal_labware_def) + tip_rack_uri = helpers.uri_from_definition(minimal_labware_def) tip_length1 = create_tip_length_data(minimal_labware_def, 22.0) tip_length2 = create_tip_length_data(minimal_labware_def, 31.0) save_tip_length_calibration("pip1", tip_length1) save_tip_length_calibration("pip2", tip_length2) - assert tip_lengths_for_pipette("pip1")[tip_rack_hash].tipLength == 22.0 - assert tip_lengths_for_pipette("pip2")[tip_rack_hash].tipLength == 31.0 + assert tip_lengths_for_pipette("pip1")[tip_rack_uri].tipLength == 22.0 + assert tip_lengths_for_pipette("pip2")[tip_rack_uri].tipLength == 31.0 def test_get_tip_length_calibration( @@ -64,11 +63,12 @@ def test_get_tip_length_calibration( Test ability to get a tip length calibration model. """ tip_length_data = load_tip_length_calibration("pip1", minimal_labware_def) + tip_rack_hash = helpers.hash_labware_def(minimal_labware_def) assert tip_length_data == models.v1.TipLengthModel( tipLength=22.0, source=cs_types.SourceType.user, lastModified=tip_length_data.lastModified, - uri=cast("LabwareUri", "opentronstest/minimal_labware_def/1"), + definitionHash=tip_rack_hash, ) with pytest.raises(cs_types.TipLengthCalNotFound): @@ -83,8 +83,8 @@ def test_delete_specific_tip_calibration( """ assert len(tip_lengths_for_pipette("pip1").keys()) == 2 assert tip_lengths_for_pipette("pip2") != {} - tip_rack_hash = helpers.hash_labware_def(minimal_labware_def) - delete_tip_length_calibration(tip_rack_hash, "pip1") + tip_rack_uri = helpers.uri_from_definition(minimal_labware_def) + delete_tip_length_calibration(tip_rack_uri, "pip1") assert len(tip_lengths_for_pipette("pip1").keys()) == 1 assert tip_lengths_for_pipette("pip2") != {} diff --git a/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py b/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py index b850803ba61d..11f2605679f6 100644 --- a/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py +++ b/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py @@ -81,7 +81,7 @@ def test_load_tip_length( tip_length_data = v1_models.TipLengthModel( tipLength=1.23, lastModified=datetime(year=2023, month=1, day=1), - uri=LabwareUri("def456"), + definitionHash="asdfghjk", source=subject.SourceType.factory, status=v1_models.CalibrationStatus( markedBad=True, @@ -99,6 +99,9 @@ def test_load_tip_length( decoy.when(calibration_storage.helpers.hash_labware_def(tip_rack_dict)).then_return( "asdfghjk" ) + decoy.when(calibration_storage.helpers.uri_from_definition(tip_rack_dict)).then_return( + "def456" + ) result = subject.load_tip_length_for_pipette( pipette_id="abc123", tiprack=tip_rack_definition diff --git a/robot-server/robot_server/service/tip_length/router.py b/robot-server/robot_server/service/tip_length/router.py index 2d6461e0b7f3..1e142ef90870 100644 --- a/robot-server/robot_server/service/tip_length/router.py +++ b/robot-server/robot_server/service/tip_length/router.py @@ -1,6 +1,6 @@ from starlette import status from fastapi import APIRouter, Depends -from typing import Optional +from typing import Optional, cast from opentrons.calibration_storage import types as cal_types from opentrons.calibration_storage.ot2 import tip_length, models @@ -12,6 +12,7 @@ from robot_server.service.shared_models import calibration as cal_model from opentrons.hardware_control import API +from opentrons_shared_data.pipette.dev_types import LabwareUri router = APIRouter() @@ -80,17 +81,19 @@ async def get_all_tip_length_calibrations( @router.delete( "/calibration/tip_length", description="Delete one specific tip length calibration by pipette " - "serial and tiprack hash", + "serial and tiprack uri", responses={status.HTTP_404_NOT_FOUND: {"model": ErrorBody}}, ) async def delete_specific_tip_length_calibration( - tiprack_hash: str, pipette_id: str, _: API = Depends(get_ot2_hardware) + tiprack_uri: str, pipette_id: str, _: API = Depends(get_ot2_hardware) ): try: - tip_length.delete_tip_length_calibration(tiprack_hash, pipette_id) + tip_length.delete_tip_length_calibration( + cast(LabwareUri, tiprack_uri), pipette_id + ) except cal_types.TipLengthCalNotFound: raise RobotServerError( definition=CommonErrorDef.RESOURCE_NOT_FOUND, resource="TipLengthCalibration", - id=f"{tiprack_hash}&{pipette_id}", + id=f"{tiprack_uri}&{pipette_id}", ) diff --git a/robot-server/tests/integration/test_tip_length_access.tavern.yaml b/robot-server/tests/integration/test_tip_length_access.tavern.yaml index 9b181e0877a8..35e8f6d2d07e 100644 --- a/robot-server/tests/integration/test_tip_length_access.tavern.yaml +++ b/robot-server/tests/integration/test_tip_length_access.tavern.yaml @@ -148,14 +148,14 @@ marks: *cal_marks stages: - name: DELETE request with correct pipette AND tiprack request: - url: "{ot2_server_base_url}/calibration/tip_length?pipette_id=321&tiprack_hash=130e17bb7b2f0c0472dcc01c1ff6f600ca1a6f9f86a90982df56c4bf43776824" + url: "{ot2_server_base_url}/calibration/tip_length?pipette_id=321&tiprack_uri=opentrons/opentrons_96_filtertiprack_200ul/1" method: DELETE response: status_code: 200 - name: DELETE request with incorrect pipette AND tiprack request: - url: "{ot2_server_base_url}/calibration/tip_length?pipette_id=321&tiprack_hash=wronghash" + url: "{ot2_server_base_url}/calibration/tip_length?pipette_id=321&tiprack_uri=wronguri" method: DELETE response: status_code: 404 diff --git a/robot-server/tests/service/tip_length/test_tip_length_management.py b/robot-server/tests/service/tip_length/test_tip_length_management.py index 628e6b0df29c..78946cfeabd7 100644 --- a/robot-server/tests/service/tip_length/test_tip_length_management.py +++ b/robot-server/tests/service/tip_length/test_tip_length_management.py @@ -1,5 +1,6 @@ PIPETTE_ID = "123" LW_HASH = "130e17bb7b2f0c0472dcc01c1ff6f600ca1a6f9f86a90982df56c4bf43776824" +LW_URI = "opentrons/opentrons_96_filtertiprack_200ul/1" FAKE_PIPETTE_ID = "fake_pip" WRONG_LW_HASH = "wronghash" @@ -33,11 +34,11 @@ def test_access_tip_length_calibration(api_client, set_up_tip_length_temp_direct def test_delete_tip_length_calibration( - api_client, set_up_pipette_offset_temp_directory + api_client, set_up_tip_length_temp_directory ): resp = api_client.delete( f"/calibration/tip_length?pipette_id={FAKE_PIPETTE_ID}&" - f"tiprack_hash={WRONG_LW_HASH}" + f"tiprack_uri={WRONG_LW_HASH}" ) assert resp.status_code == 404 body = resp.json() @@ -54,6 +55,6 @@ def test_delete_tip_length_calibration( } resp = api_client.delete( - f"/calibration/tip_length?pipette_id={PIPETTE_ID}&" f"tiprack_hash={LW_HASH}" + f"/calibration/tip_length?pipette_id={PIPETTE_ID}&" f"tiprack_uri={LW_URI}" ) assert resp.status_code == 200