From 0d7ee6a2c6402f8bb68c62c9a055166fb5bfafc8 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Mon, 30 Jan 2023 13:36:42 -0500 Subject: [PATCH 01/86] raising error when using args that are deprecated and added to versioning.rst --- api/docs/v2/versioning.rst | 2 ++ .../protocol_api/instrument_context.py | 22 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index 7d674695ba9..c109808cb7b 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -269,6 +269,8 @@ Upcoming, not yet released. - :py:class:`.Labware` and :py:class:`.Well` objects will adhere to the protocol's API level setting. Prior to this version, they incorrectly ignore the setting. - :py:meth:`.ModuleContext.load_labware_object` will be deprecated. - :py:meth:`.MagneticModuleContext.calibrate` will be deprecated. +- :py:meth:`.InstrumentContext.pick_up_tip.presses` will be deprecated. +- :py:meth:`.InstrumentContext.pick_up_tip.increment` will be deprecated. - Several internal properties of :py:class:`.Labware`, :py:class:`.Well`, and :py:class:`.ModuleContext` will be deprecated and/or removed: - ``Labware.separate_calibration`` and ``ModuleContext.separate_calibration``, which are holdovers from a calibration system that no longer exists. - The ``Well.has_tip`` setter, which will cease to function in a future upgrade to the Python protocol execution system. The corresponding `Well.has_tip` getter will not be deprecated. diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 4619495c4c4..b603ebd9036 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -42,6 +42,8 @@ _PREP_AFTER_ADDED_IN = APIVersion(2, 13) """The version after which the pick-up tip procedure should also prepare the plunger.""" +_PRESSES_INCREMENT_DEPRECATE_FROM = APIVersion(2, 14) +"""The version after which the pick-up tip procedure should also prepare the plunger.""" class InstrumentContext(publisher.CommandPublisher): @@ -648,7 +650,7 @@ def return_tip(self, home_after: bool = True) -> InstrumentContext: return self - @requires_version(2, 0) + @requires_version(2, 0) # noqa: C901 def pick_up_tip( self, location: Union[types.Location, labware.Well, labware.Labware, None] = None, @@ -691,11 +693,13 @@ def pick_up_tip( will result in the pipette hovering over the tip but not picking it up--generally not desirable, but could be used for dry-run). + .. deprecated:: 2.14 :type presses: int :param increment: The additional distance to travel on each successive press (e.g.: if `presses=3` and `increment=1.0`, then the first press will travel down into the tip by 3.5mm, the second by 4.5mm, and the third by 5.5mm). + .. deprecated:: 2.14 :type increment: float :param prep_after: Whether the pipette plunger should prepare itself to aspirate immediately after picking up a tip. @@ -728,6 +732,22 @@ def pick_up_tip( :returns: This instance """ + + if ( + presses is not None + and self._api_version >= _PRESSES_INCREMENT_DEPRECATE_FROM + ): + raise APIVersionError( + f"presses is only available in API versions lower than {_PRESSES_INCREMENT_DEPRECATE_FROM}," + f" but you are using API {self._api_version}." + ) + + if increment is not None and self._api_version >= _PRESSES_INCREMENT_DEPRECATE_FROM: + raise APIVersionError( + f"increment is only available in API versions lower than {_PRESSES_INCREMENT_DEPRECATE_FROM}," + f" but you are using API {self._api_version}." + ) + if prep_after is not None and self._api_version < _PREP_AFTER_ADDED_IN: raise APIVersionError( f"prep_after is only available in API {_PREP_AFTER_ADDED_IN} and newer," From e4d70f43962cec53a14ec11b174ed2683e503078 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Mon, 30 Jan 2023 13:47:00 -0500 Subject: [PATCH 02/86] Update api/src/opentrons/protocol_api/instrument_context.py --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index b603ebd9036..2b592ebbcbf 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -43,7 +43,7 @@ _PREP_AFTER_ADDED_IN = APIVersion(2, 13) """The version after which the pick-up tip procedure should also prepare the plunger.""" _PRESSES_INCREMENT_DEPRECATE_FROM = APIVersion(2, 14) -"""The version after which the pick-up tip procedure should also prepare the plunger.""" +"""The version after which the pick-up tip procedure deprecates presses and increment arguments.""" class InstrumentContext(publisher.CommandPublisher): From 4c4fb2f114a18e2fc555355cf196f178a1d5d166 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Mon, 30 Jan 2023 14:55:22 -0500 Subject: [PATCH 03/86] Update api/docs/v2/versioning.rst Co-authored-by: Mike Cousins --- api/docs/v2/versioning.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index c109808cb7b..db29c1794a6 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -269,8 +269,7 @@ Upcoming, not yet released. - :py:class:`.Labware` and :py:class:`.Well` objects will adhere to the protocol's API level setting. Prior to this version, they incorrectly ignore the setting. - :py:meth:`.ModuleContext.load_labware_object` will be deprecated. - :py:meth:`.MagneticModuleContext.calibrate` will be deprecated. -- :py:meth:`.InstrumentContext.pick_up_tip.presses` will be deprecated. -- :py:meth:`.InstrumentContext.pick_up_tip.increment` will be deprecated. +- The ``presses`` and ``increment`` arguments of :py:meth:`.InstrumentContext.pick_up_tip` will be deprecated. Configure your pipettes' pick-up settings with the Opentrons App, instead. - Several internal properties of :py:class:`.Labware`, :py:class:`.Well`, and :py:class:`.ModuleContext` will be deprecated and/or removed: - ``Labware.separate_calibration`` and ``ModuleContext.separate_calibration``, which are holdovers from a calibration system that no longer exists. - The ``Well.has_tip`` setter, which will cease to function in a future upgrade to the Python protocol execution system. The corresponding `Well.has_tip` getter will not be deprecated. From 6a2b20b8b2fcbcd8355fa82050bc85ddfb7fc3b1 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Mon, 30 Jan 2023 14:56:39 -0500 Subject: [PATCH 04/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 2b592ebbcbf..a20da863e58 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -693,7 +693,9 @@ def pick_up_tip( will result in the pipette hovering over the tip but not picking it up--generally not desirable, but could be used for dry-run). + .. deprecated:: 2.14 + Use the Opentrons App to change pipette pick-up settings. :type presses: int :param increment: The additional distance to travel on each successive press (e.g.: if `presses=3` and `increment=1.0`, then From 4bc29d251b12bd074b89792962025d35022f09b0 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Mon, 30 Jan 2023 20:56:24 -0500 Subject: [PATCH 05/86] fixed docstrings and formatting --- api/src/opentrons/protocol_api/instrument_context.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a20da863e58..485d853a285 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -701,7 +701,9 @@ def pick_up_tip( press (e.g.: if `presses=3` and `increment=1.0`, then the first press will travel down into the tip by 3.5mm, the second by 4.5mm, and the third by 5.5mm). - .. deprecated:: 2.14 + + .. deprecated:: 2.14 + Use the Opentrons App to change pipette pick-up settings. :type increment: float :param prep_after: Whether the pipette plunger should prepare itself to aspirate immediately after picking up a tip. @@ -744,7 +746,10 @@ def pick_up_tip( f" but you are using API {self._api_version}." ) - if increment is not None and self._api_version >= _PRESSES_INCREMENT_DEPRECATE_FROM: + if ( + increment is not None + and self._api_version >= _PRESSES_INCREMENT_DEPRECATE_FROM + ): raise APIVersionError( f"increment is only available in API versions lower than {_PRESSES_INCREMENT_DEPRECATE_FROM}," f" but you are using API {self._api_version}." From c5231a283a63e802e087a7d0d7f79ca59ff9e125 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 31 Jan 2023 12:38:16 -0500 Subject: [PATCH 06/86] WIP started PE aspirate_in_place.py --- .../commands/aspirate_in_place.py | 74 +++++++++++++++++++ .../commands/test_aspirate_in_place.py | 36 +++++++++ 2 files changed, 110 insertions(+) create mode 100644 api/src/opentrons/protocol_engine/commands/aspirate_in_place.py create mode 100644 api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py new file mode 100644 index 00000000000..b7dbd65316e --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -0,0 +1,74 @@ +"""Aspirate-in-place command request, result, and implementation models.""" + +# TODO(mm, 2022-08-15): This command is not yet in the JSON protocol schema. +# Before our production code emits this command, we must add it to the schema, +# and probably bump the schema version. + +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Type +from typing_extensions import Literal + +from .pipetting_common import ( + PipetteIdMixin, + VolumeMixin, + FlowRateMixin, + BaseLiquidHandlingResult, +) +from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate + +if TYPE_CHECKING: + from ..execution import PipettingHandler + + +AspirateInPlaceCommandType = Literal["aspirateInPlace"] + + +class AspirateInPlaceParams(PipetteIdMixin, VolumeMixin, FlowRateMixin): + """Payload required to aspirate in place.""" + + pass + + +class AspirateInPlaceResult(BaseLiquidHandlingResult): + """Result data from the execution of a AspirateInPlace command.""" + + pass + + +class AspirateInPlaceImplementation( + AbstractCommandImpl[AspirateInPlaceParams, AspirateInPlaceResult] +): + """AspirateInPlace command implementation.""" + + def __init__(self, pipetting: PipettingHandler, **kwargs: object) -> None: + self._pipetting = pipetting + + async def execute(self, params: AspirateInPlaceParams) -> AspirateInPlaceResult: + """Aspirate without moving the pipette.""" + volume = await self._pipetting.aspirate_in_place( + pipette_id=params.pipetteId, + volume=params.volume, + flow_rate=params.flowRate, + ) + return AspirateInPlaceResult(volume=volume) + + +class AspirateInPlace(BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult]): + """AspirateInPlace command model.""" + + commandType: AspirateInPlaceCommandType = "aspirateInPlace" + params: AspirateInPlaceParams + result: Optional[AspirateInPlaceResult] + + _ImplementationCls: Type[ + AspirateInPlaceImplementation + ] = AspirateInPlaceImplementation + + +class AspirateInPlaceCreate(BaseCommandCreate[AspirateInPlaceParams]): + """AspirateInPlace command request model.""" + + commandType: AspirateInPlaceCommandType = "aspirateInPlace" + params: AspirateInPlaceParams + + _CommandCls: Type[AspirateInPlace] = AspirateInPlace diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py new file mode 100644 index 00000000000..45c48976d06 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py @@ -0,0 +1,36 @@ +"""Test aspirate-in-place commands.""" +from decoy import Decoy + +from opentrons.protocol_engine.execution import PipettingHandler + +from opentrons.protocol_engine.commands.aspirate_in_place import ( + AspirateInPlaceParams, + AspirateInPlaceResult, + AspirateInPlaceImplementation, +) + + +async def test_aspirate_in_place_implementation( + decoy: Decoy, + pipetting: PipettingHandler, +) -> None: + """It should aspirate in place.""" + subject = AspirateInPlaceImplementation(pipetting=pipetting) + + data = AspirateInPlaceParams( + pipetteId="pipette-id-abc", + volume=123, + flowRate=456, + ) + + decoy.when( + await pipetting.dispense_in_place( + pipette_id="pipette-id-abc", + volume=123, + flow_rate=456, + ) + ).then_return(42) + + result = await subject.execute(data) + + assert result == AspirateInPlaceResult(volume=42) From 964a5a9a6bed60bbf84dc7e9d0f9c85423d578f4 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 1 Feb 2023 12:57:38 -0500 Subject: [PATCH 07/86] added to pipetting aspirate_in_place --- .../protocol_engine/execution/pipetting.py | 17 ++++++++ .../execution/test_pipetting_handler.py | 40 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index e629dda6f32..623a4a91c02 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -243,6 +243,23 @@ async def dispense_in_place( return volume + async def aspirate_in_place( + self, + pipette_id: str, + volume: float, + flow_rate: float, + ) -> float: + """Aspirate liquid without moving the pipette.""" + hw_pipette = self._state_store.pipettes.get_hardware_pipette( + pipette_id=pipette_id, + attached_pipettes=self._hardware_api.attached_instruments, + ) + + with self.set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate): + await self._hardware_api.aspirate(mount=hw_pipette.mount, volume=volume) + + return volume + async def touch_tip( self, pipette_id: str, diff --git a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py index b00248b837e..c45cf4786fc 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py @@ -446,6 +446,46 @@ async def test_handle_dispense_in_place_request( ) +async def test_handle_aspirate_in_place_request( + decoy: Decoy, + state_store: StateStore, + hardware_api: HardwareAPI, + movement_handler: MovementHandler, + mock_hw_pipettes: MockPipettes, + subject: PipettingHandler, +) -> None: + """It should find the pipette by ID and use it to aspirate.""" + decoy.when( + state_store.pipettes.get_hardware_pipette( + pipette_id="pipette-id", + attached_pipettes=mock_hw_pipettes.by_mount, + ) + ).then_return( + HardwarePipette( + mount=Mount.RIGHT, + config=mock_hw_pipettes.right_config, + ) + ) + + volume = await subject.aspirate_in_place( + pipette_id="pipette-id", + volume=25, + flow_rate=2.5, + ) + + assert volume == 25 + + decoy.verify( + hardware_api.set_flow_rate( + mount=Mount.RIGHT, aspirate=None, dispense=2.5, blow_out=None + ), + await hardware_api.aspirate(mount=Mount.RIGHT, volume=25), + hardware_api.set_flow_rate( + mount=Mount.RIGHT, aspirate=1.23, dispense=1.23, blow_out=1.23 + ), + ) + + async def test_handle_add_tip( decoy: Decoy, state_store: StateStore, From e963f9ec9b733b7f972f7137d301783f692e1686 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 1 Feb 2023 14:42:49 -0500 Subject: [PATCH 08/86] blow out in place pe command --- .../protocol_engine/commands/__init__.py | 28 ++++++ .../commands/blow_out_in_place.py | 90 +++++++++++++++++++ .../commands/command_unions.py | 26 ++++++ .../commands/test_blow_out_in_place.py | 71 +++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 api/src/opentrons/protocol_engine/commands/blow_out_in_place.py create mode 100644 api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index 0c463582f33..006218c45dd 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -46,6 +46,14 @@ AspirateCommandType, ) +from .aspirate_in_place import ( + AspirateInPlace, + AspirateInPlaceParams, + AspirateInPlaceCreate, + AspirateInPlaceResult, + AspirateInPlaceCommandType, +) + from .comment import ( Comment, CommentParams, @@ -215,6 +223,14 @@ BlowOut, ) +from .blow_out_in_place import ( + BlowOutInPlaceParams, + BlowOutInPlaceResult, + BlowOutInPlaceCreate, + BlowOutInPlaceImplementation, + BlowOutInPlace, +) + __all__ = [ # command type unions "Command", @@ -238,6 +254,12 @@ "AspirateParams", "AspirateResult", "AspirateCommandType", + # aspirate in place command models + "AspirateInPlace", + "AspirateInPlaceCreate", + "AspirateInPlaceParams", + "AspirateInPlaceResult", + "AspirateInPlaceCommandType", # comment command models "Comment", "CommentParams", @@ -358,6 +380,12 @@ "BlowOutImplementation", "BlowOutParams", "BlowOut", + # blow out in place command models + "BlowOutInPlaceParams", + "BlowOutInPlaceResult", + "BlowOutInPlaceCreate", + "BlowOutInPlaceImplementation", + "BlowOutInPlace", # load liquid command models "LoadLiquid", "LoadLiquidCreate", diff --git a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py new file mode 100644 index 00000000000..572bd9379a2 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py @@ -0,0 +1,90 @@ +"""Blow-out-in-place command request, result, and implementation models.""" + +# TODO(mm, 2022-08-15): This command is not yet in the JSON protocol schema. +# Before our production code emits this command, we must add it to the schema, +# and probably bump the schema version. + +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Type +from typing_extensions import Literal +from pydantic import BaseModel + +from .pipetting_common import ( + PipetteIdMixin, + FlowRateMixin, +) +from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate + +from opentrons.hardware_control import HardwareControlAPI + + +if TYPE_CHECKING: + from ..execution import PipettingHandler + from ..state import StateView + + +BlowOutInPlaceCommandType = Literal["blowOutInPlace"] + + +class BlowOutInPlaceParams(PipetteIdMixin, FlowRateMixin): + """Payload required to blow-out in place.""" + + pass + + +class BlowOutInPlaceResult(BaseModel): + """Result data from the execution of a BlowOutInPlace command.""" + + pass + + +class BlowOutInPlaceImplementation( + AbstractCommandImpl[BlowOutInPlaceParams, BlowOutInPlaceResult] +): + """BlowOutInPlace command implementation.""" + + def __init__( + self, + pipetting: PipettingHandler, + state_view: StateView, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._pipetting = pipetting + self._state_view = state_view + self._hardware_api = hardware_api + + async def execute(self, params: BlowOutInPlaceParams) -> BlowOutInPlaceResult: + """Blow-out without moving the pipette.""" + hw_pipette = self._state_view.pipettes.get_hardware_pipette( + pipette_id=params.pipetteId, + attached_pipettes=self._hardware_api.attached_instruments, + ) + + with self._pipetting.set_flow_rate( + pipette=hw_pipette, blow_out_flow_rate=params.flowRate + ): + await self._hardware_api.blow_out(mount=hw_pipette.mount) + + return BlowOutInPlaceResult() + + +class BlowOutInPlace(BaseCommand[BlowOutInPlaceParams, BlowOutInPlaceResult]): + """BlowOutInPlace command model.""" + + commandType: BlowOutInPlaceCommandType = "blowOutInPlace" + params: BlowOutInPlaceParams + result: Optional[BlowOutInPlaceResult] + + _ImplementationCls: Type[ + BlowOutInPlaceImplementation + ] = BlowOutInPlaceImplementation + + +class BlowOutInPlaceCreate(BaseCommandCreate[BlowOutInPlaceParams]): + """BlowOutInPlace command request model.""" + + commandType: BlowOutInPlaceCommandType = "blowOutInPlace" + params: BlowOutInPlaceParams + + _CommandCls: Type[BlowOutInPlace] = BlowOutInPlace diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 67cc0b29ce5..6db76a48cf6 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -25,6 +25,14 @@ AspirateCommandType, ) +from .aspirate_in_place import ( + AspirateInPlace, + AspirateInPlaceParams, + AspirateInPlaceCreate, + AspirateInPlaceResult, + AspirateInPlaceCommandType, +) + from .comment import ( Comment, CommentParams, @@ -185,13 +193,23 @@ BlowOutResult, ) +from .blow_out_in_place import ( + BlowOutInPlaceParams, + BlowOutInPlace, + BlowOutInPlaceCreate, + BlowOutInPlaceCommandType, + BlowOutInPlaceResult, +) + Command = Union[ Aspirate, + AspirateInPlace, Comment, Custom, Dispense, DispenseInPlace, BlowOut, + BlowOutInPlace, DropTip, Home, LoadLabware, @@ -236,11 +254,13 @@ CommandParams = Union[ AspirateParams, + AspirateInPlaceParams, CommentParams, CustomParams, DispenseParams, DispenseInPlaceParams, BlowOutParams, + BlowOutInPlaceParams, DropTipParams, HomeParams, LoadLabwareParams, @@ -286,11 +306,13 @@ CommandType = Union[ AspirateCommandType, + AspirateInPlaceCommandType, CommentCommandType, CustomCommandType, DispenseCommandType, DispenseInPlaceCommandType, BlowOutCommandType, + BlowOutInPlaceCommandType, DropTipCommandType, HomeCommandType, LoadLabwareCommandType, @@ -335,11 +357,13 @@ CommandCreate = Union[ AspirateCreate, + AspirateInPlaceCreate, CommentCreate, CustomCreate, DispenseCreate, DispenseInPlaceCreate, BlowOutCreate, + BlowOutInPlaceCreate, DropTipCreate, HomeCreate, LoadLabwareCreate, @@ -384,11 +408,13 @@ CommandResult = Union[ AspirateResult, + AspirateInPlaceResult, CommentResult, CustomResult, DispenseResult, DispenseInPlaceResult, BlowOutResult, + BlowOutInPlaceResult, DropTipResult, HomeResult, LoadLabwareResult, diff --git a/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py new file mode 100644 index 00000000000..685dcf5715b --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py @@ -0,0 +1,71 @@ +"""Test blow-out-in-place commands.""" +from decoy import Decoy +from typing import cast + +from opentrons.protocol_engine.state import StateView, HardwarePipette +from opentrons.protocol_engine.commands.blow_out_in_place import ( + BlowOutInPlaceParams, + BlowOutInPlaceResult, + BlowOutInPlaceImplementation, +) + +from opentrons.protocol_engine.execution import ( + MovementHandler, + PipettingHandler, +) +from opentrons.types import Mount +from opentrons.hardware_control.dev_types import PipetteDict +from opentrons.hardware_control import HardwareControlAPI + + +async def test_blow_out_in_place_implementation( + decoy: Decoy, + state_view: StateView, + hardware_api: HardwareControlAPI, + movement: MovementHandler, + pipetting: PipettingHandler, +) -> None: + """Test BlowOut command execution.""" + subject = BlowOutInPlaceImplementation( + state_view=state_view, + hardware_api=hardware_api, + pipetting=pipetting, + ) + + left_config = cast(PipetteDict, {"name": "p300_single", "pipette_id": "123"}) + right_config = cast(PipetteDict, {"name": "p300_multi", "pipette_id": "abc"}) + + pipette_dict_by_mount = {Mount.LEFT: left_config, Mount.RIGHT: right_config} + + left_pipette = HardwarePipette(mount=Mount.LEFT, config=left_config) + + decoy.when(hardware_api.attached_instruments).then_return(pipette_dict_by_mount) + decoy.when( + state_view.pipettes.get_hardware_pipette( + pipette_id="pipette-id", + attached_pipettes=pipette_dict_by_mount, + ) + ).then_return(HardwarePipette(mount=Mount.LEFT, config=left_config)) + + data = BlowOutInPlaceParams( + pipetteId="pipette-id", + flowRate=1.234, + ) + + mock_flow_rate_context = decoy.mock(name="mock flow rate context") + decoy.when( + pipetting.set_flow_rate( + pipette=HardwarePipette(mount=Mount.LEFT, config=left_config), + blow_out_flow_rate=1.234, + ) + ).then_return(mock_flow_rate_context) + + result = await subject.execute(data) + + assert result == BlowOutInPlaceResult() + + decoy.verify( + mock_flow_rate_context.__enter__(), + await hardware_api.blow_out(mount=left_pipette.mount), + mock_flow_rate_context.__exit__(None, None, None), + ) From a9f68c91db7a29eb76b090f274ec3898532660de Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 1 Feb 2023 15:29:48 -0500 Subject: [PATCH 09/86] added sync client in-place --- .../protocol_engine/clients/sync_client.py | 50 ++++++++++++ .../clients/test_sync_client.py | 81 +++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/api/src/opentrons/protocol_engine/clients/sync_client.py b/api/src/opentrons/protocol_engine/clients/sync_client.py index 7b1ba8bc731..e6dcf59d365 100644 --- a/api/src/opentrons/protocol_engine/clients/sync_client.py +++ b/api/src/opentrons/protocol_engine/clients/sync_client.py @@ -259,6 +259,24 @@ def aspirate( return cast(commands.AspirateResult, result) + def aspirate_in_place( + self, + pipette_id: str, + volume: float, + flow_rate: float, + ) -> commands.AspirateInPlaceResult: + """Execute an ``AspirateInPlace`` command and return the result.""" + request = commands.AspirateInPlaceCreate( + params=commands.AspirateInPlaceParams( + pipetteId=pipette_id, + volume=volume, + flowRate=flow_rate, + ) + ) + result = self._transport.execute_command(request=request) + + return cast(commands.AspirateInPlaceResult, result) + def dispense( self, pipette_id: str, @@ -282,6 +300,23 @@ def dispense( result = self._transport.execute_command(request=request) return cast(commands.DispenseResult, result) + def dispense_in_place( + self, + pipette_id: str, + volume: float, + flow_rate: float, + ) -> commands.DispenseInPlaceResult: + """Execute a ``DispenseInPlace`` command and return the result.""" + request = commands.DispenseInPlaceCreate( + params=commands.DispenseInPlaceParams( + pipetteId=pipette_id, + volume=volume, + flowRate=flow_rate, + ) + ) + result = self._transport.execute_command(request=request) + return cast(commands.DispenseInPlaceResult, result) + def blow_out( self, pipette_id: str, @@ -303,6 +338,21 @@ def blow_out( result = self._transport.execute_command(request=request) return cast(commands.BlowOutResult, result) + def blow_out_in_place( + self, + pipette_id: str, + flow_rate: float, + ) -> commands.BlowOutInPlaceResult: + """Execute a ``BlowOutInPlace`` command and return the result.""" + request = commands.BlowOutInPlaceCreate( + params=commands.BlowOutInPlaceParams( + pipetteId=pipette_id, + flowRate=flow_rate, + ) + ) + result = self._transport.execute_command(request=request) + return cast(commands.BlowOutInPlaceResult, result) + def touch_tip( self, pipette_id: str, diff --git a/api/tests/opentrons/protocol_engine/clients/test_sync_client.py b/api/tests/opentrons/protocol_engine/clients/test_sync_client.py index 77870c15019..5d7df623334 100644 --- a/api/tests/opentrons/protocol_engine/clients/test_sync_client.py +++ b/api/tests/opentrons/protocol_engine/clients/test_sync_client.py @@ -350,6 +350,35 @@ def test_aspirate( assert result == result_from_transport +def test_aspirate_in_place( + decoy: Decoy, + transport: AbstractSyncTransport, + subject: SyncClient, +) -> None: + """It should send an AspirateInPlaceCommand through the transport.""" + request = commands.AspirateInPlaceCreate( + params=commands.AspirateInPlaceParams( + pipetteId="123", + volume=123.45, + flowRate=6.7, + ) + ) + + result_from_transport = commands.AspirateInPlaceResult(volume=67.89) + + decoy.when(transport.execute_command(request=request)).then_return( + result_from_transport + ) + + result = subject.aspirate_in_place( + pipette_id="123", + volume=123.45, + flow_rate=6.7, + ) + + assert result == result_from_transport + + def test_dispense( decoy: Decoy, transport: AbstractSyncTransport, @@ -388,6 +417,33 @@ def test_dispense( assert result == response +def test_dispense_in_place( + decoy: Decoy, + transport: AbstractSyncTransport, + subject: SyncClient, +) -> None: + """It should execute a DispenceInPlace command.""" + request = commands.DispenseInPlaceCreate( + params=commands.DispenseInPlaceParams( + pipetteId="123", + volume=10, + flowRate=2.0, + ) + ) + + response = commands.DispenseInPlaceResult(volume=1) + + decoy.when(transport.execute_command(request=request)).then_return(response) + + result = subject.dispense_in_place( + pipette_id="123", + volume=10, + flow_rate=2.0, + ) + + assert result == response + + def test_touch_tip( decoy: Decoy, transport: AbstractSyncTransport, @@ -733,6 +789,31 @@ def test_blow_out( assert result == response +def test_blow_out_in_place( + decoy: Decoy, + transport: AbstractSyncTransport, + subject: SyncClient, +) -> None: + """It should execute a blow_out command.""" + request = commands.BlowOutInPlaceCreate( + params=commands.BlowOutInPlaceParams( + pipetteId="123", + flowRate=7.8, + ) + ) + + response = commands.BlowOutInPlaceResult() + + decoy.when(transport.execute_command(request=request)).then_return(response) + + result = subject.blow_out_in_place( + pipette_id="123", + flow_rate=7.8, + ) + + assert result == response + + def test_heater_shaker_set_target_temperature( decoy: Decoy, transport: AbstractSyncTransport, From 4fa33414a7f1043497bca1cb029b3727e79f838d Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 1 Feb 2023 20:55:42 -0500 Subject: [PATCH 10/86] engine core asprirate in place --- .../protocol_api/core/engine/instrument.py | 41 +++++++++---------- .../core/engine/test_instrument_core.py | 18 ++++++++ 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index c358ae538ab..8c2331006fa 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -66,7 +66,7 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: Location, + location: Optional[Location], well_core: Optional[WellCore], volume: float, rate: float, @@ -80,30 +80,29 @@ def aspirate( rate: Not used in this core. flow_rate: The flow rate in µL/s to aspirate at. """ - if well_core is None: - raise NotImplementedError( - "InstrumentCore.aspirate with well_core value of None not implemented" - ) + if well_core is None and location is None: + self._engine_client.aspirate_in_place(pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate) - well_name = well_core.get_name() - labware_id = well_core.labware_id + else: + well_name = well_core.get_name() + labware_id = well_core.labware_id - well_location = self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, - well_name=well_name, - absolute_point=location.point, - ) + well_location = self._engine_client.state.geometry.get_relative_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + ) - self._engine_client.aspirate( - pipette_id=self._pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=well_location, - volume=volume, - flow_rate=flow_rate, - ) + self._engine_client.aspirate( + pipette_id=self._pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + volume=volume, + flow_rate=flow_rate, + ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def dispense( self, diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index f1ce82dc3e0..5ead750a588 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -307,6 +307,24 @@ def test_aspirate_from_well( ) +def test_aspirate_in_place( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, +) -> None: + """It should aspirate in place.""" + subject.aspirate(volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=None) + + decoy.verify( + mock_engine_client.aspirate_in_place( + pipette_id="abc123", + volume=12.34, + flow_rate=7.8, + ), + ) + + def test_blow_out_to_well( decoy: Decoy, mock_engine_client: EngineClient, From 622b1f2faa6bdf906645a98c151818fd786a0c77 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 2 Feb 2023 13:00:42 -0500 Subject: [PATCH 11/86] all engine core implementation. removed move_to_well arg --- .../protocol_api/core/engine/instrument.py | 128 ++++++++++++------ .../opentrons/protocol_api/core/instrument.py | 2 - .../core/legacy/legacy_instrument_core.py | 5 +- .../legacy_instrument_core.py | 4 +- .../protocol_api/instrument_context.py | 7 - .../core/engine/test_instrument_core.py | 71 +++++++++- .../protocol_api/test_instrument_context.py | 3 - .../core/simulator/test_instrument_context.py | 2 - 8 files changed, 154 insertions(+), 68 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 8c2331006fa..13792ee2b71 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -66,7 +66,7 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: Optional[Location], + location: Location, well_core: Optional[WellCore], volume: float, rate: float, @@ -80,17 +80,31 @@ def aspirate( rate: Not used in this core. flow_rate: The flow rate in µL/s to aspirate at. """ - if well_core is None and location is None: - self._engine_client.aspirate_in_place(pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate) + if well_core is None: + self._engine_client.move_to_coordinates( + pipette_id=self._pipette_id, + coordinates=DeckPoint( + x=location.point.x, y=location.point.y, z=location.point.z + ), + minimum_z_height=None, + force_direct=False, + speed=None, + ) + + self._engine_client.aspirate_in_place( + pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate + ) else: well_name = well_core.get_name() labware_id = well_core.labware_id - well_location = self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, - well_name=well_name, - absolute_point=location.point, + well_location = ( + self._engine_client.state.geometry.get_relative_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + ) ) self._engine_client.aspirate( @@ -102,7 +116,7 @@ def aspirate( flow_rate=flow_rate, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def dispense( self, @@ -121,59 +135,85 @@ def dispense( flow_rate: The flow rate in µL/s to dispense at. """ if well_core is None: - raise NotImplementedError( - "InstrumentCore.dispense with well_core value of None not implemented" + self._engine_client.move_to_coordinates( + pipette_id=self._pipette_id, + coordinates=DeckPoint( + x=location.point.x, y=location.point.y, z=location.point.z + ), + minimum_z_height=None, + force_direct=False, + speed=None, ) - well_name = well_core.get_name() - labware_id = well_core.labware_id + self._engine_client.dispense_in_place( + pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate + ) + else: + well_name = well_core.get_name() + labware_id = well_core.labware_id - well_location = self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, well_name=well_name, absolute_point=location.point - ) + well_location = ( + self._engine_client.state.geometry.get_relative_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + ) + ) - self._engine_client.dispense( - pipette_id=self._pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=well_location, - volume=volume, - flow_rate=flow_rate, - ) + self._engine_client.dispense( + pipette_id=self._pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + volume=volume, + flow_rate=flow_rate, + ) self._protocol_core.set_last_location(location=location, mount=self.get_mount()) - def blow_out( - self, location: Location, well_core: Optional[WellCore], move_to_well: bool - ) -> None: + def blow_out(self, location: Location, well_core: Optional[WellCore]) -> None: """Blow liquid out of the tip. Args: location: The location to blow out into. well_core: The well to blow out into. - move_to_well: Unused by engine core. """ + flow_rate = self.get_absolute_blow_out_flow_rate(1.0) if well_core is None: - raise NotImplementedError("In-place blow-out is not implemented") + self._engine_client.move_to_coordinates( + pipette_id=self._pipette_id, + coordinates=DeckPoint( + x=location.point.x, y=location.point.y, z=location.point.z + ), + force_direct=False, + minimum_z_height=None, + speed=None, + ) - well_name = well_core.get_name() - labware_id = well_core.labware_id + self._engine_client.blow_out_in_place( + pipette_id=self._pipette_id, flow_rate=flow_rate + ) + else: + well_name = well_core.get_name() + labware_id = well_core.labware_id - well_location = self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, - well_name=well_name, - absolute_point=location.point, - ) + well_location = ( + self._engine_client.state.geometry.get_relative_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + ) + ) - self._engine_client.blow_out( - pipette_id=self._pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=well_location, - # TODO(jbl 2022-11-07) PAPIv2 does not have an argument for rate and - # this also needs to be refactored along with other flow rate related issues - flow_rate=self.get_absolute_blow_out_flow_rate(1.0), - ) + self._engine_client.blow_out( + pipette_id=self._pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + # TODO(jbl 2022-11-07) PAPIv2 does not have an argument for rate and + # this also needs to be refactored along with other flow rate related issues + flow_rate=flow_rate, + ) self._protocol_core.set_last_location(location=location, mount=self.get_mount()) diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 591459efea8..f2c2988bb30 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -64,14 +64,12 @@ def blow_out( self, location: types.Location, well_core: Optional[WellCoreType], - move_to_well: bool, ) -> None: """Blow liquid out of the tip. Args: location: The location to blow out into. well_core: The well to blow out into. - move_to_well: If pipette should be moved before blow-out. """ ... diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index b6db20a4969..f679b13659a 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -122,17 +122,14 @@ def blow_out( self, location: types.Location, well_core: Optional[LegacyWellCore], - move_to_well: bool, ) -> None: """Blow liquid out of the tip. Args: location: The location to blow out into. well_core: Unused by legacy core. - move_to_well: If pipette should be moved before blow-out. """ - if move_to_well: - self.move_to(location=location) + self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) def touch_tip( diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index b926cfd7ab6..6d8fe0ba701 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -117,10 +117,8 @@ def blow_out( self, location: types.Location, well_core: Optional[LegacyWellCore], - move_to_well: bool, ) -> None: - if move_to_well: - self.move_to(location=location, well_core=well_core) + self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.BLOWOUT.name) self._update_volume(0) self._pipette_dict["ready_to_aspirate"] = False diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 485d853a285..2fc8b2b6632 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -435,8 +435,6 @@ def blow_out( """ well: Optional[labware.Well] - # TODO(jbl 2022-11-10) refactor this boolean out and make location optional when PE blow-out in place exists - move_to_well = True last_location = self._protocol_core.get_last_location() if isinstance(location, labware.Well): @@ -457,10 +455,6 @@ def blow_out( elif last_location: checked_loc = last_location _, well = checked_loc.labware.get_parent_labware_and_well() - # if no explicit location given but location cache exists, - # pipette blows out immediately at - # current location, no movement is needed - move_to_well = False else: raise RuntimeError( "If blow out is called without an explicit location, another" @@ -476,7 +470,6 @@ def blow_out( self._core.blow_out( location=checked_loc, well_core=well._core if well is not None else None, - move_to_well=move_to_well, ) return self diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 5ead750a588..4c3b33c3aad 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -314,9 +314,19 @@ def test_aspirate_in_place( subject: InstrumentCore, ) -> None: """It should aspirate in place.""" - subject.aspirate(volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=None) + location = Location(point=Point(1, 2, 3), labware=None) + subject.aspirate( + volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location + ) decoy.verify( + mock_engine_client.move_to_coordinates( + pipette_id="abc123", + coordinates=DeckPoint(x=1, y=2, z=3), + minimum_z_height=None, + force_direct=False, + speed=None, + ), mock_engine_client.aspirate_in_place( pipette_id="abc123", volume=12.34, @@ -331,7 +341,7 @@ def test_blow_out_to_well( mock_protocol_core: ProtocolCore, subject: InstrumentCore, ) -> None: - """It should aspirate from a well.""" + """It should blow out from a well.""" location = Location(point=Point(1, 2, 3), labware=None) well_core = WellCore( @@ -344,7 +354,7 @@ def test_blow_out_to_well( ) ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) - subject.blow_out(location=location, well_core=well_core, move_to_well=True) + subject.blow_out(location=location, well_core=well_core) decoy.verify( mock_engine_client.blow_out( @@ -360,6 +370,33 @@ def test_blow_out_to_well( ) +def test_blow_out_in_place( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, +) -> None: + """It should blow out in place.""" + location = Location(point=Point(1, 2, 3), labware=None) + + subject.blow_out(location=location, well_core=None) + + decoy.verify( + mock_engine_client.move_to_coordinates( + pipette_id="abc123", + coordinates=DeckPoint(x=1, y=2, z=3), + minimum_z_height=None, + speed=None, + force_direct=False, + ), + mock_engine_client.blow_out_in_place( + pipette_id="abc123", + flow_rate=1.23, + ), + mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), + ) + + def test_dispense_to_well( decoy: Decoy, mock_engine_client: EngineClient, @@ -398,6 +435,34 @@ def test_dispense_to_well( ) +def test_dispense_in_place( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, +) -> None: + """It should dispense in place.""" + location = Location(point=Point(1, 2, 3), labware=None) + subject.dispense( + volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location + ) + + decoy.verify( + mock_engine_client.move_to_coordinates( + pipette_id="abc123", + coordinates=DeckPoint(x=1, y=2, z=3), + minimum_z_height=None, + force_direct=False, + speed=None, + ), + mock_engine_client.dispense_in_place( + pipette_id="abc123", + volume=12.34, + flow_rate=7.8, + ), + ) + + def test_initialization_sets_default_movement_speed( decoy: Decoy, subject: InstrumentCore, diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 17507a06582..0972e87d7b9 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -233,7 +233,6 @@ def test_blow_out_to_well( mock_instrument_core.blow_out( location=top_location, well_core=mock_well._core, - move_to_well=True, ), times=1, ) @@ -256,7 +255,6 @@ def test_blow_out_to_location( mock_instrument_core.blow_out( location=mock_location, well_core=mock_well._core, - move_to_well=True, ), times=1, ) @@ -280,7 +278,6 @@ def test_blow_out_in_place( mock_instrument_core.blow_out( location=location, well_core=mock_well._core, - move_to_well=False, ), times=1, ) diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index 0cd28b887c6..372440a45cc 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -69,7 +69,6 @@ def test_blow_out_no_tip(subject: InstrumentCore, labware: LabwareCore) -> None: subject.blow_out( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), - move_to_well=False, ) @@ -261,7 +260,6 @@ def _aspirate_blowout(i: InstrumentCore, labware: LabwareCore) -> None: i.blow_out( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), - move_to_well=False, ) From bb0736ad0e8ea18c9ebb4c7d15e1ca86a1d06443 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Mon, 6 Feb 2023 11:48:44 -0500 Subject: [PATCH 12/86] check if location is not the last location before move_to_coordinates --- .../protocol_api/core/engine/instrument.py | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 13792ee2b71..bfc264fe930 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -81,15 +81,16 @@ def aspirate( flow_rate: The flow rate in µL/s to aspirate at. """ if well_core is None: - self._engine_client.move_to_coordinates( - pipette_id=self._pipette_id, - coordinates=DeckPoint( - x=location.point.x, y=location.point.y, z=location.point.z - ), - minimum_z_height=None, - force_direct=False, - speed=None, - ) + if location != self._protocol_core.get_last_location(): + self._engine_client.move_to_coordinates( + pipette_id=self._pipette_id, + coordinates=DeckPoint( + x=location.point.x, y=location.point.y, z=location.point.z + ), + minimum_z_height=None, + force_direct=False, + speed=None, + ) self._engine_client.aspirate_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate @@ -135,15 +136,16 @@ def dispense( flow_rate: The flow rate in µL/s to dispense at. """ if well_core is None: - self._engine_client.move_to_coordinates( - pipette_id=self._pipette_id, - coordinates=DeckPoint( - x=location.point.x, y=location.point.y, z=location.point.z - ), - minimum_z_height=None, - force_direct=False, - speed=None, - ) + if location != self._protocol_core.get_last_location(): + self._engine_client.move_to_coordinates( + pipette_id=self._pipette_id, + coordinates=DeckPoint( + x=location.point.x, y=location.point.y, z=location.point.z + ), + minimum_z_height=None, + force_direct=False, + speed=None, + ) self._engine_client.dispense_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate @@ -180,15 +182,16 @@ def blow_out(self, location: Location, well_core: Optional[WellCore]) -> None: """ flow_rate = self.get_absolute_blow_out_flow_rate(1.0) if well_core is None: - self._engine_client.move_to_coordinates( - pipette_id=self._pipette_id, - coordinates=DeckPoint( - x=location.point.x, y=location.point.y, z=location.point.z - ), - force_direct=False, - minimum_z_height=None, - speed=None, - ) + if location != self._protocol_core.get_last_location(): + self._engine_client.move_to_coordinates( + pipette_id=self._pipette_id, + coordinates=DeckPoint( + x=location.point.x, y=location.point.y, z=location.point.z + ), + force_direct=False, + minimum_z_height=None, + speed=None, + ) self._engine_client.blow_out_in_place( pipette_id=self._pipette_id, flow_rate=flow_rate From f7298ce8b9d845266a697a594ac37c7b0f6d6359 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Tue, 7 Feb 2023 14:23:08 -0500 Subject: [PATCH 13/86] Update api/src/opentrons/protocol_api/instrument_context.py --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a8277d71b45..aeb64df1aa3 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -41,7 +41,6 @@ _PREP_AFTER_ADDED_IN = APIVersion(2, 13) """The version after which the pick-up tip procedure should also prepare the plunger.""" - _PRESSES_INCREMENT_REMOVED_IN = APIVersion(2, 14) """The version after which the pick-up tip procedure deprecates presses and increment arguments.""" From 6879d11a22b0dcfb9bcf979669c377e277637aeb Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Tue, 7 Feb 2023 14:27:51 -0500 Subject: [PATCH 14/86] Update api/src/opentrons/protocol_engine/commands/aspirate_in_place.py --- api/src/opentrons/protocol_engine/commands/aspirate_in_place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index b7dbd65316e..578faff2b09 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -1,4 +1,4 @@ -"""Aspirate-in-place command request, result, and implementation models.""" +"""Aspirate in place command request, result, and implementation models.""" # TODO(mm, 2022-08-15): This command is not yet in the JSON protocol schema. # Before our production code emits this command, we must add it to the schema, From 70540d2515edda675561e6a69c318e19b51e0296 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Tue, 7 Feb 2023 14:28:54 -0500 Subject: [PATCH 15/86] Update api/src/opentrons/protocol_engine/commands/blow_out_in_place.py --- api/src/opentrons/protocol_engine/commands/blow_out_in_place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py index 572bd9379a2..d759b2a3cb0 100644 --- a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py @@ -1,4 +1,4 @@ -"""Blow-out-in-place command request, result, and implementation models.""" +"""Blow-out in place command request, result, and implementation models.""" # TODO(mm, 2022-08-15): This command is not yet in the JSON protocol schema. # Before our production code emits this command, we must add it to the schema, From b2ffa4b247f877cc8cc6879fad3c01beb51b5d3a Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 7 Feb 2023 14:33:50 -0500 Subject: [PATCH 16/86] fixed move_to_well removal --- .../protocol_api/core/legacy/legacy_instrument_core.py | 3 ++- .../core/legacy_simulator/legacy_instrument_core.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index b0a4eeb3525..a48e9a1ae76 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -129,7 +129,8 @@ def blow_out( location: The location to blow out into. well_core: Unused by legacy core. """ - self.move_to(location=location) + if location != self._protocol_interface.get_last_location(mount=self._mount): + self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) def touch_tip( diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 2f4b5edf56f..8f2dfb0b0f5 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -118,7 +118,8 @@ def blow_out( location: types.Location, well_core: Optional[LegacyWellCore], ) -> None: - self.move_to(location=location, well_core=well_core) + if location != self._protocol_interface.get_last_location(mount=self._mount): + self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.BLOWOUT.name) self._update_volume(0) self._pipette_dict["ready_to_aspirate"] = False From e1c9c0675ec3653bf5963428755cd6a17fdb5822 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 7 Feb 2023 14:49:42 -0500 Subject: [PATCH 17/86] updated commands schema --- shared-data/command/schemas/7.json | 806 +++++++++++++++++++++++------ 1 file changed, 654 insertions(+), 152 deletions(-) diff --git a/shared-data/command/schemas/7.json b/shared-data/command/schemas/7.json index 9e23282fbb5..8740a608ad5 100644 --- a/shared-data/command/schemas/7.json +++ b/shared-data/command/schemas/7.json @@ -5,6 +5,9 @@ { "$ref": "#/definitions/AspirateCreate" }, + { + "$ref": "#/definitions/AspirateInPlaceCreate" + }, { "$ref": "#/definitions/CommentCreate" }, @@ -20,6 +23,9 @@ { "$ref": "#/definitions/BlowOutCreate" }, + { + "$ref": "#/definitions/BlowOutInPlaceCreate" + }, { "$ref": "#/definitions/DropTipCreate" }, @@ -145,7 +151,11 @@ "WellOrigin": { "title": "WellOrigin", "description": "Origin of WellLocation offset.", - "enum": ["top", "bottom", "center"], + "enum": [ + "top", + "bottom", + "center" + ], "type": "string" }, "WellOffset": { @@ -230,12 +240,21 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "flowRate", + "volume", + "pipetteId" + ] }, "CommandIntent": { "title": "CommandIntent", "description": "Run intent for a given command.\n\nProps:\n PROTOCOL: the command is part of the protocol run itself.\n SETUP: the command is part of the setup phase of a run.", - "enum": ["protocol", "setup"], + "enum": [ + "protocol", + "setup" + ], "type": "string" }, "AspirateCreate": { @@ -246,7 +265,9 @@ "commandType": { "title": "Commandtype", "default": "aspirate", - "enum": ["aspirate"], + "enum": [ + "aspirate" + ], "type": "string" }, "params": { @@ -266,7 +287,72 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] + }, + "AspirateInPlaceParams": { + "title": "AspirateInPlaceParams", + "description": "Payload required to aspirate in place.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "Amount of liquid in uL. Must be greater than 0 and less than a pipette-specific maximum volume.", + "exclusiveMinimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": [ + "flowRate", + "volume", + "pipetteId" + ] + }, + "AspirateInPlaceCreate": { + "title": "AspirateInPlaceCreate", + "description": "AspirateInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "aspirateInPlace", + "enum": [ + "aspirateInPlace" + ], + "type": "string" + }, + "params": { + "$ref": "#/definitions/AspirateInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": [ + "params" + ] }, "CommentParams": { "title": "CommentParams", @@ -279,7 +365,9 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] }, "CommentCreate": { "title": "CommentCreate", @@ -289,7 +377,9 @@ "commandType": { "title": "Commandtype", "default": "comment", - "enum": ["comment"], + "enum": [ + "comment" + ], "type": "string" }, "params": { @@ -309,7 +399,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CustomParams": { "title": "CustomParams", @@ -325,7 +417,9 @@ "commandType": { "title": "Commandtype", "default": "custom", - "enum": ["custom"], + "enum": [ + "custom" + ], "type": "string" }, "params": { @@ -345,7 +439,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DispenseParams": { "title": "DispenseParams", @@ -389,7 +485,13 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "flowRate", + "volume", + "pipetteId" + ] }, "DispenseCreate": { "title": "DispenseCreate", @@ -399,7 +501,9 @@ "commandType": { "title": "Commandtype", "default": "dispense", - "enum": ["dispense"], + "enum": [ + "dispense" + ], "type": "string" }, "params": { @@ -419,7 +523,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DispenseInPlaceParams": { "title": "DispenseInPlaceParams", @@ -444,7 +550,11 @@ "type": "string" } }, - "required": ["flowRate", "volume", "pipetteId"] + "required": [ + "flowRate", + "volume", + "pipetteId" + ] }, "DispenseInPlaceCreate": { "title": "DispenseInPlaceCreate", @@ -454,7 +564,9 @@ "commandType": { "title": "Commandtype", "default": "dispenseInPlace", - "enum": ["dispenseInPlace"], + "enum": [ + "dispenseInPlace" + ], "type": "string" }, "params": { @@ -474,7 +586,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "BlowOutParams": { "title": "BlowOutParams", @@ -512,7 +626,12 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "flowRate", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "flowRate", + "pipetteId" + ] }, "BlowOutCreate": { "title": "BlowOutCreate", @@ -522,7 +641,9 @@ "commandType": { "title": "Commandtype", "default": "blowout", - "enum": ["blowout"], + "enum": [ + "blowout" + ], "type": "string" }, "params": { @@ -542,7 +663,65 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] + }, + "BlowOutInPlaceParams": { + "title": "BlowOutInPlaceParams", + "description": "Payload required to blow-out in place.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": [ + "flowRate", + "pipetteId" + ] + }, + "BlowOutInPlaceCreate": { + "title": "BlowOutInPlaceCreate", + "description": "BlowOutInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "blowOutInPlace", + "enum": [ + "blowOutInPlace" + ], + "type": "string" + }, + "params": { + "$ref": "#/definitions/BlowOutInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": [ + "params" + ] }, "DropTipParams": { "title": "DropTipParams", @@ -574,7 +753,11 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "DropTipCreate": { "title": "DropTipCreate", @@ -584,7 +767,9 @@ "commandType": { "title": "Commandtype", "default": "dropTip", - "enum": ["dropTip"], + "enum": [ + "dropTip" + ], "type": "string" }, "params": { @@ -604,12 +789,21 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MotorAxis": { "title": "MotorAxis", "description": "Motor axis on which to issue a home command.", - "enum": ["x", "y", "leftZ", "rightZ", "leftPlunger", "rightPlunger"], + "enum": [ + "x", + "y", + "leftZ", + "rightZ", + "leftPlunger", + "rightPlunger" + ], "type": "string" }, "HomeParams": { @@ -634,7 +828,9 @@ "commandType": { "title": "Commandtype", "default": "home", - "enum": ["home"], + "enum": [ + "home" + ], "type": "string" }, "params": { @@ -654,12 +850,27 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeckSlotName": { "title": "DeckSlotName", "description": "Deck slot identifiers.", - "enum": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], + "enum": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ], "type": "string" }, "DeckSlotLocation": { @@ -671,7 +882,9 @@ "$ref": "#/definitions/DeckSlotName" } }, - "required": ["slotName"] + "required": [ + "slotName" + ] }, "ModuleLocation": { "title": "ModuleLocation", @@ -684,7 +897,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "LoadLabwareParams": { "title": "LoadLabwareParams", @@ -702,7 +917,9 @@ "$ref": "#/definitions/ModuleLocation" }, { - "enum": ["offDeck"], + "enum": [ + "offDeck" + ], "type": "string" } ] @@ -733,7 +950,12 @@ "type": "string" } }, - "required": ["location", "loadName", "namespace", "version"] + "required": [ + "location", + "loadName", + "namespace", + "version" + ] }, "LoadLabwareCreate": { "title": "LoadLabwareCreate", @@ -743,7 +965,9 @@ "commandType": { "title": "Commandtype", "default": "loadLabware", - "enum": ["loadLabware"], + "enum": [ + "loadLabware" + ], "type": "string" }, "params": { @@ -763,7 +987,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "LoadLiquidParams": { "title": "LoadLiquidParams", @@ -789,7 +1015,11 @@ } } }, - "required": ["liquidId", "labwareId", "volumeByWell"] + "required": [ + "liquidId", + "labwareId", + "volumeByWell" + ] }, "LoadLiquidCreate": { "title": "LoadLiquidCreate", @@ -799,7 +1029,9 @@ "commandType": { "title": "Commandtype", "default": "loadLiquid", - "enum": ["loadLiquid"], + "enum": [ + "loadLiquid" + ], "type": "string" }, "params": { @@ -819,7 +1051,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "ModuleModel": { "title": "ModuleModel", @@ -863,7 +1097,10 @@ "type": "string" } }, - "required": ["model", "location"] + "required": [ + "model", + "location" + ] }, "LoadModuleCreate": { "title": "LoadModuleCreate", @@ -873,7 +1110,9 @@ "commandType": { "title": "Commandtype", "default": "loadModule", - "enum": ["loadModule"], + "enum": [ + "loadModule" + ], "type": "string" }, "params": { @@ -893,7 +1132,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "PipetteNameType": { "title": "PipetteNameType", @@ -921,7 +1162,10 @@ "MountType": { "title": "MountType", "description": "An enumeration.", - "enum": ["left", "right"], + "enum": [ + "left", + "right" + ], "type": "string" }, "LoadPipetteParams": { @@ -937,7 +1181,9 @@ "$ref": "#/definitions/PipetteNameType" }, { - "enum": ["p1000_96"], + "enum": [ + "p1000_96" + ], "type": "string" } ] @@ -956,7 +1202,10 @@ "type": "string" } }, - "required": ["pipetteName", "mount"] + "required": [ + "pipetteName", + "mount" + ] }, "LoadPipetteCreate": { "title": "LoadPipetteCreate", @@ -966,7 +1215,9 @@ "commandType": { "title": "Commandtype", "default": "loadPipette", - "enum": ["loadPipette"], + "enum": [ + "loadPipette" + ], "type": "string" }, "params": { @@ -986,12 +1237,18 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "LabwareMovementStrategy": { "title": "LabwareMovementStrategy", "description": "Strategy to use for labware movement.", - "enum": ["usingGripper", "manualMoveWithPause", "manualMoveWithoutPause"], + "enum": [ + "usingGripper", + "manualMoveWithPause", + "manualMoveWithoutPause" + ], "type": "string" }, "LabwareOffsetVector": { @@ -1012,7 +1269,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "MoveLabwareParams": { "title": "MoveLabwareParams", @@ -1035,7 +1296,9 @@ "$ref": "#/definitions/ModuleLocation" }, { - "enum": ["offDeck"], + "enum": [ + "offDeck" + ], "type": "string" } ] @@ -1079,7 +1342,11 @@ ] } }, - "required": ["labwareId", "newLocation", "strategy"] + "required": [ + "labwareId", + "newLocation", + "strategy" + ] }, "MoveLabwareCreate": { "title": "MoveLabwareCreate", @@ -1089,7 +1356,9 @@ "commandType": { "title": "Commandtype", "default": "moveLabware", - "enum": ["moveLabware"], + "enum": [ + "moveLabware" + ], "type": "string" }, "params": { @@ -1109,12 +1378,18 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MovementAxis": { "title": "MovementAxis", "description": "Axis on which to issue a relative movement.", - "enum": ["x", "y", "z"], + "enum": [ + "x", + "y", + "z" + ], "type": "string" }, "MoveRelativeParams": { @@ -1141,7 +1416,11 @@ "type": "number" } }, - "required": ["pipetteId", "axis", "distance"] + "required": [ + "pipetteId", + "axis", + "distance" + ] }, "MoveRelativeCreate": { "title": "MoveRelativeCreate", @@ -1151,7 +1430,9 @@ "commandType": { "title": "Commandtype", "default": "moveRelative", - "enum": ["moveRelative"], + "enum": [ + "moveRelative" + ], "type": "string" }, "params": { @@ -1171,7 +1452,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeckPoint": { "title": "DeckPoint", @@ -1191,7 +1474,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "MoveToCoordinatesParams": { "title": "MoveToCoordinatesParams", @@ -1229,7 +1516,10 @@ ] } }, - "required": ["pipetteId", "coordinates"] + "required": [ + "pipetteId", + "coordinates" + ] }, "MoveToCoordinatesCreate": { "title": "MoveToCoordinatesCreate", @@ -1239,7 +1529,9 @@ "commandType": { "title": "Commandtype", "default": "moveToCoordinates", - "enum": ["moveToCoordinates"], + "enum": [ + "moveToCoordinates" + ], "type": "string" }, "params": { @@ -1259,7 +1551,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveToWellParams": { "title": "MoveToWellParams", @@ -1307,7 +1601,11 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "MoveToWellCreate": { "title": "MoveToWellCreate", @@ -1317,7 +1615,9 @@ "commandType": { "title": "Commandtype", "default": "moveToWell", - "enum": ["moveToWell"], + "enum": [ + "moveToWell" + ], "type": "string" }, "params": { @@ -1337,7 +1637,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForResumeParams": { "title": "WaitForResumeParams", @@ -1359,7 +1661,10 @@ "commandType": { "title": "Commandtype", "default": "waitForResume", - "enum": ["waitForResume", "pause"], + "enum": [ + "waitForResume", + "pause" + ], "type": "string" }, "params": { @@ -1379,7 +1684,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForDurationParams": { "title": "WaitForDurationParams", @@ -1397,7 +1704,9 @@ "type": "string" } }, - "required": ["seconds"] + "required": [ + "seconds" + ] }, "WaitForDurationCreate": { "title": "WaitForDurationCreate", @@ -1407,7 +1716,9 @@ "commandType": { "title": "Commandtype", "default": "waitForDuration", - "enum": ["waitForDuration"], + "enum": [ + "waitForDuration" + ], "type": "string" }, "params": { @@ -1427,7 +1738,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "PickUpTipParams": { "title": "PickUpTipParams", @@ -1459,7 +1772,11 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "PickUpTipCreate": { "title": "PickUpTipCreate", @@ -1469,7 +1786,9 @@ "commandType": { "title": "Commandtype", "default": "pickUpTip", - "enum": ["pickUpTip"], + "enum": [ + "pickUpTip" + ], "type": "string" }, "params": { @@ -1489,7 +1808,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SavePositionParams": { "title": "SavePositionParams", @@ -1507,7 +1828,9 @@ "type": "string" } }, - "required": ["pipetteId"] + "required": [ + "pipetteId" + ] }, "SavePositionCreate": { "title": "SavePositionCreate", @@ -1517,7 +1840,9 @@ "commandType": { "title": "Commandtype", "default": "savePosition", - "enum": ["savePosition"], + "enum": [ + "savePosition" + ], "type": "string" }, "params": { @@ -1537,7 +1862,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetRailLightsParams": { "title": "SetRailLightsParams", @@ -1550,7 +1877,9 @@ "type": "boolean" } }, - "required": ["on"] + "required": [ + "on" + ] }, "SetRailLightsCreate": { "title": "SetRailLightsCreate", @@ -1560,7 +1889,9 @@ "commandType": { "title": "Commandtype", "default": "setRailLights", - "enum": ["setRailLights"], + "enum": [ + "setRailLights" + ], "type": "string" }, "params": { @@ -1580,7 +1911,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "TouchTipParams": { "title": "TouchTipParams", @@ -1623,7 +1956,11 @@ "type": "number" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "TouchTipCreate": { "title": "TouchTipCreate", @@ -1633,7 +1970,9 @@ "commandType": { "title": "Commandtype", "default": "touchTip", - "enum": ["touchTip"], + "enum": [ + "touchTip" + ], "type": "string" }, "params": { @@ -1653,7 +1992,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureParams": { "title": "WaitForTemperatureParams", @@ -1671,7 +2012,9 @@ "type": "number" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureCreate": { "title": "WaitForTemperatureCreate", @@ -1681,7 +2024,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/waitForTemperature", - "enum": ["heaterShaker/waitForTemperature"], + "enum": [ + "heaterShaker/waitForTemperature" + ], "type": "string" }, "params": { @@ -1701,7 +2046,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureParams": { "title": "SetTargetTemperatureParams", @@ -1719,7 +2066,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureCreate": { "title": "SetTargetTemperatureCreate", @@ -1729,7 +2079,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/setTargetTemperature", - "enum": ["heaterShaker/setTargetTemperature"], + "enum": [ + "heaterShaker/setTargetTemperature" + ], "type": "string" }, "params": { @@ -1749,7 +2101,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateHeaterParams": { "title": "DeactivateHeaterParams", @@ -1762,7 +2116,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateHeaterCreate": { "title": "DeactivateHeaterCreate", @@ -1772,7 +2128,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/deactivateHeater", - "enum": ["heaterShaker/deactivateHeater"], + "enum": [ + "heaterShaker/deactivateHeater" + ], "type": "string" }, "params": { @@ -1792,7 +2150,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetAndWaitForShakeSpeedParams": { "title": "SetAndWaitForShakeSpeedParams", @@ -1810,7 +2170,10 @@ "type": "number" } }, - "required": ["moduleId", "rpm"] + "required": [ + "moduleId", + "rpm" + ] }, "SetAndWaitForShakeSpeedCreate": { "title": "SetAndWaitForShakeSpeedCreate", @@ -1820,7 +2183,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/setAndWaitForShakeSpeed", - "enum": ["heaterShaker/setAndWaitForShakeSpeed"], + "enum": [ + "heaterShaker/setAndWaitForShakeSpeed" + ], "type": "string" }, "params": { @@ -1840,7 +2205,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateShakerParams": { "title": "DeactivateShakerParams", @@ -1853,7 +2220,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateShakerCreate": { "title": "DeactivateShakerCreate", @@ -1863,7 +2232,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/deactivateShaker", - "enum": ["heaterShaker/deactivateShaker"], + "enum": [ + "heaterShaker/deactivateShaker" + ], "type": "string" }, "params": { @@ -1883,7 +2254,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "OpenLabwareLatchParams": { "title": "OpenLabwareLatchParams", @@ -1896,7 +2269,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "OpenLabwareLatchCreate": { "title": "OpenLabwareLatchCreate", @@ -1906,7 +2281,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/openLabwareLatch", - "enum": ["heaterShaker/openLabwareLatch"], + "enum": [ + "heaterShaker/openLabwareLatch" + ], "type": "string" }, "params": { @@ -1926,7 +2303,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CloseLabwareLatchParams": { "title": "CloseLabwareLatchParams", @@ -1939,7 +2318,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "CloseLabwareLatchCreate": { "title": "CloseLabwareLatchCreate", @@ -1949,7 +2330,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/closeLabwareLatch", - "enum": ["heaterShaker/closeLabwareLatch"], + "enum": [ + "heaterShaker/closeLabwareLatch" + ], "type": "string" }, "params": { @@ -1969,7 +2352,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DisengageParams": { "title": "DisengageParams", @@ -1982,7 +2367,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DisengageCreate": { "title": "DisengageCreate", @@ -1992,7 +2379,9 @@ "commandType": { "title": "Commandtype", "default": "magneticModule/disengage", - "enum": ["magneticModule/disengage"], + "enum": [ + "magneticModule/disengage" + ], "type": "string" }, "params": { @@ -2012,7 +2401,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "EngageParams": { "title": "EngageParams", @@ -2030,7 +2421,10 @@ "type": "number" } }, - "required": ["moduleId", "height"] + "required": [ + "moduleId", + "height" + ] }, "EngageCreate": { "title": "EngageCreate", @@ -2040,7 +2434,9 @@ "commandType": { "title": "Commandtype", "default": "magneticModule/engage", - "enum": ["magneticModule/engage"], + "enum": [ + "magneticModule/engage" + ], "type": "string" }, "params": { @@ -2060,7 +2456,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureParams": { "title": "SetTargetTemperatureParams", @@ -2078,7 +2476,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureCreate": { "title": "SetTargetTemperatureCreate", @@ -2088,7 +2489,9 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/setTargetTemperature", - "enum": ["temperatureModule/setTargetTemperature"], + "enum": [ + "temperatureModule/setTargetTemperature" + ], "type": "string" }, "params": { @@ -2108,7 +2511,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureParams": { "title": "WaitForTemperatureParams", @@ -2126,7 +2531,9 @@ "type": "number" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureCreate": { "title": "WaitForTemperatureCreate", @@ -2136,7 +2543,9 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/waitForTemperature", - "enum": ["temperatureModule/waitForTemperature"], + "enum": [ + "temperatureModule/waitForTemperature" + ], "type": "string" }, "params": { @@ -2156,7 +2565,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateTemperatureParams": { "title": "DeactivateTemperatureParams", @@ -2169,7 +2580,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateTemperatureCreate": { "title": "DeactivateTemperatureCreate", @@ -2179,7 +2592,9 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/deactivate", - "enum": ["temperatureModule/deactivate"], + "enum": [ + "temperatureModule/deactivate" + ], "type": "string" }, "params": { @@ -2199,7 +2614,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetTargetBlockTemperatureParams": { "title": "SetTargetBlockTemperatureParams", @@ -2222,7 +2639,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "SetTargetBlockTemperatureCreate": { "title": "SetTargetBlockTemperatureCreate", @@ -2232,7 +2652,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/setTargetBlockTemperature", - "enum": ["thermocycler/setTargetBlockTemperature"], + "enum": [ + "thermocycler/setTargetBlockTemperature" + ], "type": "string" }, "params": { @@ -2252,7 +2674,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForBlockTemperatureParams": { "title": "WaitForBlockTemperatureParams", @@ -2265,7 +2689,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "WaitForBlockTemperatureCreate": { "title": "WaitForBlockTemperatureCreate", @@ -2275,7 +2701,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/waitForBlockTemperature", - "enum": ["thermocycler/waitForBlockTemperature"], + "enum": [ + "thermocycler/waitForBlockTemperature" + ], "type": "string" }, "params": { @@ -2295,7 +2723,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetTargetLidTemperatureParams": { "title": "SetTargetLidTemperatureParams", @@ -2313,7 +2743,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "SetTargetLidTemperatureCreate": { "title": "SetTargetLidTemperatureCreate", @@ -2323,7 +2756,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/setTargetLidTemperature", - "enum": ["thermocycler/setTargetLidTemperature"], + "enum": [ + "thermocycler/setTargetLidTemperature" + ], "type": "string" }, "params": { @@ -2343,7 +2778,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForLidTemperatureParams": { "title": "WaitForLidTemperatureParams", @@ -2356,7 +2793,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "WaitForLidTemperatureCreate": { "title": "WaitForLidTemperatureCreate", @@ -2366,7 +2805,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/waitForLidTemperature", - "enum": ["thermocycler/waitForLidTemperature"], + "enum": [ + "thermocycler/waitForLidTemperature" + ], "type": "string" }, "params": { @@ -2386,7 +2827,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateBlockParams": { "title": "DeactivateBlockParams", @@ -2399,7 +2842,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateBlockCreate": { "title": "DeactivateBlockCreate", @@ -2409,7 +2854,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/deactivateBlock", - "enum": ["thermocycler/deactivateBlock"], + "enum": [ + "thermocycler/deactivateBlock" + ], "type": "string" }, "params": { @@ -2429,7 +2876,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateLidParams": { "title": "DeactivateLidParams", @@ -2442,7 +2891,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateLidCreate": { "title": "DeactivateLidCreate", @@ -2452,7 +2903,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/deactivateLid", - "enum": ["thermocycler/deactivateLid"], + "enum": [ + "thermocycler/deactivateLid" + ], "type": "string" }, "params": { @@ -2472,7 +2925,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "OpenLidParams": { "title": "OpenLidParams", @@ -2485,7 +2940,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "OpenLidCreate": { "title": "OpenLidCreate", @@ -2495,7 +2952,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/openLid", - "enum": ["thermocycler/openLid"], + "enum": [ + "thermocycler/openLid" + ], "type": "string" }, "params": { @@ -2515,7 +2974,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CloseLidParams": { "title": "CloseLidParams", @@ -2528,7 +2989,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "CloseLidCreate": { "title": "CloseLidCreate", @@ -2538,7 +3001,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/closeLid", - "enum": ["thermocycler/closeLid"], + "enum": [ + "thermocycler/closeLid" + ], "type": "string" }, "params": { @@ -2558,7 +3023,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "RunProfileStepParams": { "title": "RunProfileStepParams", @@ -2576,7 +3043,10 @@ "type": "number" } }, - "required": ["celsius", "holdSeconds"] + "required": [ + "celsius", + "holdSeconds" + ] }, "RunProfileParams": { "title": "RunProfileParams", @@ -2602,7 +3072,10 @@ "type": "number" } }, - "required": ["moduleId", "profile"] + "required": [ + "moduleId", + "profile" + ] }, "RunProfileCreate": { "title": "RunProfileCreate", @@ -2612,7 +3085,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/runProfile", - "enum": ["thermocycler/runProfile"], + "enum": [ + "thermocycler/runProfile" + ], "type": "string" }, "params": { @@ -2632,12 +3107,17 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CalibrateGripperParamsJaw": { "title": "CalibrateGripperParamsJaw", "description": "An enumeration.", - "enum": ["front", "rear"] + "enum": [ + "front", + "rear" + ] }, "Vec3f": { "title": "Vec3f", @@ -2657,7 +3137,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "CalibrateGripperParams": { "title": "CalibrateGripperParams", @@ -2682,7 +3166,9 @@ ] } }, - "required": ["jaw"] + "required": [ + "jaw" + ] }, "CalibrateGripperCreate": { "title": "CalibrateGripperCreate", @@ -2692,7 +3178,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibrateGripper", - "enum": ["calibration/calibrateGripper"], + "enum": [ + "calibration/calibrateGripper" + ], "type": "string" }, "params": { @@ -2712,7 +3200,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CalibratePipetteParams": { "title": "CalibratePipetteParams", @@ -2728,7 +3218,9 @@ ] } }, - "required": ["mount"] + "required": [ + "mount" + ] }, "CalibratePipetteCreate": { "title": "CalibratePipetteCreate", @@ -2738,7 +3230,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibratePipette", - "enum": ["calibration/calibratePipette"], + "enum": [ + "calibration/calibratePipette" + ], "type": "string" }, "params": { @@ -2758,7 +3252,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveToMaintenancePositionParams": { "title": "MoveToMaintenancePositionParams", @@ -2774,7 +3270,9 @@ ] } }, - "required": ["mount"] + "required": [ + "mount" + ] }, "MoveToMaintenancePositionCreate": { "title": "MoveToMaintenancePositionCreate", @@ -2784,7 +3282,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/moveToMaintenancePosition", - "enum": ["calibration/moveToMaintenancePosition"], + "enum": [ + "calibration/moveToMaintenancePosition" + ], "type": "string" }, "params": { @@ -2804,7 +3304,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] } }, "$id": "opentronsCommandSchemaV7", From 1d2e5056981c8702c0b622aef541365ae1fa931b Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 7 Feb 2023 15:09:20 -0500 Subject: [PATCH 18/86] linting js --- shared-data/command/schemas/7.json | 708 +++++++---------------------- 1 file changed, 158 insertions(+), 550 deletions(-) diff --git a/shared-data/command/schemas/7.json b/shared-data/command/schemas/7.json index 8740a608ad5..1ab40d76424 100644 --- a/shared-data/command/schemas/7.json +++ b/shared-data/command/schemas/7.json @@ -151,11 +151,7 @@ "WellOrigin": { "title": "WellOrigin", "description": "Origin of WellLocation offset.", - "enum": [ - "top", - "bottom", - "center" - ], + "enum": ["top", "bottom", "center"], "type": "string" }, "WellOffset": { @@ -240,21 +236,12 @@ "type": "string" } }, - "required": [ - "labwareId", - "wellName", - "flowRate", - "volume", - "pipetteId" - ] + "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] }, "CommandIntent": { "title": "CommandIntent", "description": "Run intent for a given command.\n\nProps:\n PROTOCOL: the command is part of the protocol run itself.\n SETUP: the command is part of the setup phase of a run.", - "enum": [ - "protocol", - "setup" - ], + "enum": ["protocol", "setup"], "type": "string" }, "AspirateCreate": { @@ -265,9 +252,7 @@ "commandType": { "title": "Commandtype", "default": "aspirate", - "enum": [ - "aspirate" - ], + "enum": ["aspirate"], "type": "string" }, "params": { @@ -287,9 +272,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "AspirateInPlaceParams": { "title": "AspirateInPlaceParams", @@ -314,11 +297,7 @@ "type": "string" } }, - "required": [ - "flowRate", - "volume", - "pipetteId" - ] + "required": ["flowRate", "volume", "pipetteId"] }, "AspirateInPlaceCreate": { "title": "AspirateInPlaceCreate", @@ -328,9 +307,7 @@ "commandType": { "title": "Commandtype", "default": "aspirateInPlace", - "enum": [ - "aspirateInPlace" - ], + "enum": ["aspirateInPlace"], "type": "string" }, "params": { @@ -350,9 +327,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "CommentParams": { "title": "CommentParams", @@ -365,9 +340,7 @@ "type": "string" } }, - "required": [ - "message" - ] + "required": ["message"] }, "CommentCreate": { "title": "CommentCreate", @@ -377,9 +350,7 @@ "commandType": { "title": "Commandtype", "default": "comment", - "enum": [ - "comment" - ], + "enum": ["comment"], "type": "string" }, "params": { @@ -399,9 +370,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "CustomParams": { "title": "CustomParams", @@ -417,9 +386,7 @@ "commandType": { "title": "Commandtype", "default": "custom", - "enum": [ - "custom" - ], + "enum": ["custom"], "type": "string" }, "params": { @@ -439,9 +406,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DispenseParams": { "title": "DispenseParams", @@ -485,13 +450,7 @@ "type": "string" } }, - "required": [ - "labwareId", - "wellName", - "flowRate", - "volume", - "pipetteId" - ] + "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] }, "DispenseCreate": { "title": "DispenseCreate", @@ -501,9 +460,7 @@ "commandType": { "title": "Commandtype", "default": "dispense", - "enum": [ - "dispense" - ], + "enum": ["dispense"], "type": "string" }, "params": { @@ -523,9 +480,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DispenseInPlaceParams": { "title": "DispenseInPlaceParams", @@ -550,11 +505,7 @@ "type": "string" } }, - "required": [ - "flowRate", - "volume", - "pipetteId" - ] + "required": ["flowRate", "volume", "pipetteId"] }, "DispenseInPlaceCreate": { "title": "DispenseInPlaceCreate", @@ -564,9 +515,7 @@ "commandType": { "title": "Commandtype", "default": "dispenseInPlace", - "enum": [ - "dispenseInPlace" - ], + "enum": ["dispenseInPlace"], "type": "string" }, "params": { @@ -586,9 +535,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "BlowOutParams": { "title": "BlowOutParams", @@ -626,12 +573,7 @@ "type": "string" } }, - "required": [ - "labwareId", - "wellName", - "flowRate", - "pipetteId" - ] + "required": ["labwareId", "wellName", "flowRate", "pipetteId"] }, "BlowOutCreate": { "title": "BlowOutCreate", @@ -641,9 +583,7 @@ "commandType": { "title": "Commandtype", "default": "blowout", - "enum": [ - "blowout" - ], + "enum": ["blowout"], "type": "string" }, "params": { @@ -663,9 +603,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "BlowOutInPlaceParams": { "title": "BlowOutInPlaceParams", @@ -684,10 +622,7 @@ "type": "string" } }, - "required": [ - "flowRate", - "pipetteId" - ] + "required": ["flowRate", "pipetteId"] }, "BlowOutInPlaceCreate": { "title": "BlowOutInPlaceCreate", @@ -697,9 +632,7 @@ "commandType": { "title": "Commandtype", "default": "blowOutInPlace", - "enum": [ - "blowOutInPlace" - ], + "enum": ["blowOutInPlace"], "type": "string" }, "params": { @@ -719,9 +652,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DropTipParams": { "title": "DropTipParams", @@ -753,11 +684,7 @@ "type": "string" } }, - "required": [ - "labwareId", - "wellName", - "pipetteId" - ] + "required": ["labwareId", "wellName", "pipetteId"] }, "DropTipCreate": { "title": "DropTipCreate", @@ -767,9 +694,7 @@ "commandType": { "title": "Commandtype", "default": "dropTip", - "enum": [ - "dropTip" - ], + "enum": ["dropTip"], "type": "string" }, "params": { @@ -789,21 +714,12 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "MotorAxis": { "title": "MotorAxis", "description": "Motor axis on which to issue a home command.", - "enum": [ - "x", - "y", - "leftZ", - "rightZ", - "leftPlunger", - "rightPlunger" - ], + "enum": ["x", "y", "leftZ", "rightZ", "leftPlunger", "rightPlunger"], "type": "string" }, "HomeParams": { @@ -828,9 +744,7 @@ "commandType": { "title": "Commandtype", "default": "home", - "enum": [ - "home" - ], + "enum": ["home"], "type": "string" }, "params": { @@ -850,27 +764,12 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeckSlotName": { "title": "DeckSlotName", "description": "Deck slot identifiers.", - "enum": [ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12" - ], + "enum": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"], "type": "string" }, "DeckSlotLocation": { @@ -882,9 +781,7 @@ "$ref": "#/definitions/DeckSlotName" } }, - "required": [ - "slotName" - ] + "required": ["slotName"] }, "ModuleLocation": { "title": "ModuleLocation", @@ -897,9 +794,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "LoadLabwareParams": { "title": "LoadLabwareParams", @@ -917,9 +812,7 @@ "$ref": "#/definitions/ModuleLocation" }, { - "enum": [ - "offDeck" - ], + "enum": ["offDeck"], "type": "string" } ] @@ -950,12 +843,7 @@ "type": "string" } }, - "required": [ - "location", - "loadName", - "namespace", - "version" - ] + "required": ["location", "loadName", "namespace", "version"] }, "LoadLabwareCreate": { "title": "LoadLabwareCreate", @@ -965,9 +853,7 @@ "commandType": { "title": "Commandtype", "default": "loadLabware", - "enum": [ - "loadLabware" - ], + "enum": ["loadLabware"], "type": "string" }, "params": { @@ -987,9 +873,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "LoadLiquidParams": { "title": "LoadLiquidParams", @@ -1015,11 +899,7 @@ } } }, - "required": [ - "liquidId", - "labwareId", - "volumeByWell" - ] + "required": ["liquidId", "labwareId", "volumeByWell"] }, "LoadLiquidCreate": { "title": "LoadLiquidCreate", @@ -1029,9 +909,7 @@ "commandType": { "title": "Commandtype", "default": "loadLiquid", - "enum": [ - "loadLiquid" - ], + "enum": ["loadLiquid"], "type": "string" }, "params": { @@ -1051,9 +929,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "ModuleModel": { "title": "ModuleModel", @@ -1097,10 +973,7 @@ "type": "string" } }, - "required": [ - "model", - "location" - ] + "required": ["model", "location"] }, "LoadModuleCreate": { "title": "LoadModuleCreate", @@ -1110,9 +983,7 @@ "commandType": { "title": "Commandtype", "default": "loadModule", - "enum": [ - "loadModule" - ], + "enum": ["loadModule"], "type": "string" }, "params": { @@ -1132,9 +1003,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "PipetteNameType": { "title": "PipetteNameType", @@ -1162,10 +1031,7 @@ "MountType": { "title": "MountType", "description": "An enumeration.", - "enum": [ - "left", - "right" - ], + "enum": ["left", "right"], "type": "string" }, "LoadPipetteParams": { @@ -1181,9 +1047,7 @@ "$ref": "#/definitions/PipetteNameType" }, { - "enum": [ - "p1000_96" - ], + "enum": ["p1000_96"], "type": "string" } ] @@ -1202,10 +1066,7 @@ "type": "string" } }, - "required": [ - "pipetteName", - "mount" - ] + "required": ["pipetteName", "mount"] }, "LoadPipetteCreate": { "title": "LoadPipetteCreate", @@ -1215,9 +1076,7 @@ "commandType": { "title": "Commandtype", "default": "loadPipette", - "enum": [ - "loadPipette" - ], + "enum": ["loadPipette"], "type": "string" }, "params": { @@ -1237,18 +1096,12 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "LabwareMovementStrategy": { "title": "LabwareMovementStrategy", "description": "Strategy to use for labware movement.", - "enum": [ - "usingGripper", - "manualMoveWithPause", - "manualMoveWithoutPause" - ], + "enum": ["usingGripper", "manualMoveWithPause", "manualMoveWithoutPause"], "type": "string" }, "LabwareOffsetVector": { @@ -1269,11 +1122,7 @@ "type": "number" } }, - "required": [ - "x", - "y", - "z" - ] + "required": ["x", "y", "z"] }, "MoveLabwareParams": { "title": "MoveLabwareParams", @@ -1296,9 +1145,7 @@ "$ref": "#/definitions/ModuleLocation" }, { - "enum": [ - "offDeck" - ], + "enum": ["offDeck"], "type": "string" } ] @@ -1342,11 +1189,7 @@ ] } }, - "required": [ - "labwareId", - "newLocation", - "strategy" - ] + "required": ["labwareId", "newLocation", "strategy"] }, "MoveLabwareCreate": { "title": "MoveLabwareCreate", @@ -1356,9 +1199,7 @@ "commandType": { "title": "Commandtype", "default": "moveLabware", - "enum": [ - "moveLabware" - ], + "enum": ["moveLabware"], "type": "string" }, "params": { @@ -1378,18 +1219,12 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "MovementAxis": { "title": "MovementAxis", "description": "Axis on which to issue a relative movement.", - "enum": [ - "x", - "y", - "z" - ], + "enum": ["x", "y", "z"], "type": "string" }, "MoveRelativeParams": { @@ -1416,11 +1251,7 @@ "type": "number" } }, - "required": [ - "pipetteId", - "axis", - "distance" - ] + "required": ["pipetteId", "axis", "distance"] }, "MoveRelativeCreate": { "title": "MoveRelativeCreate", @@ -1430,9 +1261,7 @@ "commandType": { "title": "Commandtype", "default": "moveRelative", - "enum": [ - "moveRelative" - ], + "enum": ["moveRelative"], "type": "string" }, "params": { @@ -1452,9 +1281,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeckPoint": { "title": "DeckPoint", @@ -1474,11 +1301,7 @@ "type": "number" } }, - "required": [ - "x", - "y", - "z" - ] + "required": ["x", "y", "z"] }, "MoveToCoordinatesParams": { "title": "MoveToCoordinatesParams", @@ -1516,10 +1339,7 @@ ] } }, - "required": [ - "pipetteId", - "coordinates" - ] + "required": ["pipetteId", "coordinates"] }, "MoveToCoordinatesCreate": { "title": "MoveToCoordinatesCreate", @@ -1529,9 +1349,7 @@ "commandType": { "title": "Commandtype", "default": "moveToCoordinates", - "enum": [ - "moveToCoordinates" - ], + "enum": ["moveToCoordinates"], "type": "string" }, "params": { @@ -1551,9 +1369,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "MoveToWellParams": { "title": "MoveToWellParams", @@ -1601,11 +1417,7 @@ "type": "string" } }, - "required": [ - "labwareId", - "wellName", - "pipetteId" - ] + "required": ["labwareId", "wellName", "pipetteId"] }, "MoveToWellCreate": { "title": "MoveToWellCreate", @@ -1615,9 +1427,7 @@ "commandType": { "title": "Commandtype", "default": "moveToWell", - "enum": [ - "moveToWell" - ], + "enum": ["moveToWell"], "type": "string" }, "params": { @@ -1637,9 +1447,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "WaitForResumeParams": { "title": "WaitForResumeParams", @@ -1661,10 +1469,7 @@ "commandType": { "title": "Commandtype", "default": "waitForResume", - "enum": [ - "waitForResume", - "pause" - ], + "enum": ["waitForResume", "pause"], "type": "string" }, "params": { @@ -1684,9 +1489,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "WaitForDurationParams": { "title": "WaitForDurationParams", @@ -1704,9 +1507,7 @@ "type": "string" } }, - "required": [ - "seconds" - ] + "required": ["seconds"] }, "WaitForDurationCreate": { "title": "WaitForDurationCreate", @@ -1716,9 +1517,7 @@ "commandType": { "title": "Commandtype", "default": "waitForDuration", - "enum": [ - "waitForDuration" - ], + "enum": ["waitForDuration"], "type": "string" }, "params": { @@ -1738,9 +1537,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "PickUpTipParams": { "title": "PickUpTipParams", @@ -1772,11 +1569,7 @@ "type": "string" } }, - "required": [ - "labwareId", - "wellName", - "pipetteId" - ] + "required": ["labwareId", "wellName", "pipetteId"] }, "PickUpTipCreate": { "title": "PickUpTipCreate", @@ -1786,9 +1579,7 @@ "commandType": { "title": "Commandtype", "default": "pickUpTip", - "enum": [ - "pickUpTip" - ], + "enum": ["pickUpTip"], "type": "string" }, "params": { @@ -1808,9 +1599,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "SavePositionParams": { "title": "SavePositionParams", @@ -1828,9 +1617,7 @@ "type": "string" } }, - "required": [ - "pipetteId" - ] + "required": ["pipetteId"] }, "SavePositionCreate": { "title": "SavePositionCreate", @@ -1840,9 +1627,7 @@ "commandType": { "title": "Commandtype", "default": "savePosition", - "enum": [ - "savePosition" - ], + "enum": ["savePosition"], "type": "string" }, "params": { @@ -1862,9 +1647,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "SetRailLightsParams": { "title": "SetRailLightsParams", @@ -1877,9 +1660,7 @@ "type": "boolean" } }, - "required": [ - "on" - ] + "required": ["on"] }, "SetRailLightsCreate": { "title": "SetRailLightsCreate", @@ -1889,9 +1670,7 @@ "commandType": { "title": "Commandtype", "default": "setRailLights", - "enum": [ - "setRailLights" - ], + "enum": ["setRailLights"], "type": "string" }, "params": { @@ -1911,9 +1690,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "TouchTipParams": { "title": "TouchTipParams", @@ -1956,11 +1733,7 @@ "type": "number" } }, - "required": [ - "labwareId", - "wellName", - "pipetteId" - ] + "required": ["labwareId", "wellName", "pipetteId"] }, "TouchTipCreate": { "title": "TouchTipCreate", @@ -1970,9 +1743,7 @@ "commandType": { "title": "Commandtype", "default": "touchTip", - "enum": [ - "touchTip" - ], + "enum": ["touchTip"], "type": "string" }, "params": { @@ -1992,9 +1763,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureParams": { "title": "WaitForTemperatureParams", @@ -2012,9 +1781,7 @@ "type": "number" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureCreate": { "title": "WaitForTemperatureCreate", @@ -2024,9 +1791,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/waitForTemperature", - "enum": [ - "heaterShaker/waitForTemperature" - ], + "enum": ["heaterShaker/waitForTemperature"], "type": "string" }, "params": { @@ -2046,9 +1811,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureParams": { "title": "SetTargetTemperatureParams", @@ -2066,10 +1829,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "celsius" - ] + "required": ["moduleId", "celsius"] }, "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureCreate": { "title": "SetTargetTemperatureCreate", @@ -2079,9 +1839,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/setTargetTemperature", - "enum": [ - "heaterShaker/setTargetTemperature" - ], + "enum": ["heaterShaker/setTargetTemperature"], "type": "string" }, "params": { @@ -2101,9 +1859,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeactivateHeaterParams": { "title": "DeactivateHeaterParams", @@ -2116,9 +1872,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "DeactivateHeaterCreate": { "title": "DeactivateHeaterCreate", @@ -2128,9 +1882,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/deactivateHeater", - "enum": [ - "heaterShaker/deactivateHeater" - ], + "enum": ["heaterShaker/deactivateHeater"], "type": "string" }, "params": { @@ -2150,9 +1902,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "SetAndWaitForShakeSpeedParams": { "title": "SetAndWaitForShakeSpeedParams", @@ -2170,10 +1920,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "rpm" - ] + "required": ["moduleId", "rpm"] }, "SetAndWaitForShakeSpeedCreate": { "title": "SetAndWaitForShakeSpeedCreate", @@ -2183,9 +1930,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/setAndWaitForShakeSpeed", - "enum": [ - "heaterShaker/setAndWaitForShakeSpeed" - ], + "enum": ["heaterShaker/setAndWaitForShakeSpeed"], "type": "string" }, "params": { @@ -2205,9 +1950,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeactivateShakerParams": { "title": "DeactivateShakerParams", @@ -2220,9 +1963,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "DeactivateShakerCreate": { "title": "DeactivateShakerCreate", @@ -2232,9 +1973,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/deactivateShaker", - "enum": [ - "heaterShaker/deactivateShaker" - ], + "enum": ["heaterShaker/deactivateShaker"], "type": "string" }, "params": { @@ -2254,9 +1993,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "OpenLabwareLatchParams": { "title": "OpenLabwareLatchParams", @@ -2269,9 +2006,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "OpenLabwareLatchCreate": { "title": "OpenLabwareLatchCreate", @@ -2281,9 +2016,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/openLabwareLatch", - "enum": [ - "heaterShaker/openLabwareLatch" - ], + "enum": ["heaterShaker/openLabwareLatch"], "type": "string" }, "params": { @@ -2303,9 +2036,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "CloseLabwareLatchParams": { "title": "CloseLabwareLatchParams", @@ -2318,9 +2049,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "CloseLabwareLatchCreate": { "title": "CloseLabwareLatchCreate", @@ -2330,9 +2059,7 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/closeLabwareLatch", - "enum": [ - "heaterShaker/closeLabwareLatch" - ], + "enum": ["heaterShaker/closeLabwareLatch"], "type": "string" }, "params": { @@ -2352,9 +2079,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DisengageParams": { "title": "DisengageParams", @@ -2367,9 +2092,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "DisengageCreate": { "title": "DisengageCreate", @@ -2379,9 +2102,7 @@ "commandType": { "title": "Commandtype", "default": "magneticModule/disengage", - "enum": [ - "magneticModule/disengage" - ], + "enum": ["magneticModule/disengage"], "type": "string" }, "params": { @@ -2401,9 +2122,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "EngageParams": { "title": "EngageParams", @@ -2421,10 +2140,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "height" - ] + "required": ["moduleId", "height"] }, "EngageCreate": { "title": "EngageCreate", @@ -2434,9 +2150,7 @@ "commandType": { "title": "Commandtype", "default": "magneticModule/engage", - "enum": [ - "magneticModule/engage" - ], + "enum": ["magneticModule/engage"], "type": "string" }, "params": { @@ -2456,9 +2170,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureParams": { "title": "SetTargetTemperatureParams", @@ -2476,10 +2188,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "celsius" - ] + "required": ["moduleId", "celsius"] }, "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureCreate": { "title": "SetTargetTemperatureCreate", @@ -2489,9 +2198,7 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/setTargetTemperature", - "enum": [ - "temperatureModule/setTargetTemperature" - ], + "enum": ["temperatureModule/setTargetTemperature"], "type": "string" }, "params": { @@ -2511,9 +2218,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureParams": { "title": "WaitForTemperatureParams", @@ -2531,9 +2236,7 @@ "type": "number" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureCreate": { "title": "WaitForTemperatureCreate", @@ -2543,9 +2246,7 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/waitForTemperature", - "enum": [ - "temperatureModule/waitForTemperature" - ], + "enum": ["temperatureModule/waitForTemperature"], "type": "string" }, "params": { @@ -2565,9 +2266,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeactivateTemperatureParams": { "title": "DeactivateTemperatureParams", @@ -2580,9 +2279,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "DeactivateTemperatureCreate": { "title": "DeactivateTemperatureCreate", @@ -2592,9 +2289,7 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/deactivate", - "enum": [ - "temperatureModule/deactivate" - ], + "enum": ["temperatureModule/deactivate"], "type": "string" }, "params": { @@ -2614,9 +2309,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "SetTargetBlockTemperatureParams": { "title": "SetTargetBlockTemperatureParams", @@ -2639,10 +2332,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "celsius" - ] + "required": ["moduleId", "celsius"] }, "SetTargetBlockTemperatureCreate": { "title": "SetTargetBlockTemperatureCreate", @@ -2652,9 +2342,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/setTargetBlockTemperature", - "enum": [ - "thermocycler/setTargetBlockTemperature" - ], + "enum": ["thermocycler/setTargetBlockTemperature"], "type": "string" }, "params": { @@ -2674,9 +2362,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "WaitForBlockTemperatureParams": { "title": "WaitForBlockTemperatureParams", @@ -2689,9 +2375,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "WaitForBlockTemperatureCreate": { "title": "WaitForBlockTemperatureCreate", @@ -2701,9 +2385,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/waitForBlockTemperature", - "enum": [ - "thermocycler/waitForBlockTemperature" - ], + "enum": ["thermocycler/waitForBlockTemperature"], "type": "string" }, "params": { @@ -2723,9 +2405,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "SetTargetLidTemperatureParams": { "title": "SetTargetLidTemperatureParams", @@ -2743,10 +2423,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "celsius" - ] + "required": ["moduleId", "celsius"] }, "SetTargetLidTemperatureCreate": { "title": "SetTargetLidTemperatureCreate", @@ -2756,9 +2433,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/setTargetLidTemperature", - "enum": [ - "thermocycler/setTargetLidTemperature" - ], + "enum": ["thermocycler/setTargetLidTemperature"], "type": "string" }, "params": { @@ -2778,9 +2453,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "WaitForLidTemperatureParams": { "title": "WaitForLidTemperatureParams", @@ -2793,9 +2466,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "WaitForLidTemperatureCreate": { "title": "WaitForLidTemperatureCreate", @@ -2805,9 +2476,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/waitForLidTemperature", - "enum": [ - "thermocycler/waitForLidTemperature" - ], + "enum": ["thermocycler/waitForLidTemperature"], "type": "string" }, "params": { @@ -2827,9 +2496,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeactivateBlockParams": { "title": "DeactivateBlockParams", @@ -2842,9 +2509,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "DeactivateBlockCreate": { "title": "DeactivateBlockCreate", @@ -2854,9 +2519,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/deactivateBlock", - "enum": [ - "thermocycler/deactivateBlock" - ], + "enum": ["thermocycler/deactivateBlock"], "type": "string" }, "params": { @@ -2876,9 +2539,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "DeactivateLidParams": { "title": "DeactivateLidParams", @@ -2891,9 +2552,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "DeactivateLidCreate": { "title": "DeactivateLidCreate", @@ -2903,9 +2562,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/deactivateLid", - "enum": [ - "thermocycler/deactivateLid" - ], + "enum": ["thermocycler/deactivateLid"], "type": "string" }, "params": { @@ -2925,9 +2582,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "OpenLidParams": { "title": "OpenLidParams", @@ -2940,9 +2595,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "OpenLidCreate": { "title": "OpenLidCreate", @@ -2952,9 +2605,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/openLid", - "enum": [ - "thermocycler/openLid" - ], + "enum": ["thermocycler/openLid"], "type": "string" }, "params": { @@ -2974,9 +2625,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "CloseLidParams": { "title": "CloseLidParams", @@ -2989,9 +2638,7 @@ "type": "string" } }, - "required": [ - "moduleId" - ] + "required": ["moduleId"] }, "CloseLidCreate": { "title": "CloseLidCreate", @@ -3001,9 +2648,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/closeLid", - "enum": [ - "thermocycler/closeLid" - ], + "enum": ["thermocycler/closeLid"], "type": "string" }, "params": { @@ -3023,9 +2668,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "RunProfileStepParams": { "title": "RunProfileStepParams", @@ -3043,10 +2686,7 @@ "type": "number" } }, - "required": [ - "celsius", - "holdSeconds" - ] + "required": ["celsius", "holdSeconds"] }, "RunProfileParams": { "title": "RunProfileParams", @@ -3072,10 +2712,7 @@ "type": "number" } }, - "required": [ - "moduleId", - "profile" - ] + "required": ["moduleId", "profile"] }, "RunProfileCreate": { "title": "RunProfileCreate", @@ -3085,9 +2722,7 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/runProfile", - "enum": [ - "thermocycler/runProfile" - ], + "enum": ["thermocycler/runProfile"], "type": "string" }, "params": { @@ -3107,17 +2742,12 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "CalibrateGripperParamsJaw": { "title": "CalibrateGripperParamsJaw", "description": "An enumeration.", - "enum": [ - "front", - "rear" - ] + "enum": ["front", "rear"] }, "Vec3f": { "title": "Vec3f", @@ -3137,11 +2767,7 @@ "type": "number" } }, - "required": [ - "x", - "y", - "z" - ] + "required": ["x", "y", "z"] }, "CalibrateGripperParams": { "title": "CalibrateGripperParams", @@ -3166,9 +2792,7 @@ ] } }, - "required": [ - "jaw" - ] + "required": ["jaw"] }, "CalibrateGripperCreate": { "title": "CalibrateGripperCreate", @@ -3178,9 +2802,7 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibrateGripper", - "enum": [ - "calibration/calibrateGripper" - ], + "enum": ["calibration/calibrateGripper"], "type": "string" }, "params": { @@ -3200,9 +2822,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "CalibratePipetteParams": { "title": "CalibratePipetteParams", @@ -3218,9 +2838,7 @@ ] } }, - "required": [ - "mount" - ] + "required": ["mount"] }, "CalibratePipetteCreate": { "title": "CalibratePipetteCreate", @@ -3230,9 +2848,7 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibratePipette", - "enum": [ - "calibration/calibratePipette" - ], + "enum": ["calibration/calibratePipette"], "type": "string" }, "params": { @@ -3252,9 +2868,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] }, "MoveToMaintenancePositionParams": { "title": "MoveToMaintenancePositionParams", @@ -3270,9 +2884,7 @@ ] } }, - "required": [ - "mount" - ] + "required": ["mount"] }, "MoveToMaintenancePositionCreate": { "title": "MoveToMaintenancePositionCreate", @@ -3282,9 +2894,7 @@ "commandType": { "title": "Commandtype", "default": "calibration/moveToMaintenancePosition", - "enum": [ - "calibration/moveToMaintenancePosition" - ], + "enum": ["calibration/moveToMaintenancePosition"], "type": "string" }, "params": { @@ -3304,9 +2914,7 @@ "type": "string" } }, - "required": [ - "params" - ] + "required": ["params"] } }, "$id": "opentronsCommandSchemaV7", From 80eecbdea551ddae8702cdbbe39dbb2fe3bb5d65 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 7 Feb 2023 16:18:07 -0500 Subject: [PATCH 19/86] fixed failing test in aspirate in place --- .../protocol_api/core/legacy/legacy_instrument_core.py | 2 +- .../core/legacy_simulator/legacy_instrument_core.py | 4 ++-- .../protocol_engine/commands/test_aspirate_in_place.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index a48e9a1ae76..61945c3c762 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -129,7 +129,7 @@ def blow_out( location: The location to blow out into. well_core: Unused by legacy core. """ - if location != self._protocol_interface.get_last_location(mount=self._mount): + if location != self._protocol_interface.get_last_location(): self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 8f2dfb0b0f5..33100559f75 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -85,7 +85,7 @@ def aspirate( "When aspirate is called on something other than a " "well relative position, we can't move to the top of" " the well to prepare for aspiration. This might " - "cause over aspiration if the previous command is a " + "cause overz aspiration if the previous command is a " "blow_out." ) self.prepare_for_aspirate() @@ -118,7 +118,7 @@ def blow_out( location: types.Location, well_core: Optional[LegacyWellCore], ) -> None: - if location != self._protocol_interface.get_last_location(mount=self._mount): + if location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.BLOWOUT.name) self._update_volume(0) diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py index 45c48976d06..c052e844ce2 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py @@ -24,7 +24,7 @@ async def test_aspirate_in_place_implementation( ) decoy.when( - await pipetting.dispense_in_place( + await pipetting.aspirate_in_place( pipette_id="pipette-id-abc", volume=123, flow_rate=456, From ca8fcc933cc949119b01a82325365a7bd5e6ea0a Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 7 Feb 2023 16:46:08 -0500 Subject: [PATCH 20/86] fixed legacy simulator test failing --- .../protocol_api_old/core/simulator/test_instrument_context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index 372440a45cc..ddeb9cd7055 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -65,6 +65,7 @@ def test_drop_tip_no_tip(subject: InstrumentCore, tip_rack: LabwareCore) -> None def test_blow_out_no_tip(subject: InstrumentCore, labware: LabwareCore) -> None: """It should raise an error if a tip is not attached.""" + subject.home() with pytest.raises(NoTipAttachedError, match="Cannot perform BLOWOUT"): subject.blow_out( location=Location(point=Point(1, 2, 3), labware=None), From d25298558281073ddf560e9c1687a306f8a12779 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 9 Feb 2023 15:40:06 -0500 Subject: [PATCH 21/86] cnanged location to optional and added exception when not ready to aspirate in place --- .../protocol_api/core/engine/instrument.py | 28 +++++++---- .../opentrons/protocol_api/core/instrument.py | 6 +-- .../core/legacy/legacy_instrument_core.py | 15 +++--- .../legacy_instrument_core.py | 15 +++--- .../protocol_api/instrument_context.py | 46 ++++++++++++----- .../protocol_engine/execution/pipetting.py | 14 ++++++ .../protocol_api/test_instrument_context.py | 2 +- .../execution/test_pipetting_handler.py | 50 +++++++++++++++++++ 8 files changed, 134 insertions(+), 42 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 2b06999fcf6..bd97995d833 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -66,7 +66,7 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: Location, + location: Optional[Location], well_core: Optional[WellCore], volume: float, rate: float, @@ -81,7 +81,7 @@ def aspirate( flow_rate: The flow rate in µL/s to aspirate at. """ if well_core is None: - if location != self._protocol_core.get_last_location(): + if location is not None: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( @@ -96,7 +96,7 @@ def aspirate( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate ) - else: + elif location is not None: well_name = well_core.get_name() labware_id = well_core.labware_id @@ -121,7 +121,7 @@ def aspirate( def dispense( self, - location: Location, + location: Optional[Location], well_core: Optional[WellCore], volume: float, rate: float, @@ -136,7 +136,7 @@ def dispense( flow_rate: The flow rate in µL/s to dispense at. """ if well_core is None: - if location != self._protocol_core.get_last_location(): + if location: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( @@ -150,7 +150,7 @@ def dispense( self._engine_client.dispense_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate ) - else: + elif location: well_name = well_core.get_name() labware_id = well_core.labware_id @@ -171,9 +171,13 @@ def dispense( flow_rate=flow_rate, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) - def blow_out(self, location: Location, well_core: Optional[WellCore]) -> None: + def blow_out( + self, location: Optional[Location], well_core: Optional[WellCore] + ) -> None: """Blow liquid out of the tip. Args: @@ -182,7 +186,7 @@ def blow_out(self, location: Location, well_core: Optional[WellCore]) -> None: """ flow_rate = self.get_absolute_blow_out_flow_rate(1.0) if well_core is None: - if location != self._protocol_core.get_last_location(): + if location: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( @@ -196,7 +200,7 @@ def blow_out(self, location: Location, well_core: Optional[WellCore]) -> None: self._engine_client.blow_out_in_place( pipette_id=self._pipette_id, flow_rate=flow_rate ) - else: + elif location: well_name = well_core.get_name() labware_id = well_core.labware_id @@ -218,7 +222,9 @@ def blow_out(self, location: Location, well_core: Optional[WellCore]) -> None: flow_rate=flow_rate, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) def touch_tip( self, diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index c796c591fc4..216cef9f252 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -24,7 +24,7 @@ def set_default_speed(self, speed: float) -> None: @abstractmethod def aspirate( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[WellCoreType], volume: float, rate: float, @@ -43,7 +43,7 @@ def aspirate( @abstractmethod def dispense( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[WellCoreType], volume: float, rate: float, @@ -62,7 +62,7 @@ def dispense( @abstractmethod def blow_out( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[WellCoreType], ) -> None: """Blow liquid out of the tip. diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 61945c3c762..344962352ff 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -61,7 +61,7 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[LegacyWellCore], volume: float, rate: float, @@ -75,7 +75,7 @@ def aspirate( rate: The rate in µL/s to aspirate at. flow_rate: Not used in this core. """ - if self.get_current_volume() == 0: + if location and self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any # liquid to prepare the pipette for aspiration if self._api_version < APIVersion(2, 3) or not self.is_ready_to_aspirate(): @@ -93,14 +93,14 @@ def aspirate( ) self.prepare_for_aspirate() self.move_to(location=location) - elif location != self._protocol_interface.get_last_location(): + elif location: self.move_to(location=location) self._protocol_interface.get_hardware().aspirate(self._mount, volume, rate) def dispense( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[LegacyWellCore], volume: float, rate: float, @@ -114,13 +114,14 @@ def dispense( rate: The rate in µL/s to dispense at. flow_rate: Not used in this core. """ - self.move_to(location=location) + if location: + self.move_to(location=location) self._protocol_interface.get_hardware().dispense(self._mount, volume, rate) def blow_out( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[LegacyWellCore], ) -> None: """Blow liquid out of the tip. @@ -129,7 +130,7 @@ def blow_out( location: The location to blow out into. well_core: Unused by legacy core. """ - if location != self._protocol_interface.get_last_location(): + if location: self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 33100559f75..1fd5ace24de 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -64,13 +64,13 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[LegacyWellCore], volume: float, rate: float, flow_rate: float, ) -> None: - if self.get_current_volume() == 0: + if location and self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any # liquid to prepare the pipette for aspiration if self._api_version < APIVersion(2, 3) or not self.is_ready_to_aspirate(): @@ -90,7 +90,7 @@ def aspirate( ) self.prepare_for_aspirate() self.move_to(location=location, well_core=well_core) - elif location != self._protocol_interface.get_last_location(): + elif location: self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.ASPIRATE.name) @@ -103,22 +103,23 @@ def aspirate( def dispense( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[LegacyWellCore], volume: float, rate: float, flow_rate: float, ) -> None: - self.move_to(location=location, well_core=well_core) + if location: + self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.DISPENSE.name) self._update_volume(self.get_current_volume() - volume) def blow_out( self, - location: types.Location, + location: Optional[types.Location], well_core: Optional[LegacyWellCore], ) -> None: - if location != self._protocol_interface.get_last_location(): + if location: self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.BLOWOUT.name) self._update_volume(0) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index aeb64df1aa3..6ff4a61339b 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -174,7 +174,14 @@ def aspirate( ) well: Optional[labware.Well] - last_location = self._protocol_core.get_last_location() + move_to_location: Optional[types.Location] + + if self._api_version >= APIVersion(2, 14): + last_location = self._protocol_core.get_last_location( + mount=types.Mount.string_to_mount(self.mount) + ) + else: + last_location = self._protocol_core.get_last_location() if isinstance(location, labware.Well): move_to_location = location.bottom(z=self._well_bottom_clearances.aspirate) @@ -187,8 +194,8 @@ def aspirate( "location should be a Well or Location, but it is {}".format(location) ) elif last_location: - move_to_location = last_location - _, well = move_to_location.labware.get_parent_labware_and_well() + move_to_location = None + _, well = last_location.labware.get_parent_labware_and_well() else: raise RuntimeError( "If aspirate is called without an explicit location, another" @@ -198,7 +205,7 @@ def aspirate( ) if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( - location=move_to_location, + location=move_to_location or last_location, # type: ignore[arg-type] reject_module=self.api_version >= APIVersion(2, 13), ) @@ -210,7 +217,7 @@ def aspirate( command=cmds.aspirate( instrument=self, volume=c_vol, - location=move_to_location, + location=move_to_location or last_location, # type: ignore[arg-type] flow_rate=flow_rate, rate=rate, ), @@ -278,7 +285,12 @@ def dispense( ) ) well: Optional[labware.Well] - last_location = self._protocol_core.get_last_location() + if self._api_version >= APIVersion(2, 14): + last_location = self._protocol_core.get_last_location( + mount=types.Mount.string_to_mount(self.mount) + ) + else: + last_location = self._protocol_core.get_last_location() if isinstance(location, labware.Well): well = location @@ -434,7 +446,13 @@ def blow_out( """ well: Optional[labware.Well] - last_location = self._protocol_core.get_last_location() + move_to_location: Optional[types.Location] + if self._api_version >= APIVersion(2, 14): + last_location = self._protocol_core.get_last_location( + mount=types.Mount.string_to_mount(self.mount) + ) + else: + last_location = self._protocol_core.get_last_location() if isinstance(location, labware.Well): if location.parent.is_tiprack: @@ -442,18 +460,18 @@ def blow_out( "Blow_out being performed on a tiprack. " "Please re-check your code" ) - checked_loc = location.top() + move_to_location = location.top() well = location elif isinstance(location, types.Location): - checked_loc = location + move_to_location = location _, well = location.labware.get_parent_labware_and_well() elif location is not None: raise TypeError( "location should be a Well or Location, but it is {}".format(location) ) elif last_location: - checked_loc = last_location - _, well = checked_loc.labware.get_parent_labware_and_well() + move_to_location = None + _, well = last_location.labware.get_parent_labware_and_well() else: raise RuntimeError( "If blow out is called without an explicit location, another" @@ -464,10 +482,12 @@ def blow_out( with publisher.publish_context( broker=self.broker, - command=cmds.blow_out(instrument=self, location=checked_loc), + command=cmds.blow_out( + instrument=self, location=move_to_location or last_location # type: ignore[arg-type] + ), ): self._core.blow_out( - location=checked_loc, + location=move_to_location, well_core=well._core if well is not None else None, ) diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index c46681d2de5..5e3460ae647 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -255,6 +255,20 @@ async def aspirate_in_place( attached_pipettes=self._hardware_api.attached_instruments, ) + ready_to_aspirate = self._state_store.pipettes.get_is_ready_to_aspirate( + pipette_id=pipette_id, + pipette_config=hw_pipette.config, + ) + + if not ready_to_aspirate: + raise ValueError( + "When aspirate is called on something other than a " + "well relative position, we can't move to the top of" + " the well to prepare for aspiration. This might " + "cause over aspiration if the previous command is a " + "blow_out." + ) + with self.set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate): await self._hardware_api.aspirate(mount=hw_pipette.mount, volume=volume) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index c298cbf7182..37bd38e7b85 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -276,7 +276,7 @@ def test_blow_out_in_place( decoy.verify( mock_instrument_core.blow_out( - location=location, + location=None, well_core=mock_well._core, ), times=1, diff --git a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py index 1ccf78ca0e7..681ba60c88e 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py @@ -467,6 +467,13 @@ async def test_handle_aspirate_in_place_request( ) ) + decoy.when( + state_store.pipettes.get_is_ready_to_aspirate( + pipette_id="pipette-id", + pipette_config=mock_hw_pipettes.right_config, + ) + ).then_return(True) + volume = await subject.aspirate_in_place( pipette_id="pipette-id", volume=25, @@ -486,6 +493,49 @@ async def test_handle_aspirate_in_place_request( ) +async def test_handle_aspirate_in_place_request_not_ready_to_aspirate( + decoy: Decoy, + state_store: StateStore, + hardware_api: HardwareAPI, + movement_handler: MovementHandler, + mock_hw_pipettes: MockPipettes, + subject: PipettingHandler, +) -> None: + """It raise an exception for not ready to aspirate.""" + decoy.when( + state_store.pipettes.get_hardware_pipette( + pipette_id="pipette-id", + attached_pipettes=mock_hw_pipettes.by_mount, + ) + ).then_return( + HardwarePipette( + mount=Mount.RIGHT, + config=mock_hw_pipettes.right_config, + ) + ) + + decoy.when( + state_store.pipettes.get_is_ready_to_aspirate( + pipette_id="pipette-id", + pipette_config=mock_hw_pipettes.right_config, + ) + ).then_return(False) + + with pytest.raises( + ValueError, + match="When aspirate is called on something other than a " + "well relative position, we can't move to the top of" + " the well to prepare for aspiration. This might " + "cause over aspiration if the previous command is a " + "blow_out.", + ): + await subject.aspirate_in_place( + pipette_id="pipette-id", + volume=25, + flow_rate=2.5, + ) + + async def test_handle_add_tip( decoy: Decoy, state_store: StateStore, From f4f7ea83c4277f234e1e7c7681a94cb8e1cbf004 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 9 Feb 2023 15:52:46 -0500 Subject: [PATCH 22/86] set last location --- api/src/opentrons/protocol_api/core/engine/instrument.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index bd97995d833..6a054d6fb3a 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -91,6 +91,7 @@ def aspirate( force_direct=False, speed=None, ) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) self._engine_client.aspirate_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate @@ -117,7 +118,7 @@ def aspirate( flow_rate=flow_rate, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def dispense( self, @@ -146,6 +147,7 @@ def dispense( force_direct=False, speed=None, ) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) self._engine_client.dispense_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate @@ -196,6 +198,7 @@ def blow_out( minimum_z_height=None, speed=None, ) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) self._engine_client.blow_out_in_place( pipette_id=self._pipette_id, flow_rate=flow_rate From 0f26c9dfd6910c17c63cc8f5dacd92a221456cca Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 9 Feb 2023 15:53:38 -0500 Subject: [PATCH 23/86] Update api/src/opentrons/protocol_api/core/engine/instrument.py --- api/src/opentrons/protocol_api/core/engine/instrument.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 6a054d6fb3a..795cb8a08af 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -97,7 +97,7 @@ def aspirate( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate ) - elif location is not None: + elif location: well_name = well_core.get_name() labware_id = well_core.labware_id From 7bc74cf88a8e754a921f27eb9552124359aac72e Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 9 Feb 2023 15:53:56 -0500 Subject: [PATCH 24/86] Update api/src/opentrons/protocol_api/core/engine/instrument.py --- api/src/opentrons/protocol_api/core/engine/instrument.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 795cb8a08af..18916dbbdf7 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -81,7 +81,7 @@ def aspirate( flow_rate: The flow rate in µL/s to aspirate at. """ if well_core is None: - if location is not None: + if location: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( From b98967df6831353c7584ab72aefe216c40521452 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 9 Feb 2023 15:55:51 -0500 Subject: [PATCH 25/86] Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py --- .../core/legacy_simulator/legacy_instrument_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 1fd5ace24de..0b2bfa2f466 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -85,7 +85,7 @@ def aspirate( "When aspirate is called on something other than a " "well relative position, we can't move to the top of" " the well to prepare for aspiration. This might " - "cause overz aspiration if the previous command is a " + "cause over aspiration if the previous command is a " "blow_out." ) self.prepare_for_aspirate() From 949ca7e7e434a1c957576b93cfb849028b744a18 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 9 Feb 2023 15:59:52 -0500 Subject: [PATCH 26/86] Update api/src/opentrons/protocol_engine/execution/pipetting.py --- api/src/opentrons/protocol_engine/execution/pipetting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index 5e3460ae647..9b980315941 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -261,7 +261,7 @@ async def aspirate_in_place( ) if not ready_to_aspirate: - raise ValueError( + raise RuntimeError( "When aspirate is called on something other than a " "well relative position, we can't move to the top of" " the well to prepare for aspiration. This might " From c10ea2720bf291fc634be468c6ee6234840934a1 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 9 Feb 2023 16:01:38 -0500 Subject: [PATCH 27/86] Update api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py --- .../protocol_engine/execution/test_pipetting_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py index 681ba60c88e..ebf5d623e30 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py @@ -522,7 +522,7 @@ async def test_handle_aspirate_in_place_request_not_ready_to_aspirate( ).then_return(False) with pytest.raises( - ValueError, + RuntimeError, match="When aspirate is called on something other than a " "well relative position, we can't move to the top of" " the well to prepare for aspiration. This might " From 94bbbe38165094de16a392879a244c293d4aa3d5 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 10 Feb 2023 14:48:38 -0500 Subject: [PATCH 28/86] Update api/src/opentrons/protocol_engine/execution/pipetting.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_engine/execution/pipetting.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index 9b980315941..df4c4e3fa32 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -262,11 +262,10 @@ async def aspirate_in_place( if not ready_to_aspirate: raise RuntimeError( - "When aspirate is called on something other than a " - "well relative position, we can't move to the top of" - " the well to prepare for aspiration. This might " - "cause over aspiration if the previous command is a " - "blow_out." + "Pipette cannot aspirate in place because of a previous blow out." + " The first aspirate following a blow-out must be from a specific well" + " so the plunger can be reset in a known safe position." + ) with self.set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate): From 0c91bb7a1b6b89cea4326d19c292ba66d4fc0bc3 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 10 Feb 2023 14:48:46 -0500 Subject: [PATCH 29/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 6ff4a61339b..02b918dd751 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -178,7 +178,7 @@ def aspirate( if self._api_version >= APIVersion(2, 14): last_location = self._protocol_core.get_last_location( - mount=types.Mount.string_to_mount(self.mount) + mount=self._core.get_mount() ) else: last_location = self._protocol_core.get_last_location() From c24f9f1ae232d6b6eaa425fb943f3d675704d829 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 9 Feb 2023 16:33:25 -0500 Subject: [PATCH 30/86] linting --- .../protocol_api/core/engine/instrument.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 18916dbbdf7..5391c2bef88 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -91,7 +91,9 @@ def aspirate( force_direct=False, speed=None, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) self._engine_client.aspirate_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate @@ -118,7 +120,9 @@ def aspirate( flow_rate=flow_rate, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) def dispense( self, @@ -147,7 +151,9 @@ def dispense( force_direct=False, speed=None, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) self._engine_client.dispense_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate @@ -198,7 +204,9 @@ def blow_out( minimum_z_height=None, speed=None, ) - self._protocol_core.set_last_location(location=location, mount=self.get_mount()) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) self._engine_client.blow_out_in_place( pipette_id=self._pipette_id, flow_rate=flow_rate From e6d1c9ed45288c465061f5237c3e29d2c805f9c9 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 10 Feb 2023 16:51:26 -0500 Subject: [PATCH 31/86] moved aspirate_in_place logic into command impl, pr fixes --- .../protocol_api/instrument_context.py | 28 ++-- .../commands/aspirate_in_place.py | 41 +++++- .../protocol_engine/errors/exceptions.py | 4 + .../protocol_engine/execution/pipetting.py | 30 ---- .../commands/test_aspirate_in_place.py | 133 ++++++++++++++++-- .../execution/test_pipetting_handler.py | 90 ------------ 6 files changed, 174 insertions(+), 152 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 02b918dd751..cb3f0ed65e5 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -23,6 +23,7 @@ ) from .core.common import InstrumentCore, ProtocolCore +from .core.engine import ENGINE_CORE_API_VERSION from .config import Clearances from . import labware @@ -176,12 +177,7 @@ def aspirate( well: Optional[labware.Well] move_to_location: Optional[types.Location] - if self._api_version >= APIVersion(2, 14): - last_location = self._protocol_core.get_last_location( - mount=self._core.get_mount() - ) - else: - last_location = self._protocol_core.get_last_location() + last_location = self._get_last_location_by_api_version() if isinstance(location, labware.Well): move_to_location = location.bottom(z=self._well_bottom_clearances.aspirate) @@ -285,12 +281,7 @@ def dispense( ) ) well: Optional[labware.Well] - if self._api_version >= APIVersion(2, 14): - last_location = self._protocol_core.get_last_location( - mount=types.Mount.string_to_mount(self.mount) - ) - else: - last_location = self._protocol_core.get_last_location() + last_location = self._get_last_location_by_api_version() if isinstance(location, labware.Well): well = location @@ -447,12 +438,7 @@ def blow_out( well: Optional[labware.Well] move_to_location: Optional[types.Location] - if self._api_version >= APIVersion(2, 14): - last_location = self._protocol_core.get_last_location( - mount=types.Mount.string_to_mount(self.mount) - ) - else: - last_location = self._protocol_core.get_last_location() + last_location = self._get_last_location_by_api_version() if isinstance(location, labware.Well): if location.parent.is_tiprack: @@ -1522,6 +1508,12 @@ def well_bottom_clearance(self) -> "Clearances": """ return self._well_bottom_clearances + def _get_last_location_by_api_version(self) -> Optional[types.Location]: + if self._api_version >= ENGINE_CORE_API_VERSION: + return self._protocol_core.get_last_location(mount=self._core.get_mount()) + else: + return self._protocol_core.get_last_location() + def __repr__(self) -> str: return "<{}: {} in {}>".format( self.__class__.__name__, diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index 578faff2b09..d372c19a5fb 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from opentrons.hardware_control import HardwareControlAPI + from .pipetting_common import ( PipetteIdMixin, VolumeMixin, @@ -15,9 +17,11 @@ BaseLiquidHandlingResult, ) from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate +from ..errors.exceptions import PipetteNotReadyToAspirateError if TYPE_CHECKING: from ..execution import PipettingHandler + from ..state import StateView AspirateInPlaceCommandType = Literal["aspirateInPlace"] @@ -40,17 +44,44 @@ class AspirateInPlaceImplementation( ): """AspirateInPlace command implementation.""" - def __init__(self, pipetting: PipettingHandler, **kwargs: object) -> None: + def __init__( + self, + pipetting: PipettingHandler, + hardware_api: HardwareControlAPI, + state_view: StateView, + **kwargs: object, + ) -> None: self._pipetting = pipetting + self._state_view = state_view + self._hardware_api = hardware_api async def execute(self, params: AspirateInPlaceParams) -> AspirateInPlaceResult: """Aspirate without moving the pipette.""" - volume = await self._pipetting.aspirate_in_place( + hw_pipette = self._state_view.pipettes.get_hardware_pipette( pipette_id=params.pipetteId, - volume=params.volume, - flow_rate=params.flowRate, + attached_pipettes=self._hardware_api.attached_instruments, ) - return AspirateInPlaceResult(volume=volume) + + ready_to_aspirate = self._state_view.pipettes.get_is_ready_to_aspirate( + pipette_id=params.pipetteId, + pipette_config=hw_pipette.config, + ) + + if not ready_to_aspirate: + raise PipetteNotReadyToAspirateError( + "Pipette cannot aspirate in place because of a previous blow out." + " The first aspirate following a blow-out must be from a specific well" + " so the plunger can be reset in a known safe position." + ) + + with self._pipetting.set_flow_rate( + pipette=hw_pipette, aspirate_flow_rate=params.flowRate + ): + await self._hardware_api.aspirate( + mount=hw_pipette.mount, volume=params.volume + ) + + return AspirateInPlaceResult(volume=params.volume) class AspirateInPlace(BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult]): diff --git a/api/src/opentrons/protocol_engine/errors/exceptions.py b/api/src/opentrons/protocol_engine/errors/exceptions.py index 3a829ae5c44..d0e5d414bbe 100644 --- a/api/src/opentrons/protocol_engine/errors/exceptions.py +++ b/api/src/opentrons/protocol_engine/errors/exceptions.py @@ -207,3 +207,7 @@ class LocationIsOccupiedError(ProtocolEngineError): class FirmwareUpdateRequired(ProtocolEngineError): """An error raised when the firmware needs to be updated.""" + + +class PipetteNotReadyToAspirateError(ProtocolEngineError): + """An error raised when the pipette is not ready to aspirate.""" diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index 3766156cc05..8893482b3f5 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -247,36 +247,6 @@ async def dispense_in_place( return volume - async def aspirate_in_place( - self, - pipette_id: str, - volume: float, - flow_rate: float, - ) -> float: - """Aspirate liquid without moving the pipette.""" - hw_pipette = self._state_store.pipettes.get_hardware_pipette( - pipette_id=pipette_id, - attached_pipettes=self._hardware_api.attached_instruments, - ) - - ready_to_aspirate = self._state_store.pipettes.get_is_ready_to_aspirate( - pipette_id=pipette_id, - pipette_config=hw_pipette.config, - ) - - if not ready_to_aspirate: - raise RuntimeError( - "Pipette cannot aspirate in place because of a previous blow out." - " The first aspirate following a blow-out must be from a specific well" - " so the plunger can be reset in a known safe position." - - ) - - with self.set_flow_rate(pipette=hw_pipette, dispense_flow_rate=flow_rate): - await self._hardware_api.aspirate(mount=hw_pipette.mount, volume=volume) - - return volume - async def touch_tip( self, pipette_id: str, diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py index c052e844ce2..7aebea013d8 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py @@ -1,36 +1,151 @@ """Test aspirate-in-place commands.""" +import pytest from decoy import Decoy +from typing import cast -from opentrons.protocol_engine.execution import PipettingHandler +from opentrons.types import Mount +from opentrons.hardware_control import API as HardwareAPI +from opentrons.hardware_control.dev_types import PipetteDict +from opentrons.protocol_engine.execution import PipettingHandler from opentrons.protocol_engine.commands.aspirate_in_place import ( AspirateInPlaceParams, AspirateInPlaceResult, AspirateInPlaceImplementation, ) +from opentrons.protocol_engine.errors.exceptions import PipetteNotReadyToAspirateError + +from opentrons.protocol_engine.state import ( + StateStore, + HardwarePipette, +) + + +@pytest.fixture +def hardware_api(decoy: Decoy) -> HardwareAPI: + """Get a mock in the shape of a HardwareAPI.""" + return decoy.mock(cls=HardwareAPI) + + +@pytest.fixture +def state_store(decoy: Decoy) -> StateStore: + """Get a mock in the shape of a StateStore.""" + return decoy.mock(cls=StateStore) + + +@pytest.fixture +def pipetting(decoy: Decoy) -> PipettingHandler: + """Get a mock in the shape of a PipettingHandler.""" + return decoy.mock(cls=PipettingHandler) async def test_aspirate_in_place_implementation( decoy: Decoy, pipetting: PipettingHandler, + state_store: StateStore, + hardware_api: HardwareAPI, ) -> None: """It should aspirate in place.""" - subject = AspirateInPlaceImplementation(pipetting=pipetting) + subject = AspirateInPlaceImplementation( + pipetting=pipetting, + hardware_api=hardware_api, + state_view=state_store, + ) + + data = AspirateInPlaceParams( + pipetteId="pipette-id-abc", + volume=123, + flowRate=1.234, + ) + + left_config = cast(PipetteDict, {"name": "p300_single", "pipette_id": "123"}) + right_config = cast(PipetteDict, {"name": "p300_multi", "pipette_id": "abc"}) + + pipette_dict_by_mount = {Mount.LEFT: left_config, Mount.RIGHT: right_config} + + hw_pipette_result = HardwarePipette( + mount=Mount.RIGHT, + config=right_config, + ) + + decoy.when(hardware_api.attached_instruments).then_return(pipette_dict_by_mount) + + decoy.when( + state_store.pipettes.get_hardware_pipette( + pipette_id="pipette-id-abc", + attached_pipettes=pipette_dict_by_mount, + ) + ).then_return(hw_pipette_result) + + decoy.when( + state_store.pipettes.get_is_ready_to_aspirate( + pipette_id="pipette-id-abc", + pipette_config=hw_pipette_result.config, + ) + ).then_return(True) + + mock_flow_rate_context = decoy.mock(name="mock flow rate context") + decoy.when( + pipetting.set_flow_rate( + pipette=hw_pipette_result, + aspirate_flow_rate=1.234, + ) + ).then_return(mock_flow_rate_context) + + result = await subject.execute(params=data) + + assert result == AspirateInPlaceResult(volume=123) + + +async def test_handle_aspirate_in_place_request_not_ready_to_aspirate( + decoy: Decoy, + pipetting: PipettingHandler, + state_store: StateStore, + hardware_api: HardwareAPI, +) -> None: + """Should raise an exception for not ready to aspirate.""" + subject = AspirateInPlaceImplementation( + pipetting=pipetting, + hardware_api=hardware_api, + state_view=state_store, + ) data = AspirateInPlaceParams( pipetteId="pipette-id-abc", volume=123, - flowRate=456, + flowRate=1.234, + ) + + left_config = cast(PipetteDict, {"name": "p300_single", "pipette_id": "123"}) + right_config = cast(PipetteDict, {"name": "p300_multi", "pipette_id": "abc"}) + + pipette_dict_by_mount = {Mount.LEFT: left_config, Mount.RIGHT: right_config} + + hw_pipette_result = HardwarePipette( + mount=Mount.RIGHT, + config=right_config, ) + decoy.when(hardware_api.attached_instruments).then_return(pipette_dict_by_mount) + decoy.when( - await pipetting.aspirate_in_place( + state_store.pipettes.get_hardware_pipette( pipette_id="pipette-id-abc", - volume=123, - flow_rate=456, + attached_pipettes=pipette_dict_by_mount, ) - ).then_return(42) + ).then_return(hw_pipette_result) - result = await subject.execute(data) + decoy.when( + state_store.pipettes.get_is_ready_to_aspirate( + pipette_id="pipette-id-abc", + pipette_config=hw_pipette_result.config, + ) + ).then_return(False) - assert result == AspirateInPlaceResult(volume=42) + with pytest.raises( + PipetteNotReadyToAspirateError, + match="Pipette cannot aspirate in place because of a previous blow out." + " The first aspirate following a blow-out must be from a specific well" + " so the plunger can be reset in a known safe position.", + ): + await subject.execute(params=data) diff --git a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py index ebf5d623e30..0fe74ff6b2a 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py @@ -446,96 +446,6 @@ async def test_handle_dispense_in_place_request( ) -async def test_handle_aspirate_in_place_request( - decoy: Decoy, - state_store: StateStore, - hardware_api: HardwareAPI, - movement_handler: MovementHandler, - mock_hw_pipettes: MockPipettes, - subject: PipettingHandler, -) -> None: - """It should find the pipette by ID and use it to aspirate.""" - decoy.when( - state_store.pipettes.get_hardware_pipette( - pipette_id="pipette-id", - attached_pipettes=mock_hw_pipettes.by_mount, - ) - ).then_return( - HardwarePipette( - mount=Mount.RIGHT, - config=mock_hw_pipettes.right_config, - ) - ) - - decoy.when( - state_store.pipettes.get_is_ready_to_aspirate( - pipette_id="pipette-id", - pipette_config=mock_hw_pipettes.right_config, - ) - ).then_return(True) - - volume = await subject.aspirate_in_place( - pipette_id="pipette-id", - volume=25, - flow_rate=2.5, - ) - - assert volume == 25 - - decoy.verify( - hardware_api.set_flow_rate( - mount=Mount.RIGHT, aspirate=None, dispense=2.5, blow_out=None - ), - await hardware_api.aspirate(mount=Mount.RIGHT, volume=25), - hardware_api.set_flow_rate( - mount=Mount.RIGHT, aspirate=1.23, dispense=1.23, blow_out=1.23 - ), - ) - - -async def test_handle_aspirate_in_place_request_not_ready_to_aspirate( - decoy: Decoy, - state_store: StateStore, - hardware_api: HardwareAPI, - movement_handler: MovementHandler, - mock_hw_pipettes: MockPipettes, - subject: PipettingHandler, -) -> None: - """It raise an exception for not ready to aspirate.""" - decoy.when( - state_store.pipettes.get_hardware_pipette( - pipette_id="pipette-id", - attached_pipettes=mock_hw_pipettes.by_mount, - ) - ).then_return( - HardwarePipette( - mount=Mount.RIGHT, - config=mock_hw_pipettes.right_config, - ) - ) - - decoy.when( - state_store.pipettes.get_is_ready_to_aspirate( - pipette_id="pipette-id", - pipette_config=mock_hw_pipettes.right_config, - ) - ).then_return(False) - - with pytest.raises( - RuntimeError, - match="When aspirate is called on something other than a " - "well relative position, we can't move to the top of" - " the well to prepare for aspiration. This might " - "cause over aspiration if the previous command is a " - "blow_out.", - ): - await subject.aspirate_in_place( - pipette_id="pipette-id", - volume=25, - flow_rate=2.5, - ) - - async def test_handle_add_tip( decoy: Decoy, state_store: StateStore, From e3c5437eac15aa5c7c40d2d2faa48cc8cb98d574 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Mon, 13 Feb 2023 12:23:33 -0500 Subject: [PATCH 32/86] fixed failing tests --- .../protocol_api/core/legacy/legacy_instrument_core.py | 7 ++++--- .../protocol_api/core/engine/test_instrument_core.py | 2 +- .../opentrons/protocol_api/test_instrument_context.py | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 344962352ff..6e1749a0493 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -75,11 +75,11 @@ def aspirate( rate: The rate in µL/s to aspirate at. flow_rate: Not used in this core. """ - if location and self.get_current_volume() == 0: + if self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any # liquid to prepare the pipette for aspiration if self._api_version < APIVersion(2, 3) or not self.is_ready_to_aspirate(): - if location.labware.is_well: + if location and location.labware.is_well: self.move_to(location=location.labware.as_well().top()) else: # TODO(seth,2019/7/29): This should be a warning exposed @@ -92,7 +92,8 @@ def aspirate( "blow_out." ) self.prepare_for_aspirate() - self.move_to(location=location) + if location: + self.move_to(location=location) elif location: self.move_to(location=location) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 65090392447..3bf915cfa0e 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -389,11 +389,11 @@ def test_blow_out_in_place( speed=None, force_direct=False, ), + mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), mock_engine_client.blow_out_in_place( pipette_id="abc123", flow_rate=1.23, ), - mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), ) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index ab3afcb53bb..918a84fa4db 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -283,7 +283,7 @@ def test_blow_out_in_place( mock_well = decoy.mock(cls=Well) location = Location(point=Point(1, 2, 3), labware=mock_well) - decoy.when(mock_protocol_core.get_last_location()).then_return(location) + decoy.when(mock_protocol_core.get_last_location(mount=Mount.LEFT)).then_return(location) subject.blow_out() @@ -302,7 +302,7 @@ def test_blow_out_no_location_cache_raises( subject: InstrumentContext, ) -> None: """It should raise if no location or well is provided and the location cache returns None.""" - decoy.when(mock_protocol_core.get_last_location()).then_return(None) + decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return(None) with pytest.raises(RuntimeError): subject.blow_out() @@ -555,7 +555,7 @@ def test_dispense_with_no_location( mock_protocol_core: ProtocolCore, ) -> None: """It should dispense to a well.""" - decoy.when(mock_protocol_core.get_last_location()).then_return( + decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return( Location(point=Point(1, 2, 3), labware=None) ) From 993469f957325a04e31e81b6a8a8c7ed94bf2f35 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Mon, 13 Feb 2023 12:27:28 -0500 Subject: [PATCH 33/86] formatting --- api/tests/opentrons/protocol_api/test_instrument_context.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 918a84fa4db..315cefdd5a9 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -283,7 +283,9 @@ def test_blow_out_in_place( mock_well = decoy.mock(cls=Well) location = Location(point=Point(1, 2, 3), labware=mock_well) - decoy.when(mock_protocol_core.get_last_location(mount=Mount.LEFT)).then_return(location) + decoy.when(mock_protocol_core.get_last_location(mount=Mount.LEFT)).then_return( + location + ) subject.blow_out() From 058c1ac0b2f46f3601cae593328eae9f99dc8e15 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 14 Feb 2023 14:49:05 -0500 Subject: [PATCH 34/86] WIP refactor instrument_context.py --- .../protocol_api/core/engine/instrument.py | 3 +- .../opentrons/protocol_api/core/instrument.py | 3 +- .../core/legacy/legacy_instrument_core.py | 3 +- .../legacy_instrument_core.py | 3 +- .../protocol_api/instrument_context.py | 54 ++++--- api/src/opentrons/protocol_api/validation.py | 62 +++++++- .../protocol_api/test_instrument_context.py | 137 +++++++++++++++++- .../opentrons/protocol_api/test_validation.py | 59 +++++++- 8 files changed, 297 insertions(+), 27 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 671f442bef6..2a6cbe816de 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -66,11 +66,12 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: Optional[Location], + location: Location, well_core: Optional[WellCore], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 216cef9f252..de76fcbb59f 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -24,11 +24,12 @@ def set_default_speed(self, speed: float) -> None: @abstractmethod def aspirate( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[WellCoreType], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 6e1749a0493..0b7a83e53bb 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -61,11 +61,12 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[LegacyWellCore], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 0b2bfa2f466..e98d00cc62c 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -64,11 +64,12 @@ def set_default_speed(self, speed: float) -> None: def aspirate( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[LegacyWellCore], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: if location and self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index cb3f0ed65e5..4dd30aa0cce 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -25,7 +25,7 @@ from .core.common import InstrumentCore, ProtocolCore from .core.engine import ENGINE_CORE_API_VERSION from .config import Clearances -from . import labware +from . import labware, validation AdvancedLiquidHandling = Union[ @@ -175,33 +175,52 @@ def aspirate( ) well: Optional[labware.Well] - move_to_location: Optional[types.Location] + move_to_location: types.Location + aspirate_in_place: bool = False last_location = self._get_last_location_by_api_version() - - if isinstance(location, labware.Well): - move_to_location = location.bottom(z=self._well_bottom_clearances.aspirate) - well = location - elif isinstance(location, types.Location): - move_to_location = location - _, well = move_to_location.labware.get_parent_labware_and_well() - elif location is not None: - raise TypeError( - "location should be a Well or Location, but it is {}".format(location) + try: + target = validation.validate_location( + location=location, last_location=last_location ) - elif last_location: - move_to_location = None - _, well = last_location.labware.get_parent_labware_and_well() - else: + except validation.NoLocationError: raise RuntimeError( "If aspirate is called without an explicit location, another" " method that moves to a location (such as move_to or " "dispense) must previously have been called so the robot " "knows where it is." ) + except validation.LocationTypeError: + raise TypeError( + "location should be a Well or Location, but it is {}".format(location) + ) + + if isinstance(target, validation.WellTarget): + move_to_location = target.location or target.well.bottom( + z=self._well_bottom_clearances.aspirate + ) + well = target.well + if isinstance(target, validation.PointTarget): + move_to_location = target.location + _, well = target.location.labware.get_parent_labware_and_well() + aspirate_in_place = target.in_place + # elif location is not None: + # raise TypeError( + # "location should be a Well or Location, but it is {}".format(location) + # ) + # elif last_location: + # move_to_location = last_location + # _, well = last_location.labware.get_parent_labware_and_well() + # else: + # raise RuntimeError( + # "If aspirate is called without an explicit location, another" + # " method that moves to a location (such as move_to or " + # "dispense) must previously have been called so the robot " + # "knows where it is." + # ) if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( - location=move_to_location or last_location, # type: ignore[arg-type] + location=move_to_location, reject_module=self.api_version >= APIVersion(2, 13), ) @@ -224,6 +243,7 @@ def aspirate( volume=c_vol, rate=rate, flow_rate=flow_rate, + in_place=aspirate_in_place, ) return self diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index d3b66b0d627..643b2a366b1 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -1,9 +1,21 @@ -from typing import Any, Dict, List, Optional, Sequence, Union, Tuple, Mapping +from __future__ import annotations +from typing import ( + Any, + Dict, + List, + Optional, + Sequence, + Union, + Tuple, + Mapping, + NamedTuple, + TYPE_CHECKING, +) from typing_extensions import TypeGuard from opentrons_shared_data.pipette.dev_types import PipetteNameType -from opentrons.types import Mount, DeckSlotName +from opentrons.types import Mount, DeckSlotName, Location from opentrons.hardware_control.modules.types import ( ModuleModel, MagneticModuleModel, @@ -13,6 +25,9 @@ ThermocyclerStep, ) +if TYPE_CHECKING: + from .labware import Well + def ensure_mount(mount: Union[str, Mount]) -> Mount: """Ensure that an input value represents a valid Mount.""" @@ -180,3 +195,46 @@ def ensure_valid_labware_offset_vector( if not all(isinstance(v, (float, int)) for v in offsets): raise TypeError("Offset values should be a number (int or float).") return offsets + + +class WellTarget(NamedTuple): + """A movement target that is a well.""" + + well: Well + location: Optional[Location] + + +class PointTarget(NamedTuple): + """A movement to coordinates""" + + location: Location + in_place: bool = False + + +class NoLocationError(ValueError): + """""" + + +class LocationTypeError(TypeError): + """""" + + +def validate_location( + location: Union[Location, Well, None], last_location: Optional[Location] +) -> Union[WellTarget, PointTarget]: + """ + Raises: NoLocationError, LocationTypeError + """ + from .labware import Well + + if location is None and last_location is None: + raise NoLocationError() + + if isinstance(location, Well): + return WellTarget(well=location, location=None) + elif isinstance(location, Location): + return WellTarget(well=location.labware.as_well(), location=location) + elif last_location and location is None: + return WellTarget(well=last_location.labware.as_well(), location=last_location) + + raise LocationTypeError() diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 315cefdd5a9..8e9eb11219f 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -16,11 +16,19 @@ Labware, Well, labware, + validation as mock_validation, ) +from opentrons.protocol_api.validation import WellTarget, PointTarget from opentrons.protocol_api.core.common import InstrumentCore, ProtocolCore from opentrons.types import Location, Mount, Point +@pytest.fixture(autouse=True) +def _mock_validation_module(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: + for name, func in inspect.getmembers(mock_validation, inspect.isfunction): + monkeypatch.setattr(mock_validation, name, decoy.mock(func=func)) + + @pytest.fixture(autouse=True) def _mock_instrument_support_module( decoy: Decoy, monkeypatch: pytest.MonkeyPatch @@ -206,23 +214,76 @@ def test_pick_up_from_well_deprecated_args( def test_aspirate( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: """It should aspirate to a well.""" mock_well = decoy.mock(cls=Well) bottom_location = Location(point=Point(1, 2, 3), labware=mock_well) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=None)) decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) decoy.when(mock_instrument_core.get_absolute_aspirate_flow_rate(1.23)).then_return( 5.67 ) - subject.aspirate(volume=42.0, location=mock_well, rate=1.23) + subject.aspirate(volume=42.0, location=input_location, rate=1.23) decoy.verify( mock_instrument_core.aspirate( location=bottom_location, well_core=mock_well._core, + in_place=False, + volume=42.0, + rate=1.23, + flow_rate=5.67, + ), + times=1, + ) + + +def test_aspirate_from_coordinates( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + """It should aspirate from given coordinates.""" + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(PointTarget(location=input_location, in_place=True)) + decoy.when(mock_instrument_core.get_absolute_aspirate_flow_rate(1.23)).then_return( + 5.67 + ) + + subject.aspirate(volume=42.0, location=input_location, rate=1.23) + + decoy.verify( + mock_instrument_core.aspirate( + location=input_location, + well_core=None, + in_place=True, volume=42.0, rate=1.23, flow_rate=5.67, @@ -231,6 +292,78 @@ def test_aspirate( ) +def test_aspirate_in_place( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + """It should aspirate in place.""" + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(PointTarget(location=input_location)) + decoy.when(mock_instrument_core.get_absolute_aspirate_flow_rate(1.23)).then_return( + 5.67 + ) + + subject.aspirate(volume=42.0, location=input_location, rate=1.23) + + decoy.verify( + mock_instrument_core.aspirate( + location=input_location, + well_core=None, + in_place=False, + volume=42.0, + rate=1.23, + flow_rate=5.67, + ), + times=1, + ) + + +def test_aspirate_raises_no_location( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) + + decoy.when( + mock_validation.validate_location(location=None, last_location=None) + ).then_raise(mock_validation.NoLocationError()) + with pytest.raises(RuntimeError): + subject.aspirate(location=None) + + +def test_aspirate_raises_wrong_location_value( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) + + decoy.when( + mock_validation.validate_location(location=None, last_location=None) + ).then_raise(mock_validation.LocationTypeError()) + with pytest.raises(TypeError): + subject.aspirate(location=None) + + def test_blow_out_to_well( decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext ) -> None: diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index b53315aea80..ca28d5314c7 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -1,10 +1,11 @@ """Tests for Protocol API input validation.""" from typing import List, Union, Optional, Dict +from decoy import Decoy import pytest from opentrons_shared_data.pipette.dev_types import PipetteNameType -from opentrons.types import Mount, DeckSlotName +from opentrons.types import Mount, DeckSlotName, Location, Point from opentrons.hardware_control.modules.types import ( ModuleModel, MagneticModuleModel, @@ -13,7 +14,7 @@ HeaterShakerModuleModel, ThermocyclerStep, ) -from opentrons.protocol_api import validation as subject +from opentrons.protocol_api import validation as subject, Well @pytest.mark.parametrize( @@ -250,3 +251,57 @@ def test_ensure_valid_labware_offset_vector(offset: Dict[str, float]) -> None: ) with pytest.raises(TypeError): subject.ensure_valid_labware_offset_vector(offset) + + +def test_validate_well_no_location(decoy: Decoy) -> None: + """Should return a WellTarget with no location.""" + input_location = decoy.mock(cls=Well) + expected_result = subject.WellTarget(well=input_location, location=None) + + result = subject.validate_location(location=input_location, last_location=None) + + assert result == expected_result + + +def test_validate_location_with_well(decoy: Decoy) -> None: + """Should return a WellTarget with location.""" + mock_well = decoy.mock(cls=Well) + input_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) + expected_result = subject.WellTarget(well=mock_well, location=input_location) + + result = subject.validate_location(location=input_location, last_location=None) + + assert result == expected_result + + +def test_validate_last_location(decoy: Decoy) -> None: + """Should return a WellTarget with location.""" + mock_well = decoy.mock(cls=Well) + input_last_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) + expected_result = subject.WellTarget(well=mock_well, location=input_last_location) + + result = subject.validate_location(location=None, last_location=input_last_location) + + assert result == expected_result + + +def test_validate_with_wrong_location_with_last_location() -> None: + + with pytest.raises(subject.LocationTypeError): + subject.validate_location( + location=42, # type: ignore[arg-type] + last_location=Location(point=Point(x=1, y=1, z=1), labware=None), + ) + + +def test_validate_with_wrong_location() -> None: + + with pytest.raises(subject.LocationTypeError): + subject.validate_location( + location=42, last_location=None # type: ignore[arg-type] + ) + + +def test_validate_raises_no_location_error() -> None: + with pytest.raises(subject.NoLocationError): + subject.validate_location(location=None, last_location=None) From 4221d68bfb25b6d7cf1c8ba3a90e0f9824114c67 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 14 Feb 2023 15:23:13 -0500 Subject: [PATCH 35/86] reverted changs for the legacy core and added logic for PointTarget --- .../core/legacy/legacy_instrument_core.py | 8 ++++---- .../protocol_api/instrument_context.py | 15 +------------- api/src/opentrons/protocol_api/validation.py | 5 +++++ .../opentrons/protocol_api/test_validation.py | 20 +++++++++++++++++++ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 0b7a83e53bb..a4732faa52d 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -75,12 +75,13 @@ def aspirate( well_core: The well to aspirate from, if applicable. rate: The rate in µL/s to aspirate at. flow_rate: Not used in this core. + in_place: Used only for the engine core. """ if self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any # liquid to prepare the pipette for aspiration if self._api_version < APIVersion(2, 3) or not self.is_ready_to_aspirate(): - if location and location.labware.is_well: + if location.labware.is_well: self.move_to(location=location.labware.as_well().top()) else: # TODO(seth,2019/7/29): This should be a warning exposed @@ -93,9 +94,8 @@ def aspirate( "blow_out." ) self.prepare_for_aspirate() - if location: - self.move_to(location=location) - elif location: + self.move_to(location=location) + elif location != self._protocol_interface.get_last_location(): self.move_to(location=location) self._protocol_interface.get_hardware().aspirate(self._mount, volume, rate) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 4dd30aa0cce..ed44028fb22 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -204,20 +204,7 @@ def aspirate( move_to_location = target.location _, well = target.location.labware.get_parent_labware_and_well() aspirate_in_place = target.in_place - # elif location is not None: - # raise TypeError( - # "location should be a Well or Location, but it is {}".format(location) - # ) - # elif last_location: - # move_to_location = last_location - # _, well = last_location.labware.get_parent_labware_and_well() - # else: - # raise RuntimeError( - # "If aspirate is called without an explicit location, another" - # " method that moves to a location (such as move_to or " - # "dispense) must previously have been called so the robot " - # "knows where it is." - # ) + if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( location=move_to_location, diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 643b2a366b1..37c53ebaf5f 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -11,6 +11,7 @@ NamedTuple, TYPE_CHECKING, ) + from typing_extensions import TypeGuard from opentrons_shared_data.pipette.dev_types import PipetteNameType @@ -233,8 +234,12 @@ def validate_location( if isinstance(location, Well): return WellTarget(well=location, location=None) elif isinstance(location, Location): + if location.labware is None: + return PointTarget(location=location, in_place=False) return WellTarget(well=location.labware.as_well(), location=location) elif last_location and location is None: + if last_location.labware is None: + return PointTarget(location=last_location, in_place=True) return WellTarget(well=last_location.labware.as_well(), location=last_location) raise LocationTypeError() diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index ca28d5314c7..e6998ddfe23 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -263,6 +263,26 @@ def test_validate_well_no_location(decoy: Decoy) -> None: assert result == expected_result +def test_validate_coordinates(decoy: Decoy) -> None: + """Should return a WellTarget with no location.""" + input_location = Location(point=Point(x=1, y=1, z=2), labware=None) + expected_result = subject.PointTarget(location=input_location, in_place=False) + + result = subject.validate_location(location=input_location, last_location=None) + + assert result == expected_result + + +def test_validate_in_place(decoy: Decoy) -> None: + """Should return a WellTarget with no location.""" + input_last_location = Location(point=Point(x=1, y=1, z=2), labware=None) + expected_result = subject.PointTarget(location=input_last_location, in_place=True) + + result = subject.validate_location(location=input_last_location, last_location=None) + + assert result == expected_result + + def test_validate_location_with_well(decoy: Decoy) -> None: """Should return a WellTarget with location.""" mock_well = decoy.mock(cls=Well) From bc0e58474446594c4e793f23fc2a1c66a786adfd Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 14 Feb 2023 15:30:12 -0500 Subject: [PATCH 36/86] fixed engine core --- .../protocol_api/core/engine/instrument.py | 13 ++++----- .../core/engine/test_instrument_core.py | 28 ++++++++++++++++--- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 2a6cbe816de..582145f111b 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -82,7 +82,7 @@ def aspirate( flow_rate: The flow rate in µL/s to aspirate at. """ if well_core is None: - if location: + if not in_place: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( @@ -92,15 +92,12 @@ def aspirate( force_direct=False, speed=None, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) self._engine_client.aspirate_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate ) - elif location: + else: well_name = well_core.get_name() labware_id = well_core.labware_id @@ -121,9 +118,9 @@ def aspirate( flow_rate=flow_rate, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) + self._protocol_core.set_last_location( + location=location, mount=self.get_mount() + ) def dispense( self, diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 3bf915cfa0e..a808031b45d 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -306,17 +306,16 @@ def test_aspirate_from_well( mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), ) - -def test_aspirate_in_place( +def test_aspirate_from_location( decoy: Decoy, mock_engine_client: EngineClient, mock_protocol_core: ProtocolCore, subject: InstrumentCore, ) -> None: - """It should aspirate in place.""" + """It should aspirate from coordinates.""" location = Location(point=Point(1, 2, 3), labware=None) subject.aspirate( - volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location + volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location, in_place=False ) decoy.verify( @@ -335,6 +334,27 @@ def test_aspirate_in_place( ) +def test_aspirate_in_place( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, +) -> None: + """It should aspirate in place.""" + location = Location(point=Point(1, 2, 3), labware=None) + subject.aspirate( + volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location, in_place=True + ) + + decoy.verify( + mock_engine_client.aspirate_in_place( + pipette_id="abc123", + volume=12.34, + flow_rate=7.8, + ), + ) + + def test_blow_out_to_well( decoy: Decoy, mock_engine_client: EngineClient, From efc16cc2ac7335ec31cf2d0b562f4292b28012aa Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Tue, 14 Feb 2023 16:48:32 -0500 Subject: [PATCH 37/86] WIP dispense in public context. reverted optional location and reverted legacy changes --- .../opentrons/protocol_api/core/instrument.py | 3 +- .../core/legacy/legacy_instrument_core.py | 10 +- .../legacy_instrument_core.py | 10 +- .../protocol_api/instrument_context.py | 43 +++--- api/src/opentrons/protocol_api/validation.py | 4 +- .../protocol_api/test_instrument_context.py | 129 +++++++++++++----- .../opentrons/protocol_api/test_validation.py | 2 +- 7 files changed, 134 insertions(+), 67 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index de76fcbb59f..9442d12dcb3 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -44,11 +44,12 @@ def aspirate( @abstractmethod def dispense( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[WellCoreType], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: """Dispense a given volume of liquid into the specified location. Args: diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index a4732faa52d..7f796214c7a 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -102,11 +102,12 @@ def aspirate( def dispense( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[LegacyWellCore], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -116,14 +117,13 @@ def dispense( rate: The rate in µL/s to dispense at. flow_rate: Not used in this core. """ - if location: - self.move_to(location=location) + self.move_to(location=location) self._protocol_interface.get_hardware().dispense(self._mount, volume, rate) def blow_out( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[LegacyWellCore], ) -> None: """Blow liquid out of the tip. @@ -132,7 +132,7 @@ def blow_out( location: The location to blow out into. well_core: Unused by legacy core. """ - if location: + if location != self._protocol_interface.get_last_location(): self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index e98d00cc62c..15d898e2217 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -104,23 +104,23 @@ def aspirate( def dispense( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[LegacyWellCore], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: - if location: - self.move_to(location=location, well_core=well_core) + self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.DISPENSE.name) self._update_volume(self.get_current_volume() - volume) def blow_out( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[LegacyWellCore], ) -> None: - if location: + if location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.BLOWOUT.name) self._update_volume(0) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index ed44028fb22..b117c182549 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -289,32 +289,34 @@ def dispense( ) well: Optional[labware.Well] last_location = self._get_last_location_by_api_version() - - if isinstance(location, labware.Well): - well = location - if well.parent._core.is_fixed_trash(): - move_to_location = location.top() - else: - move_to_location = location.bottom( - z=self._well_bottom_clearances.dispense - ) - elif isinstance(location, types.Location): - move_to_location = location - _, well = move_to_location.labware.get_parent_labware_and_well() - elif location is not None: - raise TypeError( - f"location should be a Well or Location, but it is {location}" - ) - elif last_location: - move_to_location = last_location - _, well = move_to_location.labware.get_parent_labware_and_well() - else: + dispense_in_place: bool = False + try: + target = validation.validate_location(location=location, last_location=last_location) + except validation.NoLocationError: raise RuntimeError( "If dispense is called without an explicit location, another" " method that moves to a location (such as move_to or " "aspirate) must previously have been called so the robot " "knows where it is." ) + except validation.LocationTypeError: + raise TypeError( + f"location should be a Well or Location, but it is {location}" + ) + + if isinstance(target, validation.WellTarget): + well = target.well + if well.parent._core.is_fixed_trash(): + move_to_location = target.well.top() + else: + move_to_location = target.location or target.well.bottom( + z=self._well_bottom_clearances.dispense + ) + if isinstance(target, validation.PointTarget): + move_to_location = target.location + _, well = target.location.labware.get_parent_labware_and_well() + dispense_in_place = target.in_place + if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( location=move_to_location, @@ -341,6 +343,7 @@ def dispense( location=move_to_location, well_core=well._core if well is not None else None, flow_rate=flow_rate, + in_place=dispense_in_place ) return self diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 37c53ebaf5f..fd8837b2421 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -234,11 +234,11 @@ def validate_location( if isinstance(location, Well): return WellTarget(well=location, location=None) elif isinstance(location, Location): - if location.labware is None: + if location.labware.as_well() is None: return PointTarget(location=location, in_place=False) return WellTarget(well=location.labware.as_well(), location=location) elif last_location and location is None: - if last_location.labware is None: + if last_location.labware.as_well() is None: return PointTarget(location=last_location, in_place=True) return WellTarget(well=last_location.labware.as_well(), location=last_location) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 8e9eb11219f..8183a9ddb11 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -272,7 +272,7 @@ def test_aspirate_from_coordinates( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(PointTarget(location=input_location, in_place=True)) + ).then_return(PointTarget(location=input_location, in_place=False)) decoy.when(mock_instrument_core.get_absolute_aspirate_flow_rate(1.23)).then_return( 5.67 ) @@ -283,7 +283,7 @@ def test_aspirate_from_coordinates( mock_instrument_core.aspirate( location=input_location, well_core=None, - in_place=True, + in_place=False, volume=42.0, rate=1.23, flow_rate=5.67, @@ -631,87 +631,150 @@ def test_return_tip( def test_dispense_with_location( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext + decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore ) -> None: """It should dispense to a given location.""" - mock_well = decoy.mock(cls=Well) - location = Location(point=Point(1, 2, 3), labware=mock_well) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.0)).then_return( - 3.0 + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(PointTarget(location=input_location, in_place=False)) + decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return( + 5.67 ) - subject.dispense(volume=42.0, location=location) + subject.dispense(volume=42.0, location=input_location, rate=1.23) decoy.verify( mock_instrument_core.dispense( - location=location, - well_core=mock_well._core, + location=input_location, + well_core=None, + in_place=False, volume=42.0, - rate=1.0, - flow_rate=3.0, + rate=1.23, + flow_rate=5.67, ), times=1, ) def test_dispense_with_well_location( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext + decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore ) -> None: """It should dispense to a well.""" mock_well = decoy.mock(cls=Well) + bottom_location = Location(point=Point(1, 2, 3), labware=mock_well) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_well.bottom(1.0)).then_return( - Location(point=Point(1, 2, 3), labware=mock_well) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location ) - - decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.0)).then_return( - 3.0 + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=None)) + decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) + decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return( + 5.67 ) - subject.dispense(volume=42.0, location=mock_well) + subject.dispense(volume=42.0, location=input_location, rate=1.23) decoy.verify( mock_instrument_core.dispense( - location=Location(point=Point(1, 2, 3), labware=mock_well), + location=bottom_location, well_core=mock_well._core, + in_place=False, volume=42.0, - rate=1.0, - flow_rate=3.0, + rate=1.23, + flow_rate=5.67, ), times=1, ) -def test_dispense_with_no_location( +def test_dispense_in_place( decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - """It should dispense to a well.""" - decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return( - Location(point=Point(1, 2, 3), labware=None) - ) + """It should dispense in place.""" + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.0)).then_return( - 3.0 + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(PointTarget(location=input_location, in_place=True)) + decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return( + 5.67 ) - subject.dispense(volume=42.0) + subject.dispense(volume=42.0, location=input_location, rate=1.23) decoy.verify( mock_instrument_core.dispense( - location=Location(point=Point(1, 2, 3), labware=None), + location=input_location, well_core=None, + in_place=True, volume=42.0, - rate=1.0, - flow_rate=3.0, + rate=1.23, + flow_rate=5.67, ), times=1, ) +def test_dispense_raises_no_location( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) + + decoy.when( + mock_validation.validate_location(location=None, last_location=None) + ).then_raise(mock_validation.NoLocationError()) + with pytest.raises(RuntimeError): + subject.dispense(location=None) + + +def test_dispense_raises_wrong_location_value( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) + + decoy.when( + mock_validation.validate_location(location=None, last_location=None) + ).then_raise(mock_validation.LocationTypeError()) + with pytest.raises(TypeError): + subject.dispense(location=None) + + def test_touch_tip( decoy: Decoy, mock_instrument_core: InstrumentCore, diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index e6998ddfe23..32c1800c02c 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -278,7 +278,7 @@ def test_validate_in_place(decoy: Decoy) -> None: input_last_location = Location(point=Point(x=1, y=1, z=2), labware=None) expected_result = subject.PointTarget(location=input_last_location, in_place=True) - result = subject.validate_location(location=input_last_location, last_location=None) + result = subject.validate_location(location=None, last_location=input_last_location) assert result == expected_result From dea72903630f30dcfe8e8c9a570bc62dff71efa4 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 15 Feb 2023 15:06:48 -0500 Subject: [PATCH 38/86] blow out and fixed validation method as_well --- .../protocol_api/core/engine/instrument.py | 31 ++---- .../opentrons/protocol_api/core/instrument.py | 3 +- .../core/legacy/legacy_instrument_core.py | 3 + .../legacy_instrument_core.py | 1 + .../protocol_api/instrument_context.py | 66 +++++------ api/src/opentrons/protocol_api/validation.py | 10 +- .../core/engine/test_instrument_core.py | 41 ++++++- .../protocol_api/test_instrument_context.py | 104 ++++++++++++++---- .../opentrons/protocol_api/test_validation.py | 20 +++- 9 files changed, 195 insertions(+), 84 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 582145f111b..a590bcbd62c 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -118,17 +118,16 @@ def aspirate( flow_rate=flow_rate, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def dispense( self, - location: Optional[Location], + location: Location, well_core: Optional[WellCore], volume: float, rate: float, flow_rate: float, + in_place: bool = False, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -139,7 +138,7 @@ def dispense( flow_rate: The flow rate in µL/s to dispense at. """ if well_core is None: - if location: + if not in_place: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( @@ -149,14 +148,11 @@ def dispense( force_direct=False, speed=None, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) self._engine_client.dispense_in_place( pipette_id=self._pipette_id, volume=volume, flow_rate=flow_rate ) - elif location: + else: well_name = well_core.get_name() labware_id = well_core.labware_id @@ -177,12 +173,10 @@ def dispense( flow_rate=flow_rate, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def blow_out( - self, location: Optional[Location], well_core: Optional[WellCore] + self, location: Location, well_core: Optional[WellCore], in_place: bool = False ) -> None: """Blow liquid out of the tip. @@ -192,7 +186,7 @@ def blow_out( """ flow_rate = self.get_absolute_blow_out_flow_rate(1.0) if well_core is None: - if location: + if not in_place: self._engine_client.move_to_coordinates( pipette_id=self._pipette_id, coordinates=DeckPoint( @@ -202,14 +196,11 @@ def blow_out( minimum_z_height=None, speed=None, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) self._engine_client.blow_out_in_place( pipette_id=self._pipette_id, flow_rate=flow_rate ) - elif location: + else: well_name = well_core.get_name() labware_id = well_core.labware_id @@ -231,9 +222,7 @@ def blow_out( flow_rate=flow_rate, ) - self._protocol_core.set_last_location( - location=location, mount=self.get_mount() - ) + self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def touch_tip( self, diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 9442d12dcb3..a1b10003de1 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -64,8 +64,9 @@ def dispense( @abstractmethod def blow_out( self, - location: Optional[types.Location], + location: types.Location, well_core: Optional[WellCoreType], + in_place: bool = False, ) -> None: """Blow liquid out of the tip. diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 7f796214c7a..ec39e825dcf 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -125,6 +125,7 @@ def blow_out( self, location: types.Location, well_core: Optional[LegacyWellCore], + in_place: bool = False, ) -> None: """Blow liquid out of the tip. @@ -132,6 +133,8 @@ def blow_out( location: The location to blow out into. well_core: Unused by legacy core. """ + print(location) + print(self._protocol_interface.get_last_location()) if location != self._protocol_interface.get_last_location(): self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 15d898e2217..72130c958a9 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -119,6 +119,7 @@ def blow_out( self, location: types.Location, well_core: Optional[LegacyWellCore], + in_place: bool = False, ) -> None: if location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index b117c182549..51353980673 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -202,7 +202,7 @@ def aspirate( well = target.well if isinstance(target, validation.PointTarget): move_to_location = target.location - _, well = target.location.labware.get_parent_labware_and_well() + well = None aspirate_in_place = target.in_place if self.api_version >= APIVersion(2, 11): @@ -287,11 +287,13 @@ def dispense( volume, location if location else "current position", rate ) ) - well: Optional[labware.Well] + well: Optional[labware.Well] = None last_location = self._get_last_location_by_api_version() dispense_in_place: bool = False try: - target = validation.validate_location(location=location, last_location=last_location) + target = validation.validate_location( + location=location, last_location=last_location + ) except validation.NoLocationError: raise RuntimeError( "If dispense is called without an explicit location, another" @@ -301,8 +303,8 @@ def dispense( ) except validation.LocationTypeError: raise TypeError( - f"location should be a Well or Location, but it is {location}" - ) + f"location should be a Well or Location, but it is {location}" + ) if isinstance(target, validation.WellTarget): well = target.well @@ -314,7 +316,7 @@ def dispense( ) if isinstance(target, validation.PointTarget): move_to_location = target.location - _, well = target.location.labware.get_parent_labware_and_well() + well = None dispense_in_place = target.in_place if self.api_version >= APIVersion(2, 11): @@ -343,7 +345,7 @@ def dispense( location=move_to_location, well_core=well._core if well is not None else None, flow_rate=flow_rate, - in_place=dispense_in_place + in_place=dispense_in_place, ) return self @@ -445,46 +447,48 @@ def blow_out( :py:meth:`dispense`) :returns: This instance """ - well: Optional[labware.Well] - move_to_location: Optional[types.Location] - last_location = self._get_last_location_by_api_version() + move_to_location: types.Location + blow_out_in_place: bool = False - if isinstance(location, labware.Well): - if location.parent.is_tiprack: - _log.warning( - "Blow_out being performed on a tiprack. " - "Please re-check your code" - ) - move_to_location = location.top() - well = location - elif isinstance(location, types.Location): - move_to_location = location - _, well = location.labware.get_parent_labware_and_well() - elif location is not None: - raise TypeError( - "location should be a Well or Location, but it is {}".format(location) + last_location = self._get_last_location_by_api_version() + try: + target = validation.validate_location( + location=location, last_location=last_location ) - elif last_location: - move_to_location = None - _, well = last_location.labware.get_parent_labware_and_well() - else: + except validation.NoLocationError: raise RuntimeError( "If blow out is called without an explicit location, another" " method that moves to a location (such as move_to or " "dispense) must previously have been called so the robot " "knows where it is." ) + except validation.LocationTypeError: + raise TypeError( + "location should be a Well or Location, but it is {}".format(location) + ) + + if isinstance(target, validation.WellTarget): + if target.well.parent.is_tiprack: + _log.warning( + "Blow_out being performed on a tiprack. " + "Please re-check your code" + ) + move_to_location = target.location or target.well.top() + well = target.well + elif isinstance(target, validation.PointTarget): + move_to_location = target.location + well = None + blow_out_in_place = target.in_place with publisher.publish_context( broker=self.broker, - command=cmds.blow_out( - instrument=self, location=move_to_location or last_location # type: ignore[arg-type] - ), + command=cmds.blow_out(instrument=self, location=move_to_location), ): self._core.blow_out( location=move_to_location, well_core=well._core if well is not None else None, + in_place=blow_out_in_place, ) return self diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index fd8837b2421..3845485d5b4 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -234,12 +234,14 @@ def validate_location( if isinstance(location, Well): return WellTarget(well=location, location=None) elif isinstance(location, Location): - if location.labware.as_well() is None: + _, well = location.labware.get_parent_labware_and_well() + if well is None: return PointTarget(location=location, in_place=False) - return WellTarget(well=location.labware.as_well(), location=location) + return WellTarget(well=well, location=location) elif last_location and location is None: - if last_location.labware.as_well() is None: + _, well = last_location.labware.get_parent_labware_and_well() + if well is None: return PointTarget(location=last_location, in_place=True) - return WellTarget(well=last_location.labware.as_well(), location=last_location) + return WellTarget(well=well, location=last_location) raise LocationTypeError() diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index a808031b45d..10ba3b04c1a 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -306,6 +306,7 @@ def test_aspirate_from_well( mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), ) + def test_aspirate_from_location( decoy: Decoy, mock_engine_client: EngineClient, @@ -315,7 +316,12 @@ def test_aspirate_from_location( """It should aspirate from coordinates.""" location = Location(point=Point(1, 2, 3), labware=None) subject.aspirate( - volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location, in_place=False + volume=12.34, + rate=5.6, + flow_rate=7.8, + well_core=None, + location=location, + in_place=False, ) decoy.verify( @@ -343,7 +349,12 @@ def test_aspirate_in_place( """It should aspirate in place.""" location = Location(point=Point(1, 2, 3), labware=None) subject.aspirate( - volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location, in_place=True + volume=12.34, + rate=5.6, + flow_rate=7.8, + well_core=None, + location=location, + in_place=True, ) decoy.verify( @@ -460,6 +471,32 @@ def test_dispense_in_place( mock_engine_client: EngineClient, mock_protocol_core: ProtocolCore, subject: InstrumentCore, +) -> None: + """It should dispense in place.""" + location = Location(point=Point(1, 2, 3), labware=None) + subject.dispense( + volume=12.34, + rate=5.6, + flow_rate=7.8, + well_core=None, + location=location, + in_place=True, + ) + + decoy.verify( + mock_engine_client.dispense_in_place( + pipette_id="abc123", + volume=12.34, + flow_rate=7.8, + ), + ) + + +def test_dispense_in_coordinates( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, ) -> None: """It should dispense in place.""" location = Location(point=Point(1, 2, 3), labware=None) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 8183a9ddb11..fa1735cca33 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -365,42 +365,68 @@ def test_aspirate_raises_wrong_location_value( def test_blow_out_to_well( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: """It should blow out to a well.""" mock_well = decoy.mock(cls=Well) top_location = Location(point=Point(1, 2, 3), labware=mock_well) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=None)) decoy.when(mock_well.top()).then_return(top_location) - - subject.blow_out(location=mock_well) + subject.blow_out(location=input_location) decoy.verify( mock_instrument_core.blow_out( - location=top_location, - well_core=mock_well._core, + location=top_location, well_core=mock_well._core, in_place=False ), times=1, ) def test_blow_out_to_location( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: """It should blow out to a location.""" - mock_location = decoy.mock(cls=Location) mock_well = decoy.mock(cls=Well) + input_location = Location(point=Point(2, 2, 2), labware=mock_well) + last_location = Location(point=Point(9, 9, 9), labware=None) + point_target = PointTarget(location=input_location, in_place=False) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_location.labware.get_parent_labware_and_well()).then_return( + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(point_target) + + decoy.when(point_target.location.labware.get_parent_labware_and_well()).then_return( (None, mock_well) ) - subject.blow_out(location=mock_location) + subject.blow_out(location=input_location) decoy.verify( mock_instrument_core.blow_out( - location=mock_location, - well_core=mock_well._core, + location=input_location, well_core=mock_well._core, in_place=False ), times=1, ) @@ -413,34 +439,58 @@ def test_blow_out_in_place( subject: InstrumentContext, ) -> None: """It should blow out in place.""" - mock_well = decoy.mock(cls=Well) - location = Location(point=Point(1, 2, 3), labware=mock_well) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_protocol_core.get_last_location(mount=Mount.LEFT)).then_return( - location + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location ) + decoy.when( + mock_validation.validate_location(location=None, last_location=last_location) + ).then_return(PointTarget(location=last_location, in_place=True)) subject.blow_out() decoy.verify( mock_instrument_core.blow_out( - location=None, - well_core=mock_well._core, + location=last_location, well_core=None, in_place=True ), times=1, ) -def test_blow_out_no_location_cache_raises( +def test_blow_out_raises_no_location( decoy: Decoy, - mock_protocol_core: ProtocolCore, + mock_instrument_core: InstrumentCore, subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: - """It should raise if no location or well is provided and the location cache returns None.""" - decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return(None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) + + decoy.when( + mock_validation.validate_location(location=None, last_location=None) + ).then_raise(mock_validation.NoLocationError()) with pytest.raises(RuntimeError): - subject.blow_out() + subject.blow_out(location=None) + + +def test_blow_out_raises_wrong_location_value( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) + + decoy.when( + mock_validation.validate_location(location=None, last_location=None) + ).then_raise(mock_validation.LocationTypeError()) + with pytest.raises(TypeError): + subject.blow_out(location=None) def test_pick_up_tip_from_labware( @@ -631,7 +681,10 @@ def test_return_tip( def test_dispense_with_location( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: """It should dispense to a given location.""" input_location = Location(point=Point(2, 2, 2), labware=None) @@ -666,7 +719,10 @@ def test_dispense_with_location( def test_dispense_with_well_location( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: """It should dispense to a well.""" mock_well = decoy.mock(cls=Well) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 32c1800c02c..678d6223fa7 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -14,7 +14,7 @@ HeaterShakerModuleModel, ThermocyclerStep, ) -from opentrons.protocol_api import validation as subject, Well +from opentrons.protocol_api import validation as subject, Well, Labware @pytest.mark.parametrize( @@ -325,3 +325,21 @@ def test_validate_with_wrong_location() -> None: def test_validate_raises_no_location_error() -> None: with pytest.raises(subject.NoLocationError): subject.validate_location(location=None, last_location=None) + + +def test_validate_with_labware(decoy: Decoy) -> None: + mock_labware = decoy.mock(cls=Labware) + input_location = Location(point=Point(1, 1, 1), labware=mock_labware) + + result = subject.validate_location(location=input_location, last_location=None) + + assert result == subject.PointTarget(location=input_location, in_place=False) + + +def test_validate_last_location_with_labware(decoy: Decoy) -> None: + mock_labware = decoy.mock(cls=Labware) + input_last_location = Location(point=Point(1, 1, 1), labware=mock_labware) + + result = subject.validate_location(location=None, last_location=input_last_location) + + assert result == subject.PointTarget(location=input_last_location, in_place=True) From 520b3a28473c57721d18183af0683f2df9260f02 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 15 Feb 2023 15:20:03 -0500 Subject: [PATCH 39/86] linting fixes --- .../protocol_api/test_instrument_context.py | 126 ++++++++++++++++-- .../opentrons/protocol_api/test_validation.py | 7 +- 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index fa1735cca33..b55bdbe9a54 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -254,6 +254,45 @@ def test_aspirate( ) +def test_aspirate_well_location( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + """It should aspirate to a well.""" + mock_well = decoy.mock(cls=Well) + input_location = Location(point=Point(2, 2, 2), labware=mock_well) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=input_location)) + decoy.when(mock_instrument_core.get_absolute_aspirate_flow_rate(1.23)).then_return( + 5.67 + ) + + subject.aspirate(volume=42.0, location=input_location, rate=1.23) + + decoy.verify( + mock_instrument_core.aspirate( + location=input_location, + well_core=mock_well._core, + in_place=False, + volume=42.0, + rate=1.23, + flow_rate=5.67, + ), + times=1, + ) + + def test_aspirate_from_coordinates( decoy: Decoy, mock_instrument_core: InstrumentCore, @@ -336,7 +375,7 @@ def test_aspirate_raises_no_location( subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - + """Shound raise a RuntimeError error.""" decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) @@ -353,7 +392,7 @@ def test_aspirate_raises_wrong_location_value( subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - + """Should raise a TypeError.""" decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) @@ -396,6 +435,36 @@ def test_blow_out_to_well( ) +def test_blow_out_to_well_location( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + """It should blow out to a well location.""" + mock_well = decoy.mock(cls=Well) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=input_location)) + subject.blow_out(location=input_location) + + decoy.verify( + mock_instrument_core.blow_out( + location=input_location, well_core=mock_well._core, in_place=False + ), + times=1, + ) + + def test_blow_out_to_location( decoy: Decoy, mock_instrument_core: InstrumentCore, @@ -418,15 +487,11 @@ def test_blow_out_to_location( ) ).then_return(point_target) - decoy.when(point_target.location.labware.get_parent_labware_and_well()).then_return( - (None, mock_well) - ) - subject.blow_out(location=input_location) decoy.verify( mock_instrument_core.blow_out( - location=input_location, well_core=mock_well._core, in_place=False + location=input_location, well_core=None, in_place=False ), times=1, ) @@ -465,7 +530,7 @@ def test_blow_out_raises_no_location( subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - + """Should raise a RuntimeError.""" decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) @@ -482,7 +547,7 @@ def test_blow_out_raises_wrong_location_value( subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - + """Should raise a TypeError.""" decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) @@ -723,6 +788,45 @@ def test_dispense_with_well_location( mock_instrument_core: InstrumentCore, subject: InstrumentContext, mock_protocol_core: ProtocolCore, +) -> None: + """It should dispense to a well location.""" + mock_well = decoy.mock(cls=Well) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=input_location)) + decoy.when(mock_instrument_core.get_absolute_dispense_flow_rate(1.23)).then_return( + 5.67 + ) + + subject.dispense(volume=42.0, location=input_location, rate=1.23) + + decoy.verify( + mock_instrument_core.dispense( + location=input_location, + well_core=mock_well._core, + in_place=False, + volume=42.0, + rate=1.23, + flow_rate=5.67, + ), + times=1, + ) + + +def test_dispense_with_well( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: """It should dispense to a well.""" mock_well = decoy.mock(cls=Well) @@ -803,7 +907,7 @@ def test_dispense_raises_no_location( subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - + """Should raise a RuntimeError.""" decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) @@ -820,7 +924,7 @@ def test_dispense_raises_wrong_location_value( subject: InstrumentContext, mock_protocol_core: ProtocolCore, ) -> None: - + """Should raise a TypeError.""" decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 678d6223fa7..75dd31e0620 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -306,7 +306,7 @@ def test_validate_last_location(decoy: Decoy) -> None: def test_validate_with_wrong_location_with_last_location() -> None: - + """Should raise a LocationTypeError.""" with pytest.raises(subject.LocationTypeError): subject.validate_location( location=42, # type: ignore[arg-type] @@ -315,7 +315,7 @@ def test_validate_with_wrong_location_with_last_location() -> None: def test_validate_with_wrong_location() -> None: - + """Should raise a LocationTypeError.""" with pytest.raises(subject.LocationTypeError): subject.validate_location( location=42, last_location=None # type: ignore[arg-type] @@ -323,11 +323,13 @@ def test_validate_with_wrong_location() -> None: def test_validate_raises_no_location_error() -> None: + """Should raise a NoLocationError.""" with pytest.raises(subject.NoLocationError): subject.validate_location(location=None, last_location=None) def test_validate_with_labware(decoy: Decoy) -> None: + """Should return a PointTarget for none in_place commands.""" mock_labware = decoy.mock(cls=Labware) input_location = Location(point=Point(1, 1, 1), labware=mock_labware) @@ -337,6 +339,7 @@ def test_validate_with_labware(decoy: Decoy) -> None: def test_validate_last_location_with_labware(decoy: Decoy) -> None: + """Should return a PointTarget for in_place commands.""" mock_labware = decoy.mock(cls=Labware) input_last_location = Location(point=Point(1, 1, 1), labware=mock_labware) From 1fd6b6665e724f63f9737260e9080aacd7c07fe0 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Wed, 15 Feb 2023 15:54:28 -0500 Subject: [PATCH 40/86] Update api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py --- .../protocol_api/core/legacy/legacy_instrument_core.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index ac688951cb1..380cf3e3a30 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -140,8 +140,6 @@ def blow_out( location: The location to blow out into. well_core: Unused by legacy core. """ - print(location) - print(self._protocol_interface.get_last_location()) if location != self._protocol_interface.get_last_location(): self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) From ca44f975f96202d2c122a3971aeae969e8279232 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Wed, 15 Feb 2023 15:55:11 -0500 Subject: [PATCH 41/86] Update api/src/opentrons/protocol_api/instrument_context.py --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index fe355c1b3b3..d3d81b525c4 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -219,7 +219,7 @@ def aspirate( command=cmds.aspirate( instrument=self, volume=c_vol, - location=move_to_location or last_location, # type: ignore[arg-type] + location=move_to_location flow_rate=flow_rate, rate=rate, ), From f921902b00600c9ccdda0a0993caf42276d0c028 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Wed, 15 Feb 2023 15:58:00 -0500 Subject: [PATCH 42/86] Update api/src/opentrons/protocol_api/validation.py --- api/src/opentrons/protocol_api/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 3845485d5b4..add1da396ba 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -217,7 +217,7 @@ class NoLocationError(ValueError): class LocationTypeError(TypeError): - """""" + """Error representing that the location supplied is of different expected type.""" def validate_location( From f4f3dd1a27f1c70597778c673260f5832df1a7bf Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Wed, 15 Feb 2023 15:58:11 -0500 Subject: [PATCH 43/86] Update api/src/opentrons/protocol_api/validation.py --- api/src/opentrons/protocol_api/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index add1da396ba..2883469ae36 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -213,7 +213,7 @@ class PointTarget(NamedTuple): class NoLocationError(ValueError): - """""" + """Error representing that no location was supplied.""" class LocationTypeError(TypeError): From c88e07dac8b3ee60fac1a6ca0572b455847a8d08 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Wed, 15 Feb 2023 16:07:53 -0500 Subject: [PATCH 44/86] merge conflicts and docstring --- api/src/opentrons/protocol_api/core/engine/instrument.py | 3 +++ .../protocol_api/core/legacy/legacy_instrument_core.py | 2 ++ api/src/opentrons/protocol_api/instrument_context.py | 2 +- api/src/opentrons/protocol_api/validation.py | 9 +++++++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 0d3041fc8d3..642472215fe 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -93,6 +93,7 @@ def aspirate( well_core: The well to aspirate from, if applicable. rate: Not used in this core. flow_rate: The flow rate in µL/s to aspirate at. + in_place: whether this an in-place command. """ if well_core is None: if not in_place: @@ -149,6 +150,7 @@ def dispense( well_core: The well to dispense to, if applicable. rate: Not used in this core. flow_rate: The flow rate in µL/s to dispense at. + in_place: whether this an in-place command. """ if well_core is None: if not in_place: @@ -196,6 +198,7 @@ def blow_out( Args: location: The location to blow out into. well_core: The well to blow out into. + in_place: whether this an in-place command. """ flow_rate = self.get_blow_out_flow_rate(1.0) if well_core is None: diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 380cf3e3a30..156be895c7a 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -123,6 +123,7 @@ def dispense( well_core: The well to dispense to, if applicable. rate: The rate in µL/s to dispense at. flow_rate: Not used in this core. + in_place: Used only for the engine core. """ self.move_to(location=location) @@ -139,6 +140,7 @@ def blow_out( Args: location: The location to blow out into. well_core: Unused by legacy core. + in_place: Used only for the engine core. """ if location != self._protocol_interface.get_last_location(): self.move_to(location=location) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index d3d81b525c4..15ed89549d6 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -219,7 +219,7 @@ def aspirate( command=cmds.aspirate( instrument=self, volume=c_vol, - location=move_to_location + location=move_to_location, flow_rate=flow_rate, rate=rate, ), diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 2883469ae36..59f6999c861 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -223,8 +223,13 @@ class LocationTypeError(TypeError): def validate_location( location: Union[Location, Well, None], last_location: Optional[Location] ) -> Union[WellTarget, PointTarget]: - """ - Raises: NoLocationError, LocationTypeError + """Validate and return if should use a WellTarget or a PointTarget. + Args: + location: The input location. + last_location: The cached last location. + Raises: + NoLocationError: The is no input location and no cached loaction. + LocationTypeError: The location supplied is of unexpected type. """ from .labware import Well From bd19514f22ca3d699e41eb27c8fb56fb7ed0ecca Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:08:55 -0500 Subject: [PATCH 45/86] Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py Co-authored-by: Mike Cousins --- .../core/legacy_simulator/legacy_instrument_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 82da7f4897a..fdb4e64f811 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -79,7 +79,7 @@ def aspirate( flow_rate: float, in_place: bool = False, ) -> None: - if location and self.get_current_volume() == 0: + if self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any # liquid to prepare the pipette for aspiration if self._api_version < APIVersion(2, 3) or not self.is_ready_to_aspirate(): From ed3c8c03f6907e9b07a94c724c645051040e6bac Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:09:17 -0500 Subject: [PATCH 46/86] Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py Co-authored-by: Mike Cousins --- .../core/legacy_simulator/legacy_instrument_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index fdb4e64f811..1bfa3de6eb9 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -99,7 +99,7 @@ def aspirate( ) self.prepare_for_aspirate() self.move_to(location=location, well_core=well_core) - elif location: + elif location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.ASPIRATE.name) From d77b0ea7f4bcafb714ef5157cf8ca91ecec47c83 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:09:38 -0500 Subject: [PATCH 47/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 15ed89549d6..396aa4a8fd6 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -190,10 +190,10 @@ def aspirate( "dispense) must previously have been called so the robot " "knows where it is." ) - except validation.LocationTypeError: + except validation.LocationTypeError as e: raise TypeError( - "location should be a Well or Location, but it is {}".format(location) - ) + f"location should be a Well or Location, but it is {location}" + ) from e if isinstance(target, validation.WellTarget): move_to_location = target.location or target.well.bottom( From cc8c11fa6479aab34546b9c64e6e491f6a14b4ae Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:11:25 -0500 Subject: [PATCH 48/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 396aa4a8fd6..7c49c5c4887 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -308,10 +308,12 @@ def dispense( if isinstance(target, validation.WellTarget): well = target.well - if well.parent._core.is_fixed_trash(): + if target.location: + move_to_location = target.location + elif well.parent._core.is_fixed_trash(): move_to_location = target.well.top() else: - move_to_location = target.location or target.well.bottom( + move_to_location = target.well.bottom( z=self._well_bottom_clearances.dispense ) if isinstance(target, validation.PointTarget): From 479a859e5d27e0df1aa74732a9057c5f57c3e899 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:11:38 -0500 Subject: [PATCH 49/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 7c49c5c4887..4e1fc7570d0 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -458,7 +458,7 @@ def blow_out( target = validation.validate_location( location=location, last_location=last_location ) - except validation.NoLocationError: + except validation.NoLocationError as e: raise RuntimeError( "If blow out is called without an explicit location, another" " method that moves to a location (such as move_to or " From accc27e577ba15dd7c564b678b3eda2529e49aad Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:13:16 -0500 Subject: [PATCH 50/86] Update api/src/opentrons/protocol_api/validation.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/validation.py | 32 +++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 59f6999c861..0d391be1c3f 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -233,20 +233,22 @@ def validate_location( """ from .labware import Well - if location is None and last_location is None: + target_location = location or last_location + + if target_location is None: raise NoLocationError() - if isinstance(location, Well): - return WellTarget(well=location, location=None) - elif isinstance(location, Location): - _, well = location.labware.get_parent_labware_and_well() - if well is None: - return PointTarget(location=location, in_place=False) - return WellTarget(well=well, location=location) - elif last_location and location is None: - _, well = last_location.labware.get_parent_labware_and_well() - if well is None: - return PointTarget(location=last_location, in_place=True) - return WellTarget(well=well, location=last_location) - - raise LocationTypeError() + if not isinstance(target_location, (Location, Well)): + raise LocationTypeError() + + if isinstance(target_location, Well): + return WellTarget(well=target_location, location=None) + + _, well = target_location.labware.get_parent_labware_and_well() + in_place = target_location is last_location + + return ( + WellTarget(well=well, location=target_location) + if well is not None + else PointTarget(location=target_location, in_place=in_place) + ) From a6f31319eb00e31a159346235e5aff7304bdd287 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 10:40:31 -0500 Subject: [PATCH 51/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 4e1fc7570d0..7ef80746bb5 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -464,7 +464,7 @@ def blow_out( " method that moves to a location (such as move_to or " "dispense) must previously have been called so the robot " "knows where it is." - ) + ) from e except validation.LocationTypeError: raise TypeError( "location should be a Well or Location, but it is {}".format(location) From f4f234bac32c31110754c107a4ee10da2072cbe3 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 16 Feb 2023 11:03:24 -0500 Subject: [PATCH 52/86] removed default arg for in_place. pr feedback --- .../protocol_api/core/engine/instrument.py | 6 ++--- .../opentrons/protocol_api/core/instrument.py | 9 ++++--- .../core/legacy/legacy_instrument_core.py | 6 ++--- .../legacy_instrument_core.py | 6 ++--- .../protocol_api/instrument_context.py | 18 +++---------- api/src/opentrons/protocol_api/validation.py | 4 ++- .../core/engine/test_instrument_core.py | 25 +++++++++++++++---- .../core/simulator/test_instrument_context.py | 18 ++++++++++++- 8 files changed, 58 insertions(+), 34 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 642472215fe..d70176b8128 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -84,7 +84,7 @@ def aspirate( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: @@ -141,7 +141,7 @@ def dispense( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -191,7 +191,7 @@ def dispense( self._protocol_core.set_last_location(location=location, mount=self.get_mount()) def blow_out( - self, location: Location, well_core: Optional[WellCore], in_place: bool = False + self, location: Location, well_core: Optional[WellCore], in_place: bool ) -> None: """Blow liquid out of the tip. diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index b4734cf0cae..3f4ac9a859d 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -29,7 +29,7 @@ def aspirate( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: @@ -38,6 +38,7 @@ def aspirate( well_core: The well to aspirate from, if applicable. rate: The rate for how quickly to aspirate. flow_rate: The flow rate in µL/s to aspirate at. + in_place: Whether this is in-place. """ ... @@ -49,7 +50,7 @@ def dispense( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -58,6 +59,7 @@ def dispense( well_core: The well to dispense to, if applicable. rate: The rate for how quickly to dispense. flow_rate: The flow rate in µL/s to dispense at. + in_place: Whether this is in-place. """ ... @@ -66,13 +68,14 @@ def blow_out( self, location: types.Location, well_core: Optional[WellCoreType], - in_place: bool = False, + in_place: bool, ) -> None: """Blow liquid out of the tip. Args: location: The location to blow out into. well_core: The well to blow out into. + in_place: Whether this is in-place. """ ... diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 156be895c7a..6ad9a326688 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -73,7 +73,7 @@ def aspirate( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: @@ -114,7 +114,7 @@ def dispense( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -133,7 +133,7 @@ def blow_out( self, location: types.Location, well_core: Optional[LegacyWellCore], - in_place: bool = False, + in_place: bool, ) -> None: """Blow liquid out of the tip. diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 1bfa3de6eb9..07912826fd6 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -77,7 +77,7 @@ def aspirate( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: if self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any @@ -117,7 +117,7 @@ def dispense( volume: float, rate: float, flow_rate: float, - in_place: bool = False, + in_place: bool, ) -> None: self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.DISPENSE.name) @@ -127,7 +127,7 @@ def blow_out( self, location: types.Location, well_core: Optional[LegacyWellCore], - in_place: bool = False, + in_place: bool, ) -> None: if location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 7ef80746bb5..04d99142b8a 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -183,16 +183,12 @@ def aspirate( target = validation.validate_location( location=location, last_location=last_location ) - except validation.NoLocationError: + except validation.NoLocationError as e: raise RuntimeError( "If aspirate is called without an explicit location, another" " method that moves to a location (such as move_to or " "dispense) must previously have been called so the robot " "knows where it is." - ) - except validation.LocationTypeError as e: - raise TypeError( - f"location should be a Well or Location, but it is {location}" ) from e if isinstance(target, validation.WellTarget): @@ -294,17 +290,13 @@ def dispense( target = validation.validate_location( location=location, last_location=last_location ) - except validation.NoLocationError: + except validation.NoLocationError as e: raise RuntimeError( "If dispense is called without an explicit location, another" " method that moves to a location (such as move_to or " "aspirate) must previously have been called so the robot " "knows where it is." - ) - except validation.LocationTypeError: - raise TypeError( - f"location should be a Well or Location, but it is {location}" - ) + ) from e if isinstance(target, validation.WellTarget): well = target.well @@ -465,10 +457,6 @@ def blow_out( "dispense) must previously have been called so the robot " "knows where it is." ) from e - except validation.LocationTypeError: - raise TypeError( - "location should be a Well or Location, but it is {}".format(location) - ) if isinstance(target, validation.WellTarget): if target.well.parent.is_tiprack: diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 0d391be1c3f..e1e258c3f9f 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -239,7 +239,9 @@ def validate_location( raise NoLocationError() if not isinstance(target_location, (Location, Well)): - raise LocationTypeError() + raise LocationTypeError( + "location should be a Well or Location, but it is {}".format(location) + ) if isinstance(target_location, Well): return WellTarget(well=target_location, location=None) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 454e9711816..b55fd1f90f2 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -286,7 +286,12 @@ def test_aspirate_from_well( ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) subject.aspirate( - location=location, well_core=well_core, volume=12.34, rate=5.6, flow_rate=7.8 + location=location, + well_core=well_core, + volume=12.34, + rate=5.6, + flow_rate=7.8, + in_place=False, ) decoy.verify( @@ -382,7 +387,7 @@ def test_blow_out_to_well( ) ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) - subject.blow_out(location=location, well_core=well_core) + subject.blow_out(location=location, well_core=well_core, in_place=False) decoy.verify( mock_engine_client.blow_out( @@ -407,7 +412,7 @@ def test_blow_out_in_place( """It should blow out in place.""" location = Location(point=Point(1, 2, 3), labware=None) - subject.blow_out(location=location, well_core=None) + subject.blow_out(location=location, well_core=None, in_place=False) decoy.verify( mock_engine_client.move_to_coordinates( @@ -445,7 +450,12 @@ def test_dispense_to_well( ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) subject.dispense( - location=location, well_core=well_core, volume=12.34, rate=5.6, flow_rate=6.0 + location=location, + well_core=well_core, + volume=12.34, + rate=5.6, + flow_rate=6.0, + in_place=False, ) decoy.verify( @@ -498,7 +508,12 @@ def test_dispense_in_coordinates( """It should dispense in place.""" location = Location(point=Point(1, 2, 3), labware=None) subject.dispense( - volume=12.34, rate=5.6, flow_rate=7.8, well_core=None, location=location + volume=12.34, + rate=5.6, + flow_rate=7.8, + well_core=None, + location=location, + in_place=False, ) decoy.verify( diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index ddeb9cd7055..389a9a465a4 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -50,7 +50,12 @@ def test_dispense_no_tip(subject: InstrumentCore) -> None: location = Location(point=Point(1, 2, 3), labware=None) with pytest.raises(NoTipAttachedError, match="Cannot perform DISPENSE"): subject.dispense( - volume=1, rate=1, flow_rate=1, location=location, well_core=None + volume=1, + rate=1, + flow_rate=1, + location=location, + well_core=None, + in_place=False, ) @@ -70,6 +75,7 @@ def test_blow_out_no_tip(subject: InstrumentCore, labware: LabwareCore) -> None: subject.blow_out( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), + in_place=False, ) @@ -115,6 +121,7 @@ def test_pick_up_tip_prep_after( volume=1, rate=1, flow_rate=1, + in_place=False, ) subject.dispense( volume=1, @@ -122,6 +129,7 @@ def test_pick_up_tip_prep_after( flow_rate=1, location=Location(point=Point(2, 2, 3), labware=None), well_core=labware.get_well_core("A2"), + in_place=False, ) subject.drop_tip(location=None, well_core=tip_core, home_after=True) @@ -140,6 +148,7 @@ def test_pick_up_tip_prep_after( volume=1, rate=1, flow_rate=1, + in_place=False, ) subject.dispense( volume=1, @@ -147,6 +156,7 @@ def test_pick_up_tip_prep_after( flow_rate=1, location=Location(point=Point(2, 2, 3), labware=None), well_core=labware.get_well_core("A2"), + in_place=False, ) subject.drop_tip(location=None, well_core=tip_core, home_after=True) @@ -176,6 +186,7 @@ def test_aspirate_too_much( volume=subject.get_max_volume() + 1, rate=1, flow_rate=1, + in_place=False, ) @@ -226,6 +237,7 @@ def _aspirate(i: InstrumentCore, labware: LabwareCore) -> None: volume=12, rate=10, flow_rate=10, + in_place=False, ) @@ -238,6 +250,7 @@ def _aspirate_dispense(i: InstrumentCore, labware: LabwareCore) -> None: volume=12, rate=10, flow_rate=10, + in_place=False, ) i.dispense( volume=2, @@ -245,6 +258,7 @@ def _aspirate_dispense(i: InstrumentCore, labware: LabwareCore) -> None: flow_rate=2, location=Location(point=Point(2, 2, 3), labware=None), well_core=labware.get_well_core("A2"), + in_place=False, ) @@ -257,10 +271,12 @@ def _aspirate_blowout(i: InstrumentCore, labware: LabwareCore) -> None: volume=11, rate=13, flow_rate=13, + in_place=False, ) i.blow_out( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), + in_place=False, ) From 2c26a88ced022e1d8a5a4a6fbc62c81bf999465a Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 16 Feb 2023 14:57:09 -0500 Subject: [PATCH 53/86] changed aspirate/dispense/blow_out to check against in_place. changed validation to check if last_location is equal to location --- .../core/legacy/legacy_instrument_core.py | 13 +++++++------ api/src/opentrons/protocol_api/validation.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 6ad9a326688..71c213039b2 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -82,7 +82,7 @@ def aspirate( well_core: The well to aspirate from, if applicable. rate: The rate in µL/s to aspirate at. flow_rate: Not used in this core. - in_place: Used only for the engine core. + in_place: Whether we should move_to location. """ if self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any @@ -102,7 +102,7 @@ def aspirate( ) self.prepare_for_aspirate() self.move_to(location=location) - elif location != self._protocol_interface.get_last_location(): + elif not in_place: self.move_to(location=location) self._protocol_interface.get_hardware().aspirate(self._mount, volume, rate) @@ -123,9 +123,10 @@ def dispense( well_core: The well to dispense to, if applicable. rate: The rate in µL/s to dispense at. flow_rate: Not used in this core. - in_place: Used only for the engine core. + in_place: Whether we should move_to location. """ - self.move_to(location=location) + if not in_place: + self.move_to(location=location) self._protocol_interface.get_hardware().dispense(self._mount, volume, rate) @@ -140,9 +141,9 @@ def blow_out( Args: location: The location to blow out into. well_core: Unused by legacy core. - in_place: Used only for the engine core. + in_place: Whether we should move_to location. """ - if location != self._protocol_interface.get_last_location(): + if not in_place: self.move_to(location=location) self._protocol_interface.get_hardware().blow_out(self._mount) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index e1e258c3f9f..af756b39cf4 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -247,7 +247,7 @@ def validate_location( return WellTarget(well=target_location, location=None) _, well = target_location.labware.get_parent_labware_and_well() - in_place = target_location is last_location + in_place = target_location == last_location return ( WellTarget(well=well, location=target_location) From 321d38be185ffc450a0043cfa660226c581faca4 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 16 Feb 2023 16:37:40 -0500 Subject: [PATCH 54/86] added in_place to wellTarget --- .../opentrons/protocol_api/instrument_context.py | 10 ++++------ api/src/opentrons/protocol_api/validation.py | 10 ++++++---- .../protocol_api/test_instrument_context.py | 14 +++++++------- .../opentrons/protocol_api/test_validation.py | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 04d99142b8a..a9ee4592c18 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -176,7 +176,6 @@ def aspirate( well: Optional[labware.Well] move_to_location: types.Location - aspirate_in_place: bool = False last_location = self._get_last_location_by_api_version() try: @@ -191,6 +190,7 @@ def aspirate( "knows where it is." ) from e + aspirate_in_place = target.in_place if isinstance(target, validation.WellTarget): move_to_location = target.location or target.well.bottom( z=self._well_bottom_clearances.aspirate @@ -199,7 +199,6 @@ def aspirate( if isinstance(target, validation.PointTarget): move_to_location = target.location well = None - aspirate_in_place = target.in_place if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( @@ -285,7 +284,7 @@ def dispense( ) well: Optional[labware.Well] = None last_location = self._get_last_location_by_api_version() - dispense_in_place: bool = False + try: target = validation.validate_location( location=location, last_location=last_location @@ -298,6 +297,7 @@ def dispense( "knows where it is." ) from e + dispense_in_place = target.in_place if isinstance(target, validation.WellTarget): well = target.well if target.location: @@ -311,7 +311,6 @@ def dispense( if isinstance(target, validation.PointTarget): move_to_location = target.location well = None - dispense_in_place = target.in_place if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( @@ -443,7 +442,6 @@ def blow_out( """ well: Optional[labware.Well] move_to_location: types.Location - blow_out_in_place: bool = False last_location = self._get_last_location_by_api_version() try: @@ -458,6 +456,7 @@ def blow_out( "knows where it is." ) from e + blow_out_in_place = target.in_place if isinstance(target, validation.WellTarget): if target.well.parent.is_tiprack: _log.warning( @@ -469,7 +468,6 @@ def blow_out( elif isinstance(target, validation.PointTarget): move_to_location = target.location well = None - blow_out_in_place = target.in_place with publisher.publish_context( broker=self.broker, diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index af756b39cf4..5e71d90a9c8 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -203,13 +203,14 @@ class WellTarget(NamedTuple): well: Well location: Optional[Location] + in_place: bool class PointTarget(NamedTuple): """A movement to coordinates""" location: Location - in_place: bool = False + in_place: bool class NoLocationError(ValueError): @@ -243,14 +244,15 @@ def validate_location( "location should be a Well or Location, but it is {}".format(location) ) + in_place = target_location == last_location + if isinstance(target_location, Well): - return WellTarget(well=target_location, location=None) + return WellTarget(well=target_location, location=None, in_place=in_place) _, well = target_location.labware.get_parent_labware_and_well() - in_place = target_location == last_location return ( - WellTarget(well=well, location=target_location) + WellTarget(well=well, location=target_location, in_place=in_place) if well is not None else PointTarget(location=target_location, in_place=in_place) ) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 8d58bbdf37f..7bf86eedc3a 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -228,7 +228,7 @@ def test_aspirate( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(WellTarget(well=mock_well, location=None)) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) @@ -266,7 +266,7 @@ def test_aspirate_well_location( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(WellTarget(well=mock_well, location=input_location)) + ).then_return(WellTarget(well=mock_well, location=input_location, in_place=False)) decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) subject.aspirate(volume=42.0, location=input_location, rate=1.23) @@ -338,7 +338,7 @@ def test_aspirate_in_place( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(PointTarget(location=input_location)) + ).then_return(PointTarget(location=input_location, in_place=False)) decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) subject.aspirate(volume=42.0, location=input_location, rate=1.23) @@ -410,7 +410,7 @@ def test_blow_out_to_well( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(WellTarget(well=mock_well, location=None)) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) decoy.when(mock_well.top()).then_return(top_location) subject.blow_out(location=input_location) @@ -441,7 +441,7 @@ def test_blow_out_to_well_location( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(WellTarget(well=mock_well, location=input_location)) + ).then_return(WellTarget(well=mock_well, location=input_location, in_place=False)) subject.blow_out(location=input_location) decoy.verify( @@ -787,7 +787,7 @@ def test_dispense_with_well_location( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(WellTarget(well=mock_well, location=input_location)) + ).then_return(WellTarget(well=mock_well, location=input_location, in_place=False)) decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(3.0) subject.dispense(volume=42.0, location=input_location, rate=1.23) @@ -825,7 +825,7 @@ def test_dispense_with_well( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(WellTarget(well=mock_well, location=None)) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 75dd31e0620..95774a53006 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -256,7 +256,7 @@ def test_ensure_valid_labware_offset_vector(offset: Dict[str, float]) -> None: def test_validate_well_no_location(decoy: Decoy) -> None: """Should return a WellTarget with no location.""" input_location = decoy.mock(cls=Well) - expected_result = subject.WellTarget(well=input_location, location=None) + expected_result = subject.WellTarget(well=input_location, location=None, in_place=False) result = subject.validate_location(location=input_location, last_location=None) @@ -287,7 +287,7 @@ def test_validate_location_with_well(decoy: Decoy) -> None: """Should return a WellTarget with location.""" mock_well = decoy.mock(cls=Well) input_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) - expected_result = subject.WellTarget(well=mock_well, location=input_location) + expected_result = subject.WellTarget(well=mock_well, location=input_location, in_place=False) result = subject.validate_location(location=input_location, last_location=None) @@ -298,7 +298,7 @@ def test_validate_last_location(decoy: Decoy) -> None: """Should return a WellTarget with location.""" mock_well = decoy.mock(cls=Well) input_last_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) - expected_result = subject.WellTarget(well=mock_well, location=input_last_location) + expected_result = subject.WellTarget(well=mock_well, location=input_last_location, in_place=True) result = subject.validate_location(location=None, last_location=input_last_location) From a59d08ff6044d29d494425ff27f8b6edf29914a5 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 16:42:03 -0500 Subject: [PATCH 55/86] Update api/src/opentrons/protocol_api/core/engine/instrument.py --- api/src/opentrons/protocol_api/core/engine/instrument.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index d70176b8128..ec4f5bf5829 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -93,7 +93,7 @@ def aspirate( well_core: The well to aspirate from, if applicable. rate: Not used in this core. flow_rate: The flow rate in µL/s to aspirate at. - in_place: whether this an in-place command. + in_place: whether this is a in-place command. """ if well_core is None: if not in_place: From 24ed944795ef2362477122cba53e432befaaa641 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 16:42:32 -0500 Subject: [PATCH 56/86] Update api/src/opentrons/protocol_api/core/engine/instrument.py --- api/src/opentrons/protocol_api/core/engine/instrument.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index ec4f5bf5829..1e69bea5799 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -150,7 +150,7 @@ def dispense( well_core: The well to dispense to, if applicable. rate: Not used in this core. flow_rate: The flow rate in µL/s to dispense at. - in_place: whether this an in-place command. + in_place: whether this is a in-place command. """ if well_core is None: if not in_place: From 6b518c494f9540a00a222e96adffaab98d4b4686 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 16:43:08 -0500 Subject: [PATCH 57/86] Update api/src/opentrons/protocol_api/core/engine/instrument.py --- api/src/opentrons/protocol_api/core/engine/instrument.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 1e69bea5799..7772fcbeb15 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -198,7 +198,7 @@ def blow_out( Args: location: The location to blow out into. well_core: The well to blow out into. - in_place: whether this an in-place command. + in_place: whether this is a in-place command. """ flow_rate = self.get_blow_out_flow_rate(1.0) if well_core is None: From 1bfdaa0a62265155591038877d8c97a6f9279a83 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Thu, 16 Feb 2023 16:45:38 -0500 Subject: [PATCH 58/86] Update api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py --- .../core/legacy_simulator/legacy_instrument_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 07912826fd6..8a719298c8d 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -129,7 +129,7 @@ def blow_out( well_core: Optional[LegacyWellCore], in_place: bool, ) -> None: - if location != self._protocol_interface.get_last_location(): + if not in_place: self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.BLOWOUT.name) self._update_volume(0) From aa57d8ec4216563297f406b0d03f4e2e5398eb02 Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Thu, 16 Feb 2023 16:56:01 -0500 Subject: [PATCH 59/86] simulator dispense fix --- .../core/legacy_simulator/legacy_instrument_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 8a719298c8d..ac71b590502 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -119,7 +119,8 @@ def dispense( flow_rate: float, in_place: bool, ) -> None: - self.move_to(location=location, well_core=well_core) + if not in_place: + self.move_to(location=location, well_core=well_core) self._raise_if_no_tip(HardwareAction.DISPENSE.name) self._update_volume(self.get_current_volume() - volume) From 2f877b970b7412b9c015a7ffe2ee763e320aefb9 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 09:52:18 -0500 Subject: [PATCH 60/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a9ee4592c18..ebdde784e2a 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -190,7 +190,6 @@ def aspirate( "knows where it is." ) from e - aspirate_in_place = target.in_place if isinstance(target, validation.WellTarget): move_to_location = target.location or target.well.bottom( z=self._well_bottom_clearances.aspirate From c773f4bd67d9700531b9d421c4844abf95687430 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 09:52:33 -0500 Subject: [PATCH 61/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index ebdde784e2a..49fd4c3ad1b 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -224,7 +224,7 @@ def aspirate( volume=c_vol, rate=rate, flow_rate=flow_rate, - in_place=aspirate_in_place, + in_place=target.in_place, ) return self From a73c6c43befb75b82564f55ddc5cbfad7f43d911 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 09:55:34 -0500 Subject: [PATCH 62/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 49fd4c3ad1b..1fbff30f236 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -296,7 +296,6 @@ def dispense( "knows where it is." ) from e - dispense_in_place = target.in_place if isinstance(target, validation.WellTarget): well = target.well if target.location: From 6cfd7094cd96154593baee46aedf957688716a0b Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 09:56:21 -0500 Subject: [PATCH 63/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 1fbff30f236..f72a51e2982 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -336,7 +336,7 @@ def dispense( location=move_to_location, well_core=well._core if well is not None else None, flow_rate=flow_rate, - in_place=dispense_in_place, + in_place=target.in_place, ) return self From 5ab87ceaa715b4810f408f6e75e28b75d50e5618 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 09:56:33 -0500 Subject: [PATCH 64/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index f72a51e2982..a692f397f26 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -454,7 +454,6 @@ def blow_out( "knows where it is." ) from e - blow_out_in_place = target.in_place if isinstance(target, validation.WellTarget): if target.well.parent.is_tiprack: _log.warning( From 5a692fd3a07ccd6351fb79a6497accc0dad09cc0 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 09:57:55 -0500 Subject: [PATCH 65/86] Update api/tests/opentrons/protocol_api/test_validation.py Co-authored-by: Mike Cousins --- api/tests/opentrons/protocol_api/test_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 95774a53006..c073005067d 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -339,7 +339,7 @@ def test_validate_with_labware(decoy: Decoy) -> None: def test_validate_last_location_with_labware(decoy: Decoy) -> None: - """Should return a PointTarget for in_place commands.""" + """Should return a PointTarget for non-Well previous Location.""" mock_labware = decoy.mock(cls=Labware) input_last_location = Location(point=Point(1, 1, 1), labware=mock_labware) From 5443d9c568554e69465514f35ccad001ba3a3a63 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 10:01:04 -0500 Subject: [PATCH 66/86] Update api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py Co-authored-by: Mike Cousins --- .../opentrons/protocol_api/core/engine/test_instrument_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index b55fd1f90f2..6ba2e2a53dd 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -339,6 +339,7 @@ def test_aspirate_from_location( volume=12.34, flow_rate=7.8, ), + mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), ) From 9020b3000ea4334b9ca48e06f3a814b902c7c9ec Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 10:13:59 -0500 Subject: [PATCH 67/86] Update api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py Co-authored-by: Mike Cousins --- .../opentrons/protocol_api/core/engine/test_instrument_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 6ba2e2a53dd..0d8ff32749f 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -366,6 +366,7 @@ def test_aspirate_in_place( volume=12.34, flow_rate=7.8, ), + mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), ) From bf33a188d3d0a843415968ae3c9c1285fc064a32 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 12:34:17 -0500 Subject: [PATCH 68/86] Update api/tests/opentrons/protocol_api/test_validation.py Co-authored-by: Mike Cousins --- api/tests/opentrons/protocol_api/test_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index c073005067d..6fd2ebf19d0 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -329,7 +329,7 @@ def test_validate_raises_no_location_error() -> None: def test_validate_with_labware(decoy: Decoy) -> None: - """Should return a PointTarget for none in_place commands.""" + """Should return a PointTarget for a non-Well Location.""" mock_labware = decoy.mock(cls=Labware) input_location = Location(point=Point(1, 1, 1), labware=mock_labware) From 1d97ce35e6e2354390811b293a2ee3c75afc82bf Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 12:38:08 -0500 Subject: [PATCH 69/86] Update api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py Co-authored-by: Mike Cousins --- .../protocol_api_old/core/simulator/test_instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index 389a9a465a4..d77a0ca861a 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -276,7 +276,7 @@ def _aspirate_blowout(i: InstrumentCore, labware: LabwareCore) -> None: i.blow_out( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), - in_place=False, + in_place=True, ) From 4b5ac65ad17f5bea77d434ea87c2b7953e40a387 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 12:47:41 -0500 Subject: [PATCH 70/86] Update api/tests/opentrons/protocol_api/test_validation.py Co-authored-by: Mike Cousins --- api/tests/opentrons/protocol_api/test_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 6fd2ebf19d0..e534152680e 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -274,7 +274,7 @@ def test_validate_coordinates(decoy: Decoy) -> None: def test_validate_in_place(decoy: Decoy) -> None: - """Should return a WellTarget with no location.""" + """Should return an `in_place` PointTarget.""" input_last_location = Location(point=Point(x=1, y=1, z=2), labware=None) expected_result = subject.PointTarget(location=input_last_location, in_place=True) From 4cafd324608c2748c53ff2384dd00f9c25f126d3 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:01:22 -0500 Subject: [PATCH 71/86] Update api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py Co-authored-by: Mike Cousins --- .../opentrons/protocol_api/core/engine/test_instrument_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 0d8ff32749f..5d2b4e689d1 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -405,7 +405,7 @@ def test_blow_out_to_well( ) -def test_blow_out_in_place( +def test_blow_to_coordinates( decoy: Decoy, mock_engine_client: EngineClient, mock_protocol_core: ProtocolCore, From dd97a1406d3614f41f0fbf48f3cd6a290b0c362d Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:01:55 -0500 Subject: [PATCH 72/86] Update api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py Co-authored-by: Mike Cousins --- .../opentrons/protocol_api/core/engine/test_instrument_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 5d2b4e689d1..4467eb992b1 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -501,7 +501,7 @@ def test_dispense_in_place( ) -def test_dispense_in_coordinates( +def test_dispense_to_coordinates( decoy: Decoy, mock_engine_client: EngineClient, mock_protocol_core: ProtocolCore, From e38b4744d2bd84f9b39851b538a46d1ef27a3305 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:03:21 -0500 Subject: [PATCH 73/86] Update api/tests/opentrons/protocol_api/test_instrument_context.py Co-authored-by: Mike Cousins --- api/tests/opentrons/protocol_api/test_instrument_context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 7bf86eedc3a..9059411f2ca 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -302,7 +302,7 @@ def test_aspirate_from_coordinates( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(PointTarget(location=input_location, in_place=False)) + ).then_return(PointTarget(location=input_location, in_place=True)) decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) subject.aspirate(volume=42.0, location=input_location, rate=1.23) @@ -311,7 +311,7 @@ def test_aspirate_from_coordinates( mock_instrument_core.aspirate( location=input_location, well_core=None, - in_place=False, + in_place=True, volume=42.0, rate=1.23, flow_rate=5.67, From 6c152fa8341574cd9de71a6019031a2af7541ab4 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:18:27 -0500 Subject: [PATCH 74/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a692f397f26..2c8b1c6a6fa 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -473,7 +473,7 @@ def blow_out( self._core.blow_out( location=move_to_location, well_core=well._core if well is not None else None, - in_place=blow_out_in_place, + in_place=target.in_place, ) return self From 2601ded4fe3d06134e120f010a3a2b524c7afa86 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:19:00 -0500 Subject: [PATCH 75/86] Update api/src/opentrons/protocol_engine/commands/aspirate_in_place.py Co-authored-by: Mike Cousins --- .../opentrons/protocol_engine/commands/aspirate_in_place.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index d372c19a5fb..d9f7d56541a 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -1,9 +1,5 @@ """Aspirate in place command request, result, and implementation models.""" -# TODO(mm, 2022-08-15): This command is not yet in the JSON protocol schema. -# Before our production code emits this command, we must add it to the schema, -# and probably bump the schema version. - from __future__ import annotations from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal From 51841b5dee962d2aa0e2543762ea4f20532abdb2 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:19:42 -0500 Subject: [PATCH 76/86] Update api/src/opentrons/protocol_engine/commands/blow_out_in_place.py Co-authored-by: Mike Cousins --- .../opentrons/protocol_engine/commands/blow_out_in_place.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py index d759b2a3cb0..ec5effdc236 100644 --- a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py @@ -1,9 +1,5 @@ """Blow-out in place command request, result, and implementation models.""" -# TODO(mm, 2022-08-15): This command is not yet in the JSON protocol schema. -# Before our production code emits this command, we must add it to the schema, -# and probably bump the schema version. - from __future__ import annotations from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal From f720ee7cb5aaf5da7b385d9f9aae3e1667efebe2 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:20:05 -0500 Subject: [PATCH 77/86] Update api/src/opentrons/protocol_api/validation.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 5e71d90a9c8..e49efbae8f0 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -241,7 +241,7 @@ def validate_location( if not isinstance(target_location, (Location, Well)): raise LocationTypeError( - "location should be a Well or Location, but it is {}".format(location) + f"location should be a Well or Location, but it is {location}"``` ) in_place = target_location == last_location From a7ad08a25744d766292ff6e4de7610ee731c1957 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:20:50 -0500 Subject: [PATCH 78/86] Update api/src/opentrons/protocol_api/validation.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/validation.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index e49efbae8f0..39d90ec5ed5 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -224,10 +224,16 @@ class LocationTypeError(TypeError): def validate_location( location: Union[Location, Well, None], last_location: Optional[Location] ) -> Union[WellTarget, PointTarget]: - """Validate and return if should use a WellTarget or a PointTarget. + """Validate a given location for a liquid handling command. + Args: location: The input location. - last_location: The cached last location. + last_location: The last location accessed by the pipette. + + Returns: + A `WellTarget` if the input location represents a well. + A `PointTarget` if the input location is an x, y, z coordinate. + Raises: NoLocationError: The is no input location and no cached loaction. LocationTypeError: The location supplied is of unexpected type. From 2b7865be5577a504a8ed642602107ab2537a67cb Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:21:46 -0500 Subject: [PATCH 79/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 2c8b1c6a6fa..3da8043bcf9 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -1508,6 +1508,11 @@ def well_bottom_clearance(self) -> "Clearances": return self._well_bottom_clearances def _get_last_location_by_api_version(self) -> Optional[types.Location]: + """Get the last location accessed by this pipette, if any. + + In pre-engine Protocol API versions, this call omits the pipette mount. + This is to preserve pre-existing, potentially buggy behavior. + """ if self._api_version >= ENGINE_CORE_API_VERSION: return self._protocol_core.get_last_location(mount=self._core.get_mount()) else: From 51e010102ffe7e49f9ee1f98ae958e3073ee8cc9 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:22:02 -0500 Subject: [PATCH 80/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 3da8043bcf9..e6a9e1741fd 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -197,7 +197,6 @@ def aspirate( well = target.well if isinstance(target, validation.PointTarget): move_to_location = target.location - well = None if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( From 99f4bb1337d32788505159bc979f26780207ea34 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:22:32 -0500 Subject: [PATCH 81/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index e6a9e1741fd..189114e34d0 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -174,7 +174,7 @@ def aspirate( ) ) - well: Optional[labware.Well] + well: Optional[labware.Well] = None move_to_location: types.Location last_location = self._get_last_location_by_api_version() From a9a4ae19735de4ec14fea702b92e211619723724 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:22:55 -0500 Subject: [PATCH 82/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 189114e34d0..77e34357e81 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -307,7 +307,6 @@ def dispense( ) if isinstance(target, validation.PointTarget): move_to_location = target.location - well = None if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( From 6b42ec4401b9a71088f3a58477bbcb9de7d97378 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:23:11 -0500 Subject: [PATCH 83/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 77e34357e81..a2a6581590f 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -436,7 +436,7 @@ def blow_out( :py:meth:`dispense`) :returns: This instance """ - well: Optional[labware.Well] + well: Optional[labware.Well] = None move_to_location: types.Location last_location = self._get_last_location_by_api_version() From 8a7d24b9e6beb3c11690f4177d1064c937546660 Mon Sep 17 00:00:00 2001 From: TamarZanzouri Date: Fri, 17 Feb 2023 13:23:23 -0500 Subject: [PATCH 84/86] Update api/src/opentrons/protocol_api/instrument_context.py Co-authored-by: Mike Cousins --- api/src/opentrons/protocol_api/instrument_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a2a6581590f..f5147d51f4a 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -462,7 +462,6 @@ def blow_out( well = target.well elif isinstance(target, validation.PointTarget): move_to_location = target.location - well = None with publisher.publish_context( broker=self.broker, From c2d62a02301fa123ba234baf86ddaa0a7793909e Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 17 Feb 2023 13:29:10 -0500 Subject: [PATCH 85/86] pr fixes --- .../protocol_api/test_instrument_context.py | 156 +----------------- .../opentrons/protocol_api/test_validation.py | 12 +- .../core/simulator/test_instrument_context.py | 3 +- 3 files changed, 13 insertions(+), 158 deletions(-) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 9059411f2ca..f0243d6600e 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -320,42 +320,6 @@ def test_aspirate_from_coordinates( ) -def test_aspirate_in_place( - decoy: Decoy, - mock_instrument_core: InstrumentCore, - subject: InstrumentContext, - mock_protocol_core: ProtocolCore, -) -> None: - """It should aspirate in place.""" - input_location = Location(point=Point(2, 2, 2), labware=None) - last_location = Location(point=Point(9, 9, 9), labware=None) - decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - - decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( - last_location - ) - decoy.when( - mock_validation.validate_location( - location=input_location, last_location=last_location - ) - ).then_return(PointTarget(location=input_location, in_place=False)) - decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) - - subject.aspirate(volume=42.0, location=input_location, rate=1.23) - - decoy.verify( - mock_instrument_core.aspirate( - location=input_location, - well_core=None, - in_place=False, - volume=42.0, - rate=1.23, - flow_rate=5.67, - ), - times=1, - ) - - def test_aspirate_raises_no_location( decoy: Decoy, mock_instrument_core: InstrumentCore, @@ -373,23 +337,6 @@ def test_aspirate_raises_no_location( subject.aspirate(location=None) -def test_aspirate_raises_wrong_location_value( - decoy: Decoy, - mock_instrument_core: InstrumentCore, - subject: InstrumentContext, - mock_protocol_core: ProtocolCore, -) -> None: - """Should raise a TypeError.""" - decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) - - decoy.when( - mock_validation.validate_location(location=None, last_location=None) - ).then_raise(mock_validation.LocationTypeError()) - with pytest.raises(TypeError): - subject.aspirate(location=None) - - def test_blow_out_to_well( decoy: Decoy, mock_instrument_core: InstrumentCore, @@ -478,34 +425,7 @@ def test_blow_out_to_location( decoy.verify( mock_instrument_core.blow_out( - location=input_location, well_core=None, in_place=False - ), - times=1, - ) - - -def test_blow_out_in_place( - decoy: Decoy, - mock_instrument_core: InstrumentCore, - mock_protocol_core: ProtocolCore, - subject: InstrumentContext, -) -> None: - """It should blow out in place.""" - last_location = Location(point=Point(9, 9, 9), labware=None) - decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - - decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( - last_location - ) - decoy.when( - mock_validation.validate_location(location=None, last_location=last_location) - ).then_return(PointTarget(location=last_location, in_place=True)) - - subject.blow_out() - - decoy.verify( - mock_instrument_core.blow_out( - location=last_location, well_core=None, in_place=True + location=input_location, well_core=None, in_place=True ), times=1, ) @@ -528,23 +448,6 @@ def test_blow_out_raises_no_location( subject.blow_out(location=None) -def test_blow_out_raises_wrong_location_value( - decoy: Decoy, - mock_instrument_core: InstrumentCore, - subject: InstrumentContext, - mock_protocol_core: ProtocolCore, -) -> None: - """Should raise a TypeError.""" - decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) - - decoy.when( - mock_validation.validate_location(location=None, last_location=None) - ).then_raise(mock_validation.LocationTypeError()) - with pytest.raises(TypeError): - subject.blow_out(location=None) - - def test_pick_up_tip_from_labware( decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext ) -> None: @@ -750,7 +653,7 @@ def test_dispense_with_location( mock_validation.validate_location( location=input_location, last_location=last_location ) - ).then_return(PointTarget(location=input_location, in_place=False)) + ).then_return(PointTarget(location=input_location, in_place=True)) decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) subject.dispense(volume=42.0, location=input_location, rate=1.23) @@ -759,7 +662,7 @@ def test_dispense_with_location( mock_instrument_core.dispense( location=input_location, well_core=None, - in_place=False, + in_place=True, volume=42.0, rate=1.23, flow_rate=5.67, @@ -844,42 +747,6 @@ def test_dispense_with_well( ) -def test_dispense_in_place( - decoy: Decoy, - mock_instrument_core: InstrumentCore, - subject: InstrumentContext, - mock_protocol_core: ProtocolCore, -) -> None: - """It should dispense in place.""" - input_location = Location(point=Point(2, 2, 2), labware=None) - last_location = Location(point=Point(9, 9, 9), labware=None) - decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - - decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( - last_location - ) - decoy.when( - mock_validation.validate_location( - location=input_location, last_location=last_location - ) - ).then_return(PointTarget(location=input_location, in_place=True)) - decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) - - subject.dispense(volume=42.0, location=input_location, rate=1.23) - - decoy.verify( - mock_instrument_core.dispense( - location=input_location, - well_core=None, - in_place=True, - volume=42.0, - rate=1.23, - flow_rate=5.67, - ), - times=1, - ) - - def test_dispense_raises_no_location( decoy: Decoy, mock_instrument_core: InstrumentCore, @@ -897,23 +764,6 @@ def test_dispense_raises_no_location( subject.dispense(location=None) -def test_dispense_raises_wrong_location_value( - decoy: Decoy, - mock_instrument_core: InstrumentCore, - subject: InstrumentContext, - mock_protocol_core: ProtocolCore, -) -> None: - """Should raise a TypeError.""" - decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) - decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return(None) - - decoy.when( - mock_validation.validate_location(location=None, last_location=None) - ).then_raise(mock_validation.LocationTypeError()) - with pytest.raises(TypeError): - subject.dispense(location=None) - - def test_touch_tip( decoy: Decoy, mock_instrument_core: InstrumentCore, diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index e534152680e..bded885a740 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -256,7 +256,9 @@ def test_ensure_valid_labware_offset_vector(offset: Dict[str, float]) -> None: def test_validate_well_no_location(decoy: Decoy) -> None: """Should return a WellTarget with no location.""" input_location = decoy.mock(cls=Well) - expected_result = subject.WellTarget(well=input_location, location=None, in_place=False) + expected_result = subject.WellTarget( + well=input_location, location=None, in_place=False + ) result = subject.validate_location(location=input_location, last_location=None) @@ -287,7 +289,9 @@ def test_validate_location_with_well(decoy: Decoy) -> None: """Should return a WellTarget with location.""" mock_well = decoy.mock(cls=Well) input_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) - expected_result = subject.WellTarget(well=mock_well, location=input_location, in_place=False) + expected_result = subject.WellTarget( + well=mock_well, location=input_location, in_place=False + ) result = subject.validate_location(location=input_location, last_location=None) @@ -298,7 +302,9 @@ def test_validate_last_location(decoy: Decoy) -> None: """Should return a WellTarget with location.""" mock_well = decoy.mock(cls=Well) input_last_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) - expected_result = subject.WellTarget(well=mock_well, location=input_last_location, in_place=True) + expected_result = subject.WellTarget( + well=mock_well, location=input_last_location, in_place=True + ) result = subject.validate_location(location=None, last_location=input_last_location) diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index d77a0ca861a..71e7f7c3211 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -70,12 +70,11 @@ def test_drop_tip_no_tip(subject: InstrumentCore, tip_rack: LabwareCore) -> None def test_blow_out_no_tip(subject: InstrumentCore, labware: LabwareCore) -> None: """It should raise an error if a tip is not attached.""" - subject.home() with pytest.raises(NoTipAttachedError, match="Cannot perform BLOWOUT"): subject.blow_out( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), - in_place=False, + in_place=True, ) From 16b6fba2ca9aded4e973a830457af8e0bc62f56e Mon Sep 17 00:00:00 2001 From: tamarzanzouri Date: Fri, 17 Feb 2023 13:49:41 -0500 Subject: [PATCH 86/86] pr fixes --- .../protocol_api/instrument_context.py | 2 +- api/src/opentrons/protocol_api/validation.py | 8 +++---- .../core/engine/test_instrument_core.py | 24 ++++++++++++++++++- .../protocol_api/test_instrument_context.py | 2 +- .../opentrons/protocol_api/test_validation.py | 16 +++++++++++++ 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index f5147d51f4a..5cc4352d099 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -1506,7 +1506,7 @@ def well_bottom_clearance(self) -> "Clearances": def _get_last_location_by_api_version(self) -> Optional[types.Location]: """Get the last location accessed by this pipette, if any. - + In pre-engine Protocol API versions, this call omits the pipette mount. This is to preserve pre-existing, potentially buggy behavior. """ diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 39d90ec5ed5..c4d7f897248 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -225,15 +225,15 @@ def validate_location( location: Union[Location, Well, None], last_location: Optional[Location] ) -> Union[WellTarget, PointTarget]: """Validate a given location for a liquid handling command. - + Args: location: The input location. last_location: The last location accessed by the pipette. - + Returns: A `WellTarget` if the input location represents a well. A `PointTarget` if the input location is an x, y, z coordinate. - + Raises: NoLocationError: The is no input location and no cached loaction. LocationTypeError: The location supplied is of unexpected type. @@ -247,7 +247,7 @@ def validate_location( if not isinstance(target_location, (Location, Well)): raise LocationTypeError( - f"location should be a Well or Location, but it is {location}"``` + f"location should be a Well or Location, but it is {location}" ) in_place = target_location == last_location diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 4467eb992b1..709db0190dd 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -411,7 +411,7 @@ def test_blow_to_coordinates( mock_protocol_core: ProtocolCore, subject: InstrumentCore, ) -> None: - """It should blow out in place.""" + """It should move to coordinate and blow out in place.""" location = Location(point=Point(1, 2, 3), labware=None) subject.blow_out(location=location, well_core=None, in_place=False) @@ -432,6 +432,28 @@ def test_blow_to_coordinates( ) +def test_blow_out_in_place( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, +) -> None: + """Should blow-out in place.""" + location = Location(point=Point(1, 2, 3), labware=None) + subject.blow_out( + location=location, + well_core=None, + in_place=True, + ) + + decoy.verify( + mock_engine_client.blow_out_in_place( + pipette_id="abc123", + flow_rate=6.7, + ), + ) + + def test_dispense_to_well( decoy: Decoy, mock_engine_client: EngineClient, diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index f0243d6600e..e327f545de9 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -409,7 +409,7 @@ def test_blow_out_to_location( mock_well = decoy.mock(cls=Well) input_location = Location(point=Point(2, 2, 2), labware=mock_well) last_location = Location(point=Point(9, 9, 9), labware=None) - point_target = PointTarget(location=input_location, in_place=False) + point_target = PointTarget(location=input_location, in_place=True) decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index bded885a740..5fbc7f89741 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -311,6 +311,22 @@ def test_validate_last_location(decoy: Decoy) -> None: assert result == expected_result +def test_validate_location_matches_last_location(decoy: Decoy) -> None: + """Should return an in_place WellTarget.""" + mock_well = decoy.mock(cls=Well) + input_last_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) + input_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) + expected_result = subject.WellTarget( + well=mock_well, location=input_last_location, in_place=True + ) + + result = subject.validate_location( + location=input_location, last_location=input_last_location + ) + + assert result == expected_result + + def test_validate_with_wrong_location_with_last_location() -> None: """Should raise a LocationTypeError.""" with pytest.raises(subject.LocationTypeError):