diff --git a/api/src/opentrons/protocol_api/core/engine/__init__.py b/api/src/opentrons/protocol_api/core/engine/__init__.py index ded1ff960e0..69287a4edfa 100644 --- a/api/src/opentrons/protocol_api/core/engine/__init__.py +++ b/api/src/opentrons/protocol_api/core/engine/__init__.py @@ -10,6 +10,7 @@ from .well import WellCore ENGINE_CORE_API_VERSION: Final = APIVersion(2, 14) +SET_OFFSET_RESTORED_API_VERSION: Final = APIVersion(2, 18) __all__ = [ "ENGINE_CORE_API_VERSION", diff --git a/api/src/opentrons/protocol_api/core/engine/labware.py b/api/src/opentrons/protocol_api/core/engine/labware.py index 9b48b309aa2..301d7dcdece 100644 --- a/api/src/opentrons/protocol_api/core/engine/labware.py +++ b/api/src/opentrons/protocol_api/core/engine/labware.py @@ -10,9 +10,14 @@ from opentrons.protocol_engine.errors import LabwareNotOnDeckError, ModuleNotOnDeckError from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient +from opentrons.protocol_engine.types import ( + LabwareOffsetCreate, + LabwareOffsetVector, +) from opentrons.types import DeckSlotName, Point from opentrons.hardware_control.nozzle_manager import NozzleMap + from ..labware import AbstractLabware, LabwareLoadParams from .well import WellCore @@ -92,8 +97,28 @@ def get_quirks(self) -> List[str]: return self._definition.parameters.quirks or [] def set_calibration(self, delta: Point) -> None: - raise NotImplementedError( - "Setting a labware's calibration after it's been loaded is not supported." + """Add a labware offset for this labware at its current location. + + This will override any previous labware offsets for this definition URI and location, + even if the other labware offset was for a different specific labware instance. + """ + offset_location = self._engine_client.state.geometry.get_offset_location( + self._labware_id + ) + if not offset_location: + raise LabwareNotOnDeckError( + message=f"Cannot set offset for {self.get_name()} as it is not currently in a deck slot.", + details={"kind": "labware-not-in-slot"}, + ) + + request = LabwareOffsetCreate.construct( + definitionUri=self.get_uri(), + location=offset_location, + vector=LabwareOffsetVector(x=delta.x, y=delta.y, z=delta.z), + ) + self._engine_client.add_labware_offset(request) + self._engine_client.reload_labware( + labware_id=self._labware_id, ) def get_calibrated_offset(self) -> Point: diff --git a/api/src/opentrons/protocol_api/labware.py b/api/src/opentrons/protocol_api/labware.py index ecb4d06ac5b..3b7ae943208 100644 --- a/api/src/opentrons/protocol_api/labware.py +++ b/api/src/opentrons/protocol_api/labware.py @@ -35,7 +35,7 @@ from ._liquid import Liquid from ._types import OffDeckType from .core import well_grid -from .core.engine import ENGINE_CORE_API_VERSION +from .core.engine import ENGINE_CORE_API_VERSION, SET_OFFSET_RESTORED_API_VERSION from .core.labware import AbstractLabware from .core.module import AbstractModuleCore from .core.core_map import LoadedCoreMap @@ -594,16 +594,13 @@ def set_offset(self, x: float, y: float, z: float) -> None: Instead, use Labware Position Check in the app or on the touchscreen. """ - if self._api_version >= ENGINE_CORE_API_VERSION: - # TODO(mm, 2023-02-13): See Jira RCORE-535. - # - # Until that issue is resolved, the only way to simulate or run a - # >=ENGINE_CORE_API_VERSION protocol is through the Opentrons App. - # Therefore, in >=ENGINE_CORE_API_VERSION protocols, - # there's no legitimate way to use this method. + if ( + self._api_version >= ENGINE_CORE_API_VERSION + and self._api_version < SET_OFFSET_RESTORED_API_VERSION + ): raise APIVersionError( - "Labware.set_offset() is not supported when apiLevel is 2.14 or higher." - " Use a lower apiLevel" + "Labware.set_offset() is not supported when apiLevel is 2.14, 2.15, 2.16, or 2.17." + " Use apilevel 2.13 or below, or 2.18 or above to set offset," " or use the Opentrons App's Labware Position Check." ) else: diff --git a/api/src/opentrons/protocol_engine/clients/sync_client.py b/api/src/opentrons/protocol_engine/clients/sync_client.py index 2277ce815a4..ed6a499090b 100644 --- a/api/src/opentrons/protocol_engine/clients/sync_client.py +++ b/api/src/opentrons/protocol_engine/clients/sync_client.py @@ -27,6 +27,7 @@ Liquid, NozzleLayoutConfigurationType, AddressableOffsetVector, + LabwareOffsetCreate, ) from .transports import ChildThreadTransport @@ -92,6 +93,10 @@ def reset_tips(self, labware_id: str) -> None: labware_id=labware_id, ) + def add_labware_offset(self, request: LabwareOffsetCreate) -> None: + """Add a labware offset.""" + self._transport.call_method("add_labware_offset", request=request) + def set_pipette_movement_speed( self, pipette_id: str, speed: Optional[float] ) -> None: diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 4a37bf798c1..112d7d60ef4 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -42,6 +42,7 @@ AddressableAreaLocation, AddressableOffsetVector, StagingSlotLocation, + LabwareOffsetLocation, ) from .config import Config from .labware import LabwareView @@ -1090,3 +1091,48 @@ def _labware_gripper_offsets( return slot_based_offset or self._labware.get_labware_gripper_offsets( labware_id=labware_id, slot_name=None ) + + def get_offset_location(self, labware_id: str) -> Optional[LabwareOffsetLocation]: + """Provide the LabwareOffsetLocation specifying the current position of the labware. + + If the labware is in a location that cannot be specified by a LabwareOffsetLocation + (for instance, OFF_DECK) then return None. + """ + parent_location = self._labware.get_location(labware_id) + + if isinstance(parent_location, DeckSlotLocation): + return LabwareOffsetLocation( + slotName=parent_location.slotName, moduleModel=None, definitionUri=None + ) + elif isinstance(parent_location, ModuleLocation): + module_model = self._modules.get_requested_model(parent_location.moduleId) + module_location = self._modules.get_location(parent_location.moduleId) + return LabwareOffsetLocation( + slotName=module_location.slotName, + moduleModel=module_model, + definitionUri=None, + ) + elif isinstance(parent_location, OnLabwareLocation): + non_labware_parent_location = self._labware.get_parent_location(labware_id) + + parent_uri = self._labware.get_definition_uri(parent_location.labwareId) + if isinstance(non_labware_parent_location, DeckSlotLocation): + return LabwareOffsetLocation( + slotName=non_labware_parent_location.slotName, + moduleModel=None, + definitionUri=parent_uri, + ) + elif isinstance(non_labware_parent_location, ModuleLocation): + module_model = self._modules.get_requested_model( + non_labware_parent_location.moduleId + ) + module_location = self._modules.get_location( + non_labware_parent_location.moduleId + ) + return LabwareOffsetLocation( + slotName=module_location.slotName, + moduleModel=module_model, + definitionUri=parent_uri, + ) + + return None diff --git a/api/tests/opentrons/protocol_api/__init__.py b/api/tests/opentrons/protocol_api/__init__.py index 70938f49e66..8cf95c55e6d 100644 --- a/api/tests/opentrons/protocol_api/__init__.py +++ b/api/tests/opentrons/protocol_api/__init__.py @@ -1 +1,131 @@ """Tests for opentrons.protocol_api.""" +from typing import List, overload, Optional + +from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocol_api import ( + MAX_SUPPORTED_VERSION, + MIN_SUPPORTED_VERSION, + MIN_SUPPORTED_VERSION_FOR_FLEX, +) + + +def versions_at_or_above(from_version: APIVersion) -> List[APIVersion]: + """Get a list of versions >= the specified one.""" + return versions_between( + low_inclusive_bound=from_version, high_inclusive_bound=MAX_SUPPORTED_VERSION + ) + + +def versions_at_or_below( + from_version: APIVersion, flex_only: bool = False +) -> List[APIVersion]: + """Get a list of versions <= the specified one. + + Since there are different minimum supported versions for Flex and OT-2, specify which you care about + with the second argument. + """ + if flex_only: + return versions_between( + low_inclusive_bound=MIN_SUPPORTED_VERSION_FOR_FLEX, + high_inclusive_bound=from_version, + ) + else: + return versions_between( + low_inclusive_bound=MIN_SUPPORTED_VERSION, high_inclusive_bound=from_version + ) + + +def versions_above(from_version: APIVersion) -> List[APIVersion]: + """Get a list of versions > the specified one.""" + return versions_between( + low_exclusive_bound=from_version, high_inclusive_bound=MAX_SUPPORTED_VERSION + ) + + +def versions_below(from_version: APIVersion, flex_only: bool) -> List[APIVersion]: + """Get a list of versions < the specified one. + + Since there are different minimum supported versions for Flex and OT-2, specify which you care about + with the second argument. + """ + if flex_only: + return versions_between( + low_inclusive_bound=MIN_SUPPORTED_VERSION_FOR_FLEX, + high_exclusive_bound=from_version, + ) + else: + return versions_between( + low_inclusive_bound=MIN_SUPPORTED_VERSION, high_exclusive_bound=from_version + ) + + +@overload +def versions_between( + *, + low_inclusive_bound: APIVersion, + high_inclusive_bound: APIVersion, +) -> List[APIVersion]: + ... + + +@overload +def versions_between( + *, low_inclusive_bound: APIVersion, high_exclusive_bound: APIVersion +) -> List[APIVersion]: + ... + + +@overload +def versions_between( + *, + high_inclusive_bound: APIVersion, + low_exclusive_bound: APIVersion, +) -> List[APIVersion]: + ... + + +@overload +def versions_between( + *, low_exclusive_bound: APIVersion, high_exclusive_bound: APIVersion +) -> List[APIVersion]: + ... + + +def versions_between( + low_inclusive_bound: Optional[APIVersion] = None, + high_inclusive_bound: Optional[APIVersion] = None, + low_exclusive_bound: Optional[APIVersion] = None, + high_exclusive_bound: Optional[APIVersion] = None, +) -> List[APIVersion]: + """Build a list of versions based on exclusive and inclusive constraints.""" + if low_inclusive_bound and high_inclusive_bound: + assert ( + low_inclusive_bound.major == high_inclusive_bound.major + ), "You need to change this test when you add a new major version" + major = low_inclusive_bound.major + start = low_inclusive_bound.minor + stop = high_inclusive_bound.minor + 1 + elif low_inclusive_bound and high_exclusive_bound: + assert ( + low_inclusive_bound.major == high_exclusive_bound.major + ), "You need to change this test when you add a new major version" + major = low_inclusive_bound.major + start = low_inclusive_bound.minor + stop = high_exclusive_bound.minor + elif low_exclusive_bound and high_inclusive_bound: + assert ( + low_exclusive_bound.major == high_inclusive_bound.major + ), "You need to change this test when you add a new major version" + major = low_exclusive_bound.major + start = low_exclusive_bound.minor + 1 + stop = high_inclusive_bound.minor + 1 + elif low_exclusive_bound and high_exclusive_bound: + assert ( + low_exclusive_bound.major == high_exclusive_bound.major + ), "You need to change this test when you add a new major version" + major = low_exclusive_bound.major + start = low_exclusive_bound.minor + 1 + stop = high_exclusive_bound.minor + else: + raise ValueError("You must specify one low bound and one high bound") + return [APIVersion(major, minor) for minor in range(start, stop)] diff --git a/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py b/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py index 37d4511cce0..e02b5e7b8f7 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py @@ -19,9 +19,15 @@ from opentrons.types import DeckSlotName, Point from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocol_engine.errors import LabwareNotOnDeckError +from opentrons.protocol_engine.types import ( + LabwareOffsetCreate, + LabwareOffsetLocation, + LabwareOffsetVector, +) from opentrons.protocol_api.core.labware import LabwareLoadParams from opentrons.protocol_api.core.engine import LabwareCore, WellCore +from opentrons.calibration_storage.helpers import uri_from_details @pytest.fixture @@ -36,11 +42,9 @@ def mock_engine_client( ) -> EngineClient: """Get a mock ProtocolEngine synchronous client.""" engine_client = decoy.mock(cls=EngineClient) - decoy.when(engine_client.state.labware.get_definition("cool-labware")).then_return( labware_definition ) - return engine_client @@ -67,9 +71,87 @@ def test_get_load_params(subject: LabwareCore) -> None: assert subject.load_name == "world" -def test_set_calibration(subject: LabwareCore) -> None: - """It should raise if you attempt to set calibration.""" - with pytest.raises(NotImplementedError): +@pytest.mark.parametrize( + "labware_definition", + [ + LabwareDefinition.construct( # type: ignore[call-arg] + namespace="hello", + version=42, + parameters=LabwareDefinitionParameters.construct(loadName="world"), # type: ignore[call-arg] + ordering=[], + metadata=LabwareDefinitionMetadata.construct(displayName="what a cool labware"), # type: ignore[call-arg] + ) + ], +) +def test_set_calibration_succeeds_in_ok_location( + decoy: Decoy, + subject: LabwareCore, + mock_engine_client: EngineClient, + labware_definition: LabwareDefinition, +) -> None: + """It should pass along an AddLabwareOffset if possible.""" + decoy.when( + mock_engine_client.state.labware.get_definition_uri("cool-labware") + ).then_return( + uri_from_details( + load_name=labware_definition.parameters.loadName, + namespace=labware_definition.namespace, + version=labware_definition.version, + ) + ) + decoy.when( + mock_engine_client.state.labware.get_display_name("cool-labware") + ).then_return("what a cool labware") + location = LabwareOffsetLocation(slotName=DeckSlotName.SLOT_C2) + decoy.when( + mock_engine_client.state.geometry.get_offset_location("cool-labware") + ).then_return(location) + subject.set_calibration(Point(1, 2, 3)) + decoy.verify( + mock_engine_client.add_labware_offset( + LabwareOffsetCreate( + definitionUri="hello/world/42", + location=location, + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + ), + mock_engine_client.reload_labware( + labware_id="cool-labware", + ), + ) + + +@pytest.mark.parametrize( + "labware_definition", + [ + LabwareDefinition.construct( # type: ignore[call-arg] + namespace="hello", + version=42, + parameters=LabwareDefinitionParameters.construct(loadName="world"), # type: ignore[call-arg] + ordering=[], + ) + ], +) +def test_set_calibration_fails_in_bad_location( + decoy: Decoy, + subject: LabwareCore, + mock_engine_client: EngineClient, + labware_definition: LabwareDefinition, +) -> None: + """It should raise if you attempt to set calibration when the labware is not on deck.""" + decoy.when( + mock_engine_client.state.labware.get_definition_uri("cool-labware") + ).then_return( + uri_from_details( + load_name=labware_definition.parameters.loadName, + namespace=labware_definition.namespace, + version=labware_definition.version, + ) + ) + decoy.when( + mock_engine_client.state.geometry.get_offset_location("cool-labware") + ).then_return(None) + with pytest.raises(LabwareNotOnDeckError): subject.set_calibration(Point(1, 2, 3)) diff --git a/api/tests/opentrons/protocol_api/test_labware.py b/api/tests/opentrons/protocol_api/test_labware.py index 58bb3ca0b0d..b9b008e77a1 100644 --- a/api/tests/opentrons/protocol_api/test_labware.py +++ b/api/tests/opentrons/protocol_api/test_labware.py @@ -24,6 +24,8 @@ from opentrons.types import Point +from . import versions_at_or_below, versions_at_or_above, versions_between + @pytest.fixture(autouse=True) def _mock_well_grid_module(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: @@ -317,7 +319,7 @@ def test_child( assert subject.child == mock_labware -@pytest.mark.parametrize("api_version", [APIVersion(2, 13)]) +@pytest.mark.parametrize("api_version", versions_at_or_below(APIVersion(2, 13))) def test_set_offset_succeeds_on_low_api_version( decoy: Decoy, subject: Labware, @@ -328,8 +330,13 @@ def test_set_offset_succeeds_on_low_api_version( decoy.verify(mock_labware_core.set_calibration(Point(1, 2, 3))) -@pytest.mark.parametrize("api_version", [APIVersion(2, 14)]) -def test_set_offset_raises_on_high_api_version( +@pytest.mark.parametrize( + "api_version", + versions_between( + low_inclusive_bound=APIVersion(2, 14), high_inclusive_bound=APIVersion(2, 17) + ), +) +def test_set_offset_raises_on_intermediate_api_version( decoy: Decoy, subject: Labware, mock_labware_core: LabwareCore, @@ -339,7 +346,16 @@ def test_set_offset_raises_on_high_api_version( subject.set_offset(1, 2, 3) -@pytest.mark.parametrize("api_version", [APIVersion(2, 14)]) +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 18))) +def test_set_offset_succeeds_on_high_api_version( + decoy: Decoy, subject: Labware, mock_labware_core: LabwareCore +) -> None: + """It should not raise an API version error on the most recent versions.""" + subject.set_offset(1, 2, 3) + decoy.verify(mock_labware_core.set_calibration(Point(1, 2, 3))) + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 14))) def test_separate_calibration_raises_on_high_api_version( decoy: Decoy, subject: Labware, diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index b83c1c162fe..d4f299b1f87 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -1,12 +1,14 @@ """Test state getters for retrieving geometry views of state.""" import inspect +import json import pytest from decoy import Decoy -from typing import cast, List, Tuple, Optional, NamedTuple, Dict, Set +from typing import cast, List, Tuple, Optional, NamedTuple +from datetime import datetime from opentrons_shared_data.deck.dev_types import DeckDefinitionV5 -from opentrons_shared_data.robot.dev_types import RobotType +from opentrons_shared_data.deck import load as load_deck from opentrons_shared_data.labware.dev_types import LabwareUri from opentrons_shared_data.pipette import pipette_definition from opentrons.calibration_storage.helpers import uri_from_details @@ -18,6 +20,7 @@ Parameters as LabwareDefinitionParameters, CornerOffsetFromSlot, ) +from opentrons_shared_data import load_shared_data from opentrons.protocol_engine import errors from opentrons.protocol_engine.types import ( @@ -27,7 +30,6 @@ ModuleLocation, OnLabwareLocation, AddressableAreaLocation, - AddressableArea, ModuleOffsetVector, ModuleOffsetData, LoadedLabware, @@ -47,35 +49,45 @@ LabwareMovementOffsetData, LoadedPipette, TipGeometry, - PotentialCutoutFixture, - DeckConfigurationType, + ModuleDefinition, ) +from opentrons.protocol_engine.commands import ( + CommandStatus, + LoadLabwareResult, + LoadLabware, + LoadLabwareParams, + LoadModuleResult, + LoadModule, + LoadModuleParams, +) +from opentrons.protocol_engine.actions import SucceedCommandAction from opentrons.protocol_engine.state import move_types from opentrons.protocol_engine.state.config import Config -from opentrons.protocol_engine.state.labware import LabwareView -from opentrons.protocol_engine.state.modules import ModuleView +from opentrons.protocol_engine.state.labware import LabwareView, LabwareStore +from opentrons.protocol_engine.state.modules import ModuleView, ModuleStore from opentrons.protocol_engine.state.pipettes import ( PipetteView, + PipetteStore, StaticPipetteConfig, BoundingNozzlesOffsets, PipetteBoundingBoxOffsets, ) from opentrons.protocol_engine.state.addressable_areas import ( AddressableAreaView, - AddressableAreaState, + AddressableAreaStore, ) from opentrons.protocol_engine.state.geometry import GeometryView, _GripperMoveType from ..pipette_fixtures import get_default_nozzle_map @pytest.fixture -def labware_view(decoy: Decoy) -> LabwareView: +def mock_labware_view(decoy: Decoy) -> LabwareView: """Get a mock in the shape of a LabwareView.""" return decoy.mock(cls=LabwareView) @pytest.fixture -def module_view(decoy: Decoy) -> ModuleView: +def mock_module_view(decoy: Decoy) -> ModuleView: """Get a mock in the shape of a ModuleView.""" return decoy.mock(cls=ModuleView) @@ -87,7 +99,7 @@ def mock_pipette_view(decoy: Decoy) -> PipetteView: @pytest.fixture -def addressable_area_view(decoy: Decoy) -> AddressableAreaView: +def mock_addressable_area_view(decoy: Decoy) -> AddressableAreaView: """Get a mock in the shape of a AddressableAreaView.""" return decoy.mock(cls=AddressableAreaView) @@ -99,54 +111,145 @@ def patch_mock_move_types(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None monkeypatch.setattr(move_types, name, decoy.mock(func=func)) +@pytest.fixture +def use_mocks() -> bool: + """True to use mocks; add a use_mocks parameter of False to your test to use real states.""" + return True + + +@pytest.fixture +def deck_definition(state_config: Config) -> DeckDefinitionV5: + """Override as parameter to use a non-flex deck def.""" + return load_deck(name=state_config.deck_type.value, version=5) + + +@pytest.fixture +def state_config() -> Config: + """Get a state config. This is set up for a Flex.""" + return Config( + robot_type="OT-3 Standard", + deck_type=DeckType.OT3_STANDARD, + ) + + +@pytest.fixture +def labware_store(deck_definition: DeckDefinitionV5) -> LabwareStore: + """Get a labware store that can accept actions.""" + return LabwareStore(deck_definition=deck_definition, deck_fixed_labware=[]) + + +@pytest.fixture +def labware_view(labware_store: LabwareStore) -> LabwareView: + """Get a labware view of a real labware store.""" + return LabwareView(labware_store._state) + + +@pytest.fixture +def module_store(state_config: Config) -> ModuleStore: + """Get a module store that can accept actions.""" + return ModuleStore(config=state_config, module_calibration_offsets={}) + + +@pytest.fixture +def module_view(module_store: ModuleStore) -> ModuleView: + """Get a module view of a real labware store.""" + return ModuleView(module_store._state) + + +@pytest.fixture +def pipette_store() -> PipetteStore: + """Get a pipette store that can accept actions.""" + return PipetteStore() + + +@pytest.fixture +def pipette_view(pipette_store: PipetteStore) -> PipetteView: + """Get a pipette view of a real pipette store.""" + return PipetteView(pipette_store._state) + + +@pytest.fixture +def addressable_area_store( + state_config: Config, deck_definition: DeckDefinitionV5 +) -> AddressableAreaStore: + """Get an addressable area store that can accept actions.""" + return AddressableAreaStore( + deck_configuration=[], config=state_config, deck_definition=deck_definition + ) + + +@pytest.fixture +def addressable_area_view( + addressable_area_store: AddressableAreaStore, +) -> AddressableAreaView: + """Get an addressable area view of a real addressable are store.""" + return AddressableAreaView(addressable_area_store._state) + + +@pytest.fixture +def nice_labware_definition() -> LabwareDefinition: + """Load a nice labware def that won't blow up your terminal.""" + return LabwareDefinition.parse_obj( + json.loads( + load_shared_data("labware/fixtures/2/fixture_12_trough_v2.json").decode( + "utf-8" + ) + ) + ) + + +@pytest.fixture +def nice_adapter_definition() -> LabwareDefinition: + """Load a friendly adapter definition.""" + return LabwareDefinition.parse_obj( + json.loads( + load_shared_data( + "labware/definitions/2/opentrons_aluminum_flat_bottom_plate/1.json" + ).decode("utf-8") + ) + ) + + @pytest.fixture def subject( + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_pipette_view: PipetteView, + mock_addressable_area_view: AddressableAreaView, + state_config: Config, labware_view: LabwareView, module_view: ModuleView, - mock_pipette_view: PipetteView, + pipette_view: PipetteView, addressable_area_view: AddressableAreaView, + use_mocks: bool, ) -> GeometryView: - """Get a GeometryView with its store dependencies mocked out.""" + """Get a GeometryView with its store dependencies provided. + + By default, this will return a view with those dependencies as mocked. If you add a + parameter to your test of use_mocks that returns false, i.e. + + @pytest.mark.parametrize('use_mocks', [False]) + def my_cool_test(subject: GeometryView) -> None: + pass + + then the provided subject will use actual states. Over time, we should get more and more + things using use_mocks=True, and then flip the default + """ return GeometryView( - config=Config( - robot_type="OT-3 Standard", - deck_type=DeckType.OT3_STANDARD, - ), - labware_view=labware_view, - module_view=module_view, - pipette_view=mock_pipette_view, - addressable_area_view=addressable_area_view, - ) - - -def get_addressable_area_view( - loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, - potential_cutout_fixtures_by_cutout_id: Optional[ - Dict[str, Set[PotentialCutoutFixture]] - ] = None, - deck_definition: Optional[DeckDefinitionV5] = None, - deck_configuration: Optional[DeckConfigurationType] = None, - robot_type: RobotType = "OT-3 Standard", - use_simulated_deck_config: bool = False, -) -> AddressableAreaView: - """Get a labware view test subject.""" - state = AddressableAreaState( - loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, - potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id - or {}, - deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), - deck_configuration=deck_configuration or [], - robot_type=robot_type, - use_simulated_deck_config=use_simulated_deck_config, + config=state_config, + labware_view=mock_labware_view if use_mocks else labware_view, + module_view=mock_module_view if use_mocks else module_view, + pipette_view=mock_pipette_view if use_mocks else pipette_view, + addressable_area_view=mock_addressable_area_view + if use_mocks + else addressable_area_view, ) - return AddressableAreaView(state=state) - def test_get_labware_parent_position( decoy: Decoy, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should return a deck slot position for labware in a deck slot.""" @@ -157,9 +260,9 @@ def test_get_labware_parent_position( location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), offsetId=None, ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) result = subject.get_labware_parent_position("labware-id") @@ -169,7 +272,7 @@ def test_get_labware_parent_position( def test_raise_error_for_off_deck_labware_parent( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, subject: GeometryView, ) -> None: """Test raise error when fetching parent for labware that's off-deck.""" @@ -180,16 +283,16 @@ def test_raise_error_for_off_deck_labware_parent( location=OFF_DECK_LOCATION, offsetId=None, ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) with pytest.raises(errors.LabwareNotOnDeckError): subject.get_labware_parent_position("labware-id") def test_get_labware_parent_position_on_module( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: @@ -202,32 +305,34 @@ def test_get_labware_parent_position_on_module( offsetId=None, ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) decoy.when( - module_view.get_nominal_module_offset( + mock_module_view.get_nominal_module_offset( module_id="module-id", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=4, y=5, z=6)) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.THERMOCYCLER_MODULE_V2 ) decoy.when( - labware_view.get_module_overlap_offsets( + mock_labware_view.get_module_overlap_offsets( "labware-id", ModuleModel.THERMOCYCLER_MODULE_V2 ) ).then_return(OverlapOffset(x=1, y=2, z=3)) - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=2, y=3, z=4), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), @@ -241,9 +346,9 @@ def test_get_labware_parent_position_on_module( def test_get_labware_parent_position_on_labware( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: @@ -262,39 +367,41 @@ def test_get_labware_parent_position_on_labware( location=ModuleLocation(moduleId="module-id"), offsetId=None, ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) - decoy.when(labware_view.get("adapter-id")).then_return(adapter_data) - decoy.when(labware_view.get_dimensions("adapter-id")).then_return( + decoy.when(mock_labware_view.get("adapter-id")).then_return(adapter_data) + decoy.when(mock_labware_view.get_dimensions("adapter-id")).then_return( Dimensions(x=123, y=456, z=5) ) decoy.when( - labware_view.get_labware_overlap_offsets("labware-id", "xyz") + mock_labware_view.get_labware_overlap_offsets("labware-id", "xyz") ).then_return(OverlapOffset(x=1, y=2, z=2)) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) decoy.when( - module_view.get_nominal_module_offset( + mock_module_view.get_nominal_module_offset( module_id="module-id", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=1, y=2, z=3)) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.MAGNETIC_MODULE_V2 ) decoy.when( - labware_view.get_module_overlap_offsets( + mock_labware_view.get_module_overlap_offsets( "adapter-id", ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=-3, y=-2, z=-1)) - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=3, y=4, z=5), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), @@ -308,8 +415,8 @@ def test_get_labware_parent_position_on_labware( def test_module_calibration_offset_rotation( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: @@ -322,14 +429,14 @@ def test_module_calibration_offset_rotation( offsetId=None, ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_D1) ) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.TEMPERATURE_MODULE_V2 ) - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=2, y=3, z=4), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_D1), @@ -341,7 +448,7 @@ def test_module_calibration_offset_rotation( assert result == ModuleOffsetVector(x=2, y=3, z=4) # the module has changed from slot D1 to D3, so we should rotate the calibration offset 180 degrees along the z axis - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_D3) ) result = subject._get_calibrated_module_offset(ModuleLocation(moduleId="module-id")) @@ -350,7 +457,7 @@ def test_module_calibration_offset_rotation( # attempting to load the module calibration offset from an invalid slot in the middle of the deck (A2, B2, C2, D2) # is not be allowed since you can't even load a module in the middle to perform a module calibration in the # first place. So if someone manually edits the stored module calibration offset we will throw an assert error. - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=2, y=3, z=4), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_D2), @@ -365,8 +472,8 @@ def test_module_calibration_offset_rotation( def test_get_labware_origin_position( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should return a deck slot position with the labware's offset as its origin.""" @@ -378,10 +485,12 @@ def test_get_labware_origin_position( offsetId=None, ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) expected_parent = Point(1, 2, 3) @@ -400,8 +509,8 @@ def test_get_labware_origin_position( def test_get_labware_highest_z( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should get the absolute location of a labware's highest Z point.""" @@ -415,13 +524,15 @@ def test_get_labware_highest_z( slot_pos = Point(1, 2, 3) calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(slot_pos) highest_z = subject.get_labware_highest_z("labware-id") @@ -432,9 +543,9 @@ def test_get_labware_highest_z( def test_get_module_labware_highest_z( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: @@ -449,36 +560,40 @@ def test_get_module_labware_highest_z( slot_pos = Point(1, 2, 3) calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(slot_pos) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) decoy.when( - module_view.get_nominal_module_offset( + mock_module_view.get_nominal_module_offset( module_id="module-id", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=4, y=5, z=6)) - decoy.when(module_view.get_height_over_labware("module-id")).then_return(0.5) - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_height_over_labware("module-id")).then_return(0.5) + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=0, y=0, z=0), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), ) ) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.MAGNETIC_MODULE_V2 ) decoy.when( - labware_view.get_module_overlap_offsets( + mock_labware_view.get_module_overlap_offsets( "labware-id", ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=0, y=0, z=0)) @@ -490,15 +605,15 @@ def test_get_module_labware_highest_z( def test_get_all_obstacle_highest_z_no_equipment( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should return 0 if no loaded equipment.""" - decoy.when(module_view.get_all()).then_return([]) - decoy.when(labware_view.get_all()).then_return([]) - decoy.when(addressable_area_view.get_all()).then_return([]) + decoy.when(mock_module_view.get_all()).then_return([]) + decoy.when(mock_labware_view.get_all()).then_return([]) + decoy.when(mock_addressable_area_view.get_all()).then_return([]) result = subject.get_all_obstacle_highest_z() @@ -510,9 +625,9 @@ def test_get_all_obstacle_highest_z( well_plate_def: LabwareDefinition, reservoir_def: LabwareDefinition, falcon_tuberack_def: LabwareDefinition, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should get the highest Z amongst all labware.""" @@ -542,35 +657,37 @@ def test_get_all_obstacle_highest_z( off_deck_lw_offset = LabwareOffsetVector(x=1, y=-2, z=3) reservoir_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(module_view.get_all()).then_return([]) - decoy.when(addressable_area_view.get_all()).then_return([]) + decoy.when(mock_module_view.get_all()).then_return([]) + decoy.when(mock_addressable_area_view.get_all()).then_return([]) - decoy.when(labware_view.get_all()).then_return([plate, off_deck_lw, reservoir]) - decoy.when(labware_view.get("plate-id")).then_return(plate) - decoy.when(labware_view.get("off-deck-plate-id")).then_return(off_deck_lw) - decoy.when(labware_view.get("reservoir-id")).then_return(reservoir) + decoy.when(mock_labware_view.get_all()).then_return([plate, off_deck_lw, reservoir]) + decoy.when(mock_labware_view.get("plate-id")).then_return(plate) + decoy.when(mock_labware_view.get("off-deck-plate-id")).then_return(off_deck_lw) + decoy.when(mock_labware_view.get("reservoir-id")).then_return(reservoir) - decoy.when(labware_view.get_definition("plate-id")).then_return(well_plate_def) - decoy.when(labware_view.get_definition("off-deck-plate-id")).then_return( + decoy.when(mock_labware_view.get_definition("plate-id")).then_return(well_plate_def) + decoy.when(mock_labware_view.get_definition("off-deck-plate-id")).then_return( falcon_tuberack_def # Something tall. ) - decoy.when(labware_view.get_definition("reservoir-id")).then_return(reservoir_def) + decoy.when(mock_labware_view.get_definition("reservoir-id")).then_return( + reservoir_def + ) - decoy.when(labware_view.get_labware_offset_vector("plate-id")).then_return( + decoy.when(mock_labware_view.get_labware_offset_vector("plate-id")).then_return( plate_offset ) - decoy.when(labware_view.get_labware_offset_vector("off-deck-plate-id")).then_return( - off_deck_lw_offset - ) - decoy.when(labware_view.get_labware_offset_vector("reservoir-id")).then_return( + decoy.when( + mock_labware_view.get_labware_offset_vector("off-deck-plate-id") + ).then_return(off_deck_lw_offset) + decoy.when(mock_labware_view.get_labware_offset_vector("reservoir-id")).then_return( reservoir_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(Point(4, 5, 6)) plate_z = subject.get_labware_highest_z("plate-id") @@ -585,9 +702,9 @@ def test_get_all_obstacle_highest_z_with_staging_area( decoy: Decoy, well_plate_def: LabwareDefinition, falcon_tuberack_def: LabwareDefinition, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should get the highest Z amongst all labware including staging area.""" @@ -609,31 +726,31 @@ def test_get_all_obstacle_highest_z_with_staging_area( plate_offset = LabwareOffsetVector(x=1, y=-2, z=3) staging_lw_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(module_view.get_all()).then_return([]) - decoy.when(addressable_area_view.get_all()).then_return([]) + decoy.when(mock_module_view.get_all()).then_return([]) + decoy.when(mock_addressable_area_view.get_all()).then_return([]) - decoy.when(labware_view.get_all()).then_return([plate, staging_lw]) - decoy.when(labware_view.get("plate-id")).then_return(plate) - decoy.when(labware_view.get("staging-id")).then_return(staging_lw) + decoy.when(mock_labware_view.get_all()).then_return([plate, staging_lw]) + decoy.when(mock_labware_view.get("plate-id")).then_return(plate) + decoy.when(mock_labware_view.get("staging-id")).then_return(staging_lw) - decoy.when(labware_view.get_definition("plate-id")).then_return(well_plate_def) - decoy.when(labware_view.get_definition("staging-id")).then_return( + decoy.when(mock_labware_view.get_definition("plate-id")).then_return(well_plate_def) + decoy.when(mock_labware_view.get_definition("staging-id")).then_return( falcon_tuberack_def # Something tall. ) - decoy.when(labware_view.get_labware_offset_vector("plate-id")).then_return( + decoy.when(mock_labware_view.get_labware_offset_vector("plate-id")).then_return( plate_offset ) - decoy.when(labware_view.get_labware_offset_vector("staging-id")).then_return( + decoy.when(mock_labware_view.get_labware_offset_vector("staging-id")).then_return( staging_lw_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) - decoy.when(addressable_area_view.get_addressable_area_position("D4")).then_return( - Point(4, 5, 6) - ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position("D4") + ).then_return(Point(4, 5, 6)) staging_z = subject.get_labware_highest_z("staging-id") all_z = subject.get_all_obstacle_highest_z() @@ -643,21 +760,21 @@ def test_get_all_obstacle_highest_z_with_staging_area( def test_get_all_obstacle_highest_z_with_modules( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should get the highest Z including modules.""" module_1 = LoadedModule.construct(id="module-id-1") # type: ignore[call-arg] module_2 = LoadedModule.construct(id="module-id-2") # type: ignore[call-arg] - decoy.when(labware_view.get_all()).then_return([]) - decoy.when(addressable_area_view.get_all()).then_return([]) + decoy.when(mock_labware_view.get_all()).then_return([]) + decoy.when(mock_addressable_area_view.get_all()).then_return([]) - decoy.when(module_view.get_all()).then_return([module_1, module_2]) - decoy.when(module_view.get_overall_height("module-id-1")).then_return(42.0) - decoy.when(module_view.get_overall_height("module-id-2")).then_return(1337.0) + decoy.when(mock_module_view.get_all()).then_return([module_1, module_2]) + decoy.when(mock_module_view.get_overall_height("module-id-1")).then_return(42.0) + decoy.when(mock_module_view.get_overall_height("module-id-2")).then_return(1337.0) result = subject.get_all_obstacle_highest_z() @@ -666,20 +783,20 @@ def test_get_all_obstacle_highest_z_with_modules( def test_get_all_obstacle_highest_z_with_fixtures( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should get the highest Z including fixtures.""" - decoy.when(labware_view.get_all()).then_return([]) - decoy.when(module_view.get_all()).then_return([]) + decoy.when(mock_labware_view.get_all()).then_return([]) + decoy.when(mock_module_view.get_all()).then_return([]) - decoy.when(addressable_area_view.get_all_cutout_fixtures()).then_return( + decoy.when(mock_addressable_area_view.get_all_cutout_fixtures()).then_return( ["abc", "xyz"] ) - decoy.when(addressable_area_view.get_fixture_height("abc")).then_return(42.0) - decoy.when(addressable_area_view.get_fixture_height("xyz")).then_return(1337.0) + decoy.when(mock_addressable_area_view.get_fixture_height("abc")).then_return(42.0) + decoy.when(mock_addressable_area_view.get_fixture_height("xyz")).then_return(1337.0) result = subject.get_all_obstacle_highest_z() @@ -688,8 +805,8 @@ def test_get_all_obstacle_highest_z_with_fixtures( def test_get_highest_z_in_slot_with_single_labware( decoy: Decoy, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, well_plate_def: LabwareDefinition, ) -> None: @@ -705,21 +822,21 @@ def test_get_highest_z_in_slot_with_single_labware( slot_pos = Point(1, 2, 3) calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return( + decoy.when(mock_labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return( labware_in_slot ) - decoy.when(labware_view.get_id_by_labware("just-labware-id")).then_raise( + decoy.when(mock_labware_view.get_id_by_labware("just-labware-id")).then_raise( errors.LabwareNotLoadedOnLabwareError("no more labware") ) - decoy.when(labware_view.get("just-labware-id")).then_return(labware_in_slot) - decoy.when(labware_view.get_definition("just-labware-id")).then_return( + decoy.when(mock_labware_view.get("just-labware-id")).then_return(labware_in_slot) + decoy.when(mock_labware_view.get_definition("just-labware-id")).then_return( well_plate_def ) - decoy.when(labware_view.get_labware_offset_vector("just-labware-id")).then_return( - calibration_offset - ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_labware_view.get_labware_offset_vector("just-labware-id") + ).then_return(calibration_offset) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(slot_pos) expected_highest_z = well_plate_def.dimensions.zDimension + 3 + 3 @@ -731,9 +848,9 @@ def test_get_highest_z_in_slot_with_single_labware( def test_get_highest_z_in_slot_with_single_module( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ot2_standard_deck_def: DeckDefinitionV5, ) -> None: @@ -745,15 +862,19 @@ def test_get_highest_z_in_slot_with_single_module( location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), ) - decoy.when(module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(module_in_slot) - decoy.when(labware_view.get_id_by_module("only-module")).then_raise( + decoy.when(mock_module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return( + module_in_slot + ) + decoy.when(mock_labware_view.get_id_by_module("only-module")).then_raise( errors.LabwareNotLoadedOnModuleError("only module") ) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) decoy.when( - module_view.get_module_highest_z( + mock_module_view.get_module_highest_z( module_id="only-module", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(12345) @@ -768,8 +889,8 @@ def test_get_highest_z_in_slot_with_single_module( # in an easier-to-understand manner. def test_get_highest_z_in_slot_with_stacked_labware_on_slot( decoy: Decoy, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, well_plate_def: LabwareDefinition, ) -> None: @@ -801,50 +922,50 @@ def test_get_highest_z_in_slot_with_stacked_labware_on_slot( slot_pos = Point(11, 22, 33) top_lw_lpc_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return( + decoy.when(mock_labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return( labware_in_slot ) - decoy.when(labware_view.get_id_by_labware("bottom-labware-id")).then_return( + decoy.when(mock_labware_view.get_id_by_labware("bottom-labware-id")).then_return( "middle-labware-id" ) - decoy.when(labware_view.get_id_by_labware("middle-labware-id")).then_return( + decoy.when(mock_labware_view.get_id_by_labware("middle-labware-id")).then_return( "top-labware-id" ) - decoy.when(labware_view.get_id_by_labware("top-labware-id")).then_raise( + decoy.when(mock_labware_view.get_id_by_labware("top-labware-id")).then_raise( errors.LabwareNotLoadedOnLabwareError("top labware") ) - decoy.when(labware_view.get("bottom-labware-id")).then_return(labware_in_slot) - decoy.when(labware_view.get("middle-labware-id")).then_return(middle_labware) - decoy.when(labware_view.get("top-labware-id")).then_return(top_labware) + decoy.when(mock_labware_view.get("bottom-labware-id")).then_return(labware_in_slot) + decoy.when(mock_labware_view.get("middle-labware-id")).then_return(middle_labware) + decoy.when(mock_labware_view.get("top-labware-id")).then_return(top_labware) - decoy.when(labware_view.get_definition("top-labware-id")).then_return( + decoy.when(mock_labware_view.get_definition("top-labware-id")).then_return( well_plate_def ) - decoy.when(labware_view.get_labware_offset_vector("top-labware-id")).then_return( - top_lw_lpc_offset - ) - decoy.when(labware_view.get_dimensions("middle-labware-id")).then_return( + decoy.when( + mock_labware_view.get_labware_offset_vector("top-labware-id") + ).then_return(top_lw_lpc_offset) + decoy.when(mock_labware_view.get_dimensions("middle-labware-id")).then_return( Dimensions(x=10, y=20, z=30) ) - decoy.when(labware_view.get_dimensions("bottom-labware-id")).then_return( + decoy.when(mock_labware_view.get_dimensions("bottom-labware-id")).then_return( Dimensions(x=11, y=12, z=13) ) decoy.when( - labware_view.get_labware_overlap_offsets( + mock_labware_view.get_labware_overlap_offsets( "top-labware-id", below_labware_name="middle-labware-name" ) ).then_return(OverlapOffset(x=4, y=5, z=6)) decoy.when( - labware_view.get_labware_overlap_offsets( + mock_labware_view.get_labware_overlap_offsets( "middle-labware-id", below_labware_name="bottom-labware-name" ) ).then_return(OverlapOffset(x=7, y=8, z=9)) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(slot_pos) expected_highest_z = ( @@ -861,9 +982,9 @@ def test_get_highest_z_in_slot_with_stacked_labware_on_slot( # in an easier-to-understand manner. def test_get_highest_z_in_slot_with_labware_stack_on_module( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, well_plate_def: LabwareDefinition, ot2_standard_deck_def: DeckDefinitionV5, @@ -895,57 +1016,63 @@ def test_get_highest_z_in_slot_with_labware_stack_on_module( slot_pos = Point(11, 22, 33) top_lw_lpc_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(module_view.get("module-id")).then_return(module_on_slot) - decoy.when(module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(module_on_slot) + decoy.when(mock_module_view.get("module-id")).then_return(module_on_slot) + decoy.when(mock_module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return( + module_on_slot + ) - decoy.when(labware_view.get_id_by_module("module-id")).then_return("adapter-id") - decoy.when(labware_view.get_id_by_labware("adapter-id")).then_return( + decoy.when(mock_labware_view.get_id_by_module("module-id")).then_return( + "adapter-id" + ) + decoy.when(mock_labware_view.get_id_by_labware("adapter-id")).then_return( "top-labware-id" ) - decoy.when(labware_view.get_id_by_labware("top-labware-id")).then_raise( + decoy.when(mock_labware_view.get_id_by_labware("top-labware-id")).then_raise( errors.LabwareNotLoadedOnLabwareError("top labware") ) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) - decoy.when(labware_view.get_definition("top-labware-id")).then_return( + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) + decoy.when(mock_labware_view.get_definition("top-labware-id")).then_return( well_plate_def ) - decoy.when(labware_view.get("adapter-id")).then_return(adapter) - decoy.when(labware_view.get("top-labware-id")).then_return(top_labware) - decoy.when(labware_view.get_labware_offset_vector("top-labware-id")).then_return( - top_lw_lpc_offset - ) - decoy.when(labware_view.get_dimensions("adapter-id")).then_return( + decoy.when(mock_labware_view.get("adapter-id")).then_return(adapter) + decoy.when(mock_labware_view.get("top-labware-id")).then_return(top_labware) + decoy.when( + mock_labware_view.get_labware_offset_vector("top-labware-id") + ).then_return(top_lw_lpc_offset) + decoy.when(mock_labware_view.get_dimensions("adapter-id")).then_return( Dimensions(x=10, y=20, z=30) ) decoy.when( - labware_view.get_labware_overlap_offsets( + mock_labware_view.get_labware_overlap_offsets( labware_id="top-labware-id", below_labware_name="adapter-name" ) ).then_return(OverlapOffset(x=4, y=5, z=6)) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) decoy.when( - module_view.get_nominal_module_offset( + mock_module_view.get_nominal_module_offset( module_id="module-id", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=40, y=50, z=60)) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.TEMPERATURE_MODULE_V2 ) decoy.when( - labware_view.get_module_overlap_offsets( + mock_labware_view.get_module_overlap_offsets( "adapter-id", ModuleModel.TEMPERATURE_MODULE_V2 ) ).then_return(OverlapOffset(x=1.1, y=2.2, z=3.3)) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(slot_pos) expected_highest_z = ( @@ -972,9 +1099,9 @@ def test_get_highest_z_in_slot_with_labware_stack_on_module( def test_get_min_travel_z( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, location: Optional[CurrentWell], min_z_height: Optional[float], expected_min_z: float, @@ -989,18 +1116,20 @@ def test_get_min_travel_z( offsetId="offset-id", ) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( LabwareOffsetVector(x=0, y=0, z=3) ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(0, 0, 3)) - decoy.when(module_view.get_all()).then_return([]) - decoy.when(labware_view.get_all()).then_return([]) - decoy.when(addressable_area_view.get_all()).then_return([]) + decoy.when(mock_module_view.get_all()).then_return([]) + decoy.when(mock_labware_view.get_all()).then_return([]) + decoy.when(mock_addressable_area_view.get_all()).then_return([]) min_travel_z = subject.get_min_travel_z( "pipette-id", "labware-id", location, min_z_height @@ -1012,8 +1141,8 @@ def test_get_min_travel_z( def test_get_labware_position( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should return the slot position plus calibrated offset.""" @@ -1027,13 +1156,15 @@ def test_get_labware_position( calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) slot_pos = Point(4, 5, 6) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) position = subject.get_labware_position(labware_id="labware-id") @@ -1048,8 +1179,8 @@ def test_get_labware_position( def test_get_well_position( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should be able to get the position of a well top in a labware.""" @@ -1064,15 +1195,17 @@ def test_get_well_position( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) @@ -1088,12 +1221,12 @@ def test_get_well_position( def test_get_well_height( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, + mock_labware_view: LabwareView, subject: GeometryView, ) -> None: """It should be able to get the well height.""" well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) assert subject.get_well_height("labware-id", "B2") == 10.67 @@ -1102,9 +1235,9 @@ def test_get_well_height( def test_get_module_labware_well_position( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: @@ -1120,38 +1253,42 @@ def test_get_module_labware_well_position( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_4) ) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) decoy.when( - module_view.get_nominal_module_offset( + mock_module_view.get_nominal_module_offset( module_id="module-id", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=4, y=5, z=6)) - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=0, y=0, z=0), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), ) ) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.MAGNETIC_MODULE_V2 ) decoy.when( - labware_view.get_module_overlap_offsets( + mock_labware_view.get_module_overlap_offsets( "labware-id", ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=0, y=0, z=0)) @@ -1167,8 +1304,8 @@ def test_get_module_labware_well_position( def test_get_well_position_with_top_offset( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should be able to get the position of a well top in a labware.""" @@ -1183,15 +1320,17 @@ def test_get_well_position_with_top_offset( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) @@ -1214,8 +1353,8 @@ def test_get_well_position_with_top_offset( def test_get_well_position_with_bottom_offset( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should be able to get the position of a well bottom in a labware.""" @@ -1230,15 +1369,17 @@ def test_get_well_position_with_bottom_offset( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) @@ -1261,8 +1402,8 @@ def test_get_well_position_with_bottom_offset( def test_get_well_position_with_center_offset( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should be able to get the position of a well center in a labware.""" @@ -1277,15 +1418,17 @@ def test_get_well_position_with_center_offset( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) @@ -1308,8 +1451,8 @@ def test_get_well_position_with_center_offset( def test_get_relative_well_location( decoy: Decoy, well_plate_def: LabwareDefinition, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should get the relative location of a well given an absolute position.""" @@ -1324,15 +1467,17 @@ def test_get_relative_well_location( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - decoy.when(labware_view.get("labware-id")).then_return(labware_data) - decoy.when(labware_view.get_definition("labware-id")).then_return(well_plate_def) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) ).then_return(slot_pos) - decoy.when(labware_view.get_well_definition("labware-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( well_def ) @@ -1358,12 +1503,12 @@ def test_get_relative_well_location( def test_get_nominal_effective_tip_length( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, mock_pipette_view: PipetteView, subject: GeometryView, ) -> None: """It should get the effective tip length from a labware ID and pipette config.""" - decoy.when(labware_view.get_definition_uri("tip-rack-id")).then_return( + decoy.when(mock_labware_view.get_definition_uri("tip-rack-id")).then_return( LabwareUri("opentrons/opentrons_96_tiprack_300ul/1") ) @@ -1375,7 +1520,7 @@ def test_get_nominal_effective_tip_length( ).then_return(10) decoy.when( - labware_view.get_tip_length(labware_id="tip-rack-id", overlap=10) + mock_labware_view.get_tip_length(labware_id="tip-rack-id", overlap=10) ).then_return(100) result = subject.get_nominal_effective_tip_length( @@ -1389,18 +1534,18 @@ def test_get_nominal_effective_tip_length( def test_get_nominal_tip_geometry( decoy: Decoy, tip_rack_def: LabwareDefinition, - labware_view: LabwareView, + mock_labware_view: LabwareView, mock_pipette_view: PipetteView, subject: GeometryView, ) -> None: """It should get a "well's" tip geometry.""" well_def = tip_rack_def.wells["B2"] - decoy.when(labware_view.get_definition_uri("tip-rack-id")).then_return( + decoy.when(mock_labware_view.get_definition_uri("tip-rack-id")).then_return( LabwareUri("opentrons/opentrons_96_tiprack_300ul/1") ) - decoy.when(labware_view.get_well_definition("tip-rack-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("tip-rack-id", "B2")).then_return( well_def ) @@ -1412,7 +1557,7 @@ def test_get_nominal_tip_geometry( ).then_return(10) decoy.when( - labware_view.get_tip_length(labware_id="tip-rack-id", overlap=10) + mock_labware_view.get_tip_length(labware_id="tip-rack-id", overlap=10) ).then_return(100) result = subject.get_nominal_tip_geometry( @@ -1429,14 +1574,14 @@ def test_get_nominal_tip_geometry( def test_get_nominal_tip_geometry_raises( decoy: Decoy, tip_rack_def: LabwareDefinition, - labware_view: LabwareView, + mock_labware_view: LabwareView, subject: GeometryView, ) -> None: """It should raise LabwareIsNotTipRackError if well is not circular.""" well_def = tip_rack_def.wells["B2"] well_def.shape = "rectangular" - decoy.when(labware_view.get_well_definition("tip-rack-id", "B2")).then_return( + decoy.when(mock_labware_view.get_well_definition("tip-rack-id", "B2")).then_return( well_def ) @@ -1448,18 +1593,20 @@ def test_get_nominal_tip_geometry_raises( def test_get_tip_drop_location( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, mock_pipette_view: PipetteView, subject: GeometryView, tip_rack_def: LabwareDefinition, ) -> None: """It should get relative drop tip location for a pipette/labware combo.""" - decoy.when(labware_view.get_definition("tip-rack-id")).then_return(tip_rack_def) + decoy.when(mock_labware_view.get_definition("tip-rack-id")).then_return( + tip_rack_def + ) decoy.when(mock_pipette_view.get_return_tip_scale("pipette-id")).then_return(0.5) decoy.when( - labware_view.get_tip_drop_z_offset( + mock_labware_view.get_tip_drop_z_offset( labware_id="tip-rack-id", length_scale=0.5, additional_offset=3 ) ).then_return(1337) @@ -1478,12 +1625,14 @@ def test_get_tip_drop_location( def test_get_tip_drop_location_with_non_tiprack( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, subject: GeometryView, reservoir_def: LabwareDefinition, ) -> None: """It should get relative drop tip location for a labware that is not a tiprack.""" - decoy.when(labware_view.get_definition("labware-id")).then_return(reservoir_def) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + reservoir_def + ) location = subject.get_checked_tip_drop_location( pipette_id="pipette-id", @@ -1502,12 +1651,14 @@ def test_get_tip_drop_location_with_non_tiprack( def test_get_tip_drop_explicit_location( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, subject: GeometryView, tip_rack_def: LabwareDefinition, ) -> None: """It should pass the location through if origin is not WellOrigin.DROP_TIP.""" - decoy.when(labware_view.get_definition("tip-rack-id")).then_return(tip_rack_def) + decoy.when(mock_labware_view.get_definition("tip-rack-id")).then_return( + tip_rack_def + ) input_location = DropTipWellLocation( origin=DropTipWellOrigin.TOP, @@ -1526,12 +1677,12 @@ def test_get_tip_drop_explicit_location( def test_get_ancestor_slot_name( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """It should get name of ancestor slot of labware.""" - decoy.when(labware_view.get("labware-1")).then_return( + decoy.when(mock_labware_view.get("labware-1")).then_return( LoadedLabware( id="labware-1", loadName="load-name", @@ -1541,7 +1692,7 @@ def test_get_ancestor_slot_name( ) assert subject.get_ancestor_slot_name("labware-1") == DeckSlotName.SLOT_4 - decoy.when(labware_view.get("labware-2")).then_return( + decoy.when(mock_labware_view.get("labware-2")).then_return( LoadedLabware( id="labware-2", loadName="load-name", @@ -1549,7 +1700,7 @@ def test_get_ancestor_slot_name( location=ModuleLocation(moduleId="4321"), ) ) - decoy.when(module_view.get_location("4321")).then_return( + decoy.when(mock_module_view.get_location("4321")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_1) ) assert subject.get_ancestor_slot_name("labware-2") == DeckSlotName.SLOT_1 @@ -1557,8 +1708,8 @@ def test_get_ancestor_slot_name( def test_ensure_location_not_occupied_raises( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """It should raise error when labware is present in given location.""" @@ -1567,20 +1718,20 @@ def test_ensure_location_not_occupied_raises( assert subject.ensure_location_not_occupied(location=slot_location) == slot_location # Raise if labware in location - decoy.when(labware_view.raise_if_labware_in_location(slot_location)).then_raise( - errors.LocationIsOccupiedError("Woops!") - ) + decoy.when( + mock_labware_view.raise_if_labware_in_location(slot_location) + ).then_raise(errors.LocationIsOccupiedError("Woops!")) with pytest.raises(errors.LocationIsOccupiedError): subject.ensure_location_not_occupied(location=slot_location) # Raise if module in location module_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_1) - decoy.when(labware_view.raise_if_labware_in_location(module_location)).then_return( - None - ) - decoy.when(module_view.raise_if_module_in_location(module_location)).then_raise( - errors.LocationIsOccupiedError("Woops again!") - ) + decoy.when( + mock_labware_view.raise_if_labware_in_location(module_location) + ).then_return(None) + decoy.when( + mock_module_view.raise_if_module_in_location(module_location) + ).then_raise(errors.LocationIsOccupiedError("Woops again!")) with pytest.raises(errors.LocationIsOccupiedError): subject.ensure_location_not_occupied(location=module_location) @@ -1593,19 +1744,19 @@ def test_ensure_location_not_occupied_raises( def test_get_labware_grip_point( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: """It should get the grip point of the labware at the specified location.""" decoy.when( - labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom("labware-id") ).then_return(100) decoy.when( - addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_1.id) + mock_addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_1.id) ).then_return(Point(x=101, y=102, z=103)) labware_center = subject.get_labware_grip_point( labware_id="labware-id", location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1) @@ -1616,14 +1767,14 @@ def test_get_labware_grip_point( def test_get_labware_grip_point_on_labware( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: """It should get the grip point of a labware on another labware.""" - decoy.when(labware_view.get(labware_id="labware-id")).then_return( + decoy.when(mock_labware_view.get(labware_id="labware-id")).then_return( LoadedLabware( id="labware-id", loadName="above-name", @@ -1631,7 +1782,7 @@ def test_get_labware_grip_point_on_labware( location=OnLabwareLocation(labwareId="below-id"), ) ) - decoy.when(labware_view.get(labware_id="below-id")).then_return( + decoy.when(mock_labware_view.get(labware_id="below-id")).then_return( LoadedLabware( id="below-id", loadName="below-name", @@ -1640,18 +1791,18 @@ def test_get_labware_grip_point_on_labware( ) ) - decoy.when(labware_view.get_dimensions("below-id")).then_return( + decoy.when(mock_labware_view.get_dimensions("below-id")).then_return( Dimensions(x=1000, y=1001, z=11) ) decoy.when( - labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom("labware-id") ).then_return(100) decoy.when( - labware_view.get_labware_overlap_offsets("labware-id", "below-name") + mock_labware_view.get_labware_overlap_offsets("labware-id", "below-name") ).then_return(OverlapOffset(x=0, y=1, z=6)) decoy.when( - addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_4.id) ).then_return(Point(x=5, y=9, z=10)) grip_point = subject.get_labware_grip_point( @@ -1663,42 +1814,44 @@ def test_get_labware_grip_point_on_labware( def test_get_labware_grip_point_for_labware_on_module( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: """It should return the grip point for labware directly on a module.""" decoy.when( - labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom("labware-id") ).then_return(500) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_4) ) - decoy.when(labware_view.get_deck_definition()).then_return(ot2_standard_deck_def) + decoy.when(mock_labware_view.get_deck_definition()).then_return( + ot2_standard_deck_def + ) decoy.when( - module_view.get_nominal_module_offset( + mock_module_view.get_nominal_module_offset( module_id="module-id", - addressable_areas=addressable_area_view, + addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=1, y=2, z=3)) - decoy.when(module_view.get_connected_model("module-id")).then_return( + decoy.when(mock_module_view.get_connected_model("module-id")).then_return( ModuleModel.MAGNETIC_MODULE_V2 ) decoy.when( - labware_view.get_module_overlap_offsets( + mock_labware_view.get_module_overlap_offsets( "labware-id", ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=10, y=20, z=30)) - decoy.when(module_view.get_module_calibration_offset("module-id")).then_return( + decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( ModuleOffsetData( moduleOffsetVector=ModuleOffsetVector(x=100, y=200, z=300), location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), ) ) decoy.when( - addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_4.id) + mock_addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_4.id) ).then_return(Point(100, 200, 300)) result_grip_point = subject.get_labware_grip_point( labware_id="labware-id", location=ModuleLocation(moduleId="module-id") @@ -1720,16 +1873,16 @@ def test_get_labware_grip_point_for_labware_on_module( ) def test_get_extra_waypoints( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, + mock_addressable_area_view: AddressableAreaView, location: Optional[CurrentPipetteLocation], should_dodge: bool, expected_waypoints: List[Tuple[float, float]], subject: GeometryView, ) -> None: """It should return extra waypoints if thermocycler should be dodged.""" - decoy.when(labware_view.get("from-labware-id")).then_return( + decoy.when(mock_labware_view.get("from-labware-id")).then_return( LoadedLabware( id="labware1", loadName="load-name1", @@ -1739,17 +1892,17 @@ def test_get_extra_waypoints( ) decoy.when( - addressable_area_view.get_addressable_area_base_slot("area-name") + mock_addressable_area_view.get_addressable_area_base_slot("area-name") ).then_return(DeckSlotName.SLOT_1) decoy.when( - module_view.should_dodge_thermocycler( + mock_module_view.should_dodge_thermocycler( from_slot=DeckSlotName.SLOT_1, to_slot=DeckSlotName.SLOT_2 ) ).then_return(should_dodge) decoy.when( # Assume the subject's Config is for an OT-3, so use an OT-3 slot name. - addressable_area_view.get_addressable_area_center( + mock_addressable_area_view.get_addressable_area_center( addressable_area_name=DeckSlotName.SLOT_C2.id ) ).then_return(Point(x=11, y=22, z=33)) @@ -1761,21 +1914,21 @@ def test_get_extra_waypoints( def test_get_slot_item( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """It should get items in certain slots.""" labware = LoadedLabware.construct(id="cool-labware") # type: ignore[call-arg] module = LoadedModule.construct(id="cool-module") # type: ignore[call-arg] - decoy.when(labware_view.get_by_slot(DeckSlotName.SLOT_1)).then_return(None) - decoy.when(labware_view.get_by_slot(DeckSlotName.SLOT_2)).then_return(labware) - decoy.when(labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(None) + decoy.when(mock_labware_view.get_by_slot(DeckSlotName.SLOT_1)).then_return(None) + decoy.when(mock_labware_view.get_by_slot(DeckSlotName.SLOT_2)).then_return(labware) + decoy.when(mock_labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(None) - decoy.when(module_view.get_by_slot(DeckSlotName.SLOT_1)).then_return(None) - decoy.when(module_view.get_by_slot(DeckSlotName.SLOT_2)).then_return(None) - decoy.when(module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(module) + decoy.when(mock_module_view.get_by_slot(DeckSlotName.SLOT_1)).then_return(None) + decoy.when(mock_module_view.get_by_slot(DeckSlotName.SLOT_2)).then_return(None) + decoy.when(mock_module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(module) assert ( subject.get_slot_item( @@ -1789,16 +1942,16 @@ def test_get_slot_item( def test_get_slot_item_that_is_overflowed_module( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """It should return the module that occupies the slot, even if not loaded on it.""" module = LoadedModule.construct(id="cool-module") # type: ignore[call-arg] - decoy.when(labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(None) - decoy.when(module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(None) + decoy.when(mock_labware_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(None) + decoy.when(mock_module_view.get_by_slot(DeckSlotName.SLOT_3)).then_return(None) decoy.when( - module_view.get_overflowed_module_in_slot(DeckSlotName.SLOT_3) + mock_module_view.get_overflowed_module_in_slot(DeckSlotName.SLOT_3) ).then_return(module) assert subject.get_slot_item(DeckSlotName.SLOT_3) == module @@ -1883,7 +2036,7 @@ class DropTipLocationFinderSpec(NamedTuple): ) def test_get_next_drop_tip_location( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, mock_pipette_view: PipetteView, subject: GeometryView, labware_slot: DeckSlotName, @@ -1894,9 +2047,9 @@ def test_get_next_drop_tip_location( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, ) -> None: """It should provide the next location to drop tips into within a labware.""" - decoy.when(labware_view.is_fixed_trash(labware_id="abc")).then_return(True) + decoy.when(mock_labware_view.is_fixed_trash(labware_id="abc")).then_return(True) decoy.when( - labware_view.get_well_size(labware_id="abc", well_name="A1") + mock_labware_view.get_well_size(labware_id="abc", well_name="A1") ).then_return((well_size, 0, 0)) if pipette_channels == 96: pip_type = PipetteNameType.P1000_96 @@ -1927,7 +2080,7 @@ def test_get_next_drop_tip_location( ) ) decoy.when(mock_pipette_view.get_mount("pip-123")).then_return(pipette_mount) - decoy.when(labware_view.get("abc")).then_return( + decoy.when(mock_labware_view.get("abc")).then_return( LoadedLabware( id="abc", loadName="load-name2", @@ -1949,12 +2102,12 @@ def test_get_next_drop_tip_location( def test_get_next_drop_tip_location_in_non_trash_labware( decoy: Decoy, - labware_view: LabwareView, + mock_labware_view: LabwareView, mock_pipette_view: PipetteView, subject: GeometryView, ) -> None: """It should provide the default drop tip location when dropping into a non-fixed-trash labware.""" - decoy.when(labware_view.is_fixed_trash(labware_id="abc")).then_return(False) + decoy.when(mock_labware_view.is_fixed_trash(labware_id="abc")).then_return(False) assert subject.get_next_tip_drop_location( labware_id="abc", well_name="A1", pipette_id="pip-123" ) == DropTipWellLocation( @@ -1965,18 +2118,18 @@ def test_get_next_drop_tip_location_in_non_trash_labware( def test_get_final_labware_movement_offset_vectors( decoy: Decoy, - module_view: ModuleView, - labware_view: LabwareView, + mock_module_view: ModuleView, + mock_labware_view: LabwareView, subject: GeometryView, ) -> None: """It should provide the final labware movement offset data based on locations.""" - decoy.when(labware_view.get_deck_default_gripper_offsets()).then_return( + decoy.when(mock_labware_view.get_deck_default_gripper_offsets()).then_return( LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3), dropOffset=LabwareOffsetVector(x=3, y=2, z=1), ) ) - decoy.when(module_view.get_default_gripper_offsets("module-id")).then_return( + decoy.when(mock_module_view.get_default_gripper_offsets("module-id")).then_return( LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=11, y=22, z=33), dropOffset=LabwareOffsetVector(x=33, y=22, z=11), @@ -2017,19 +2170,19 @@ def test_ensure_valid_gripper_location(subject: GeometryView) -> None: def test_get_total_nominal_gripper_offset( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """It should calculate the correct gripper offsets given the location and move type..""" - decoy.when(labware_view.get_deck_default_gripper_offsets()).then_return( + decoy.when(mock_labware_view.get_deck_default_gripper_offsets()).then_return( LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3), dropOffset=LabwareOffsetVector(x=3, y=2, z=1), ) ) - decoy.when(module_view.get_default_gripper_offsets("module-id")).then_return( + decoy.when(mock_module_view.get_default_gripper_offsets("module-id")).then_return( LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=11, y=22, z=33), dropOffset=LabwareOffsetVector(x=33, y=22, z=11), @@ -2053,23 +2206,23 @@ def test_get_total_nominal_gripper_offset( def test_get_stacked_labware_total_nominal_offset_slot_specific( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """Get nominal offset for stacked labware.""" # Case: labware on adapter on module, adapter has slot-specific offsets - decoy.when(module_view.get_default_gripper_offsets("module-id")).then_return( + decoy.when(mock_module_view.get_default_gripper_offsets("module-id")).then_return( LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=11, y=22, z=33), dropOffset=LabwareOffsetVector(x=33, y=22, z=11), ) ) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_C1) ) decoy.when( - labware_view.get_labware_gripper_offsets( + mock_labware_view.get_labware_gripper_offsets( labware_id="adapter-id", slot_name=DeckSlotName.SLOT_C1 ) ).then_return( @@ -2078,7 +2231,7 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( dropOffset=LabwareOffsetVector(x=300, y=200, z=100), ) ) - decoy.when(labware_view.get_parent_location("adapter-id")).then_return( + decoy.when(mock_labware_view.get_parent_location("adapter-id")).then_return( ModuleLocation(moduleId="module-id") ) result1 = subject.get_total_nominal_gripper_offset_for_move_type( @@ -2096,28 +2249,28 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( def test_get_stacked_labware_total_nominal_offset_default( decoy: Decoy, - labware_view: LabwareView, - module_view: ModuleView, + mock_labware_view: LabwareView, + mock_module_view: ModuleView, subject: GeometryView, ) -> None: """Get nominal offset for stacked labware.""" # Case: labware on adapter on module, adapter has only default offsets - decoy.when(module_view.get_default_gripper_offsets("module-id")).then_return( + decoy.when(mock_module_view.get_default_gripper_offsets("module-id")).then_return( LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=11, y=22, z=33), dropOffset=LabwareOffsetVector(x=33, y=22, z=11), ) ) - decoy.when(module_view.get_location("module-id")).then_return( + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_4) ) decoy.when( - labware_view.get_labware_gripper_offsets( + mock_labware_view.get_labware_gripper_offsets( labware_id="adapter-id", slot_name=DeckSlotName.SLOT_C1 ) ).then_return(None) decoy.when( - labware_view.get_labware_gripper_offsets( + mock_labware_view.get_labware_gripper_offsets( labware_id="adapter-id", slot_name=None ) ).then_return( @@ -2126,7 +2279,7 @@ def test_get_stacked_labware_total_nominal_offset_default( dropOffset=LabwareOffsetVector(x=300, y=200, z=100), ) ) - decoy.when(labware_view.get_parent_location("adapter-id")).then_return( + decoy.when(mock_labware_view.get_parent_location("adapter-id")).then_return( ModuleLocation(moduleId="module-id") ) result1 = subject.get_total_nominal_gripper_offset_for_move_type( @@ -2145,8 +2298,8 @@ def test_get_stacked_labware_total_nominal_offset_default( def test_check_gripper_labware_tip_collision( decoy: Decoy, mock_pipette_view: PipetteView, - labware_view: LabwareView, - addressable_area_view: AddressableAreaView, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, subject: GeometryView, ) -> None: """It should raise a labware movement error if attached tips will collide with the labware during a gripper lift.""" @@ -2192,34 +2345,34 @@ def test_check_gripper_labware_tip_collision( offsetId=None, ) - decoy.when(labware_view.get_definition("labware-id")).then_return(definition) - decoy.when(labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return(definition) + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) decoy.when( - addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_1.id) + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_1.id) ).then_return(Point(1, 2, 3)) calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) - decoy.when(labware_view.get_labware_offset_vector("labware-id")).then_return( + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( calibration_offset ) decoy.when(subject.get_labware_origin_position("labware-id")).then_return( Point(1, 2, 3) ) - decoy.when(labware_view.get_definition("labware-id")).then_return(definition) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return(definition) decoy.when(subject._get_highest_z_from_labware_data(labware_data)).then_return(1000) - decoy.when(labware_view.get_definition("labware-id")).then_return(definition) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return(definition) decoy.when(subject.get_labware_highest_z("labware-id")).then_return(100.0) decoy.when( - addressable_area_view.get_addressable_area_center( + mock_addressable_area_view.get_addressable_area_center( addressable_area_name=DeckSlotName.SLOT_1.id ) ).then_return(Point(x=11, y=22, z=33)) decoy.when( - labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom("labware-id") ).then_return(1.0) - decoy.when(labware_view.get_definition("labware-id")).then_return(definition) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return(definition) decoy.when( subject.get_labware_grip_point( labware_id="labware-id", @@ -2233,3 +2386,216 @@ def test_check_gripper_labware_tip_collision( labware_id="labware-id", current_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), ) + + +# Note: Below here, all tests should be done using actual state objects rather than mocks of dependent views. +# I (sf) think this is a better way to do things, but let's try and do it as we add more stuff and see if I'm +# right! + + +@pytest.mark.parametrize("use_mocks", [False]) +def test_get_offset_location_deck_slot( + decoy: Decoy, + labware_store: LabwareStore, + nice_labware_definition: LabwareDefinition, + subject: GeometryView, +) -> None: + """Test if you can get the offset location of a labware in a deck slot.""" + action = SucceedCommandAction( + command=LoadLabware( + id="load-labware-1", + createdAt=datetime.now(), + key="load-labware-1", + status=CommandStatus.SUCCEEDED, + result=LoadLabwareResult( + labwareId="labware-id-1", + definition=nice_labware_definition, + offsetId=None, + ), + params=LoadLabwareParams( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_C2), + loadName=nice_labware_definition.parameters.loadName, + namespace=nice_labware_definition.namespace, + version=nice_labware_definition.version, + ), + ), + private_result=None, + ) + labware_store.handle_action(action) + offset_location = subject.get_offset_location("labware-id-1") + assert offset_location is not None + assert offset_location.slotName == DeckSlotName.SLOT_C2 + assert offset_location.definitionUri is None + assert offset_location.moduleModel is None + + +@pytest.mark.parametrize("use_mocks", [False]) +def test_get_offset_location_module( + decoy: Decoy, + labware_store: LabwareStore, + module_store: ModuleStore, + nice_labware_definition: LabwareDefinition, + tempdeck_v2_def: ModuleDefinition, + subject: GeometryView, +) -> None: + """Test if you can get the offset of a labware directly on a module.""" + load_module = SucceedCommandAction( + command=LoadModule( + params=LoadModuleParams( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A3), + model=ModuleModel.TEMPERATURE_MODULE_V1, + ), + id="load-module-1", + createdAt=datetime.now(), + key="load-module-1", + status=CommandStatus.SUCCEEDED, + result=LoadModuleResult( + moduleId="module-id-1", + definition=tempdeck_v2_def, + model=tempdeck_v2_def.model, + ), + ), + private_result=None, + ) + load_labware = SucceedCommandAction( + command=LoadLabware( + id="load-labware-1", + createdAt=datetime.now(), + key="load-labware-1", + status=CommandStatus.SUCCEEDED, + result=LoadLabwareResult( + labwareId="labware-id-1", + definition=nice_labware_definition, + offsetId=None, + ), + params=LoadLabwareParams( + location=ModuleLocation(moduleId="module-id-1"), + loadName=nice_labware_definition.parameters.loadName, + namespace=nice_labware_definition.namespace, + version=nice_labware_definition.version, + ), + ), + private_result=None, + ) + module_store.handle_action(load_module) + labware_store.handle_action(load_labware) + offset_location = subject.get_offset_location("labware-id-1") + assert offset_location is not None + assert offset_location.slotName == DeckSlotName.SLOT_A3 + assert offset_location.definitionUri is None + assert offset_location.moduleModel == ModuleModel.TEMPERATURE_MODULE_V1 + + +@pytest.mark.parametrize("use_mocks", [False]) +def test_get_offset_location_module_with_adapter( + decoy: Decoy, + labware_store: LabwareStore, + module_store: ModuleStore, + nice_labware_definition: LabwareDefinition, + nice_adapter_definition: LabwareDefinition, + tempdeck_v2_def: ModuleDefinition, + labware_view: LabwareView, + subject: GeometryView, +) -> None: + """Test if you can get the offset of a labware directly on a module.""" + load_module = SucceedCommandAction( + command=LoadModule( + params=LoadModuleParams( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A2), + model=ModuleModel.TEMPERATURE_MODULE_V1, + ), + id="load-module-1", + createdAt=datetime.now(), + key="load-module-1", + status=CommandStatus.SUCCEEDED, + result=LoadModuleResult( + moduleId="module-id-1", + definition=tempdeck_v2_def, + model=tempdeck_v2_def.model, + ), + ), + private_result=None, + ) + load_adapter = SucceedCommandAction( + command=LoadLabware( + id="load-adapter-1", + createdAt=datetime.now(), + key="load-adapter-1", + status=CommandStatus.SUCCEEDED, + result=LoadLabwareResult( + labwareId="adapter-id-1", + definition=nice_adapter_definition, + offsetId=None, + ), + params=LoadLabwareParams( + location=ModuleLocation(moduleId="module-id-1"), + loadName=nice_adapter_definition.parameters.loadName, + namespace=nice_adapter_definition.namespace, + version=nice_adapter_definition.version, + ), + ), + private_result=None, + ) + load_labware = SucceedCommandAction( + command=LoadLabware( + id="load-labware-1", + createdAt=datetime.now(), + key="load-labware-1", + status=CommandStatus.SUCCEEDED, + result=LoadLabwareResult( + labwareId="labware-id-1", + definition=nice_labware_definition, + offsetId=None, + ), + params=LoadLabwareParams( + location=OnLabwareLocation(labwareId="adapter-id-1"), + loadName=nice_labware_definition.parameters.loadName, + namespace=nice_labware_definition.namespace, + version=nice_labware_definition.version, + ), + ), + private_result=None, + ) + module_store.handle_action(load_module) + labware_store.handle_action(load_adapter) + labware_store.handle_action(load_labware) + offset_location = subject.get_offset_location("labware-id-1") + assert offset_location is not None + assert offset_location.slotName == DeckSlotName.SLOT_A2 + assert offset_location.definitionUri == labware_view.get_uri_from_definition( + nice_adapter_definition + ) + assert offset_location.moduleModel == ModuleModel.TEMPERATURE_MODULE_V1 + + +@pytest.mark.parametrize("use_mocks", [False]) +def test_get_offset_fails_with_off_deck_labware( + decoy: Decoy, + labware_store: LabwareStore, + nice_labware_definition: LabwareDefinition, + subject: GeometryView, +) -> None: + """You cannot get the offset location for a labware loaded OFF_DECK.""" + action = SucceedCommandAction( + command=LoadLabware( + id="load-labware-1", + createdAt=datetime.now(), + key="load-labware-1", + status=CommandStatus.SUCCEEDED, + result=LoadLabwareResult( + labwareId="labware-id-1", + definition=nice_labware_definition, + offsetId=None, + ), + params=LoadLabwareParams( + location=OFF_DECK_LOCATION, + loadName=nice_labware_definition.parameters.loadName, + namespace=nice_labware_definition.namespace, + version=nice_labware_definition.version, + ), + ), + private_result=None, + ) + labware_store.handle_action(action) + offset_location = subject.get_offset_location("labware-id-1") + assert offset_location is None