-
Notifications
You must be signed in to change notification settings - Fork 199
feat(api): add custom liquid class creator #18416
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
86cb9ec
091c628
ccffefa
8e9203d
4ae503b
c3a1e27
214b345
94c0915
395ae24
42fe83c
20c56fa
8665c68
7697315
1b81fcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from typing import Optional, Dict, Union, TYPE_CHECKING | ||
| from typing import Optional, Dict, Union, TYPE_CHECKING, Tuple | ||
|
|
||
| from opentrons_shared_data.liquid_classes.liquid_class_definition import ( | ||
| LiquidClassSchemaV1, | ||
|
|
@@ -63,6 +63,20 @@ def create(cls, liquid_class_definition: LiquidClassSchemaV1) -> "LiquidClass": | |
| _by_pipette_setting=by_pipette_settings, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def create_from( | ||
| cls, | ||
| name: str, | ||
| display_name: str, | ||
| by_pipette_setting: Dict[str, Dict[str, TransferProperties]], | ||
| ) -> "LiquidClass": | ||
| """Create a liquid class from the passed in args.""" | ||
| return cls( | ||
| _name=name, | ||
| _display_name=display_name, | ||
| _by_pipette_setting=by_pipette_setting, | ||
| ) | ||
|
|
||
| @property | ||
| def name(self) -> str: | ||
| return self._name | ||
|
|
@@ -71,10 +85,54 @@ def name(self) -> str: | |
| def display_name(self) -> str: | ||
| return self._display_name | ||
|
|
||
| def update_for( | ||
| self, | ||
| pipette: Union[str, InstrumentContext], | ||
| tip_rack: Union[str, Labware], | ||
| transfer_properties: TransferProperties, | ||
| ) -> None: | ||
| """Update the transfer properties for the given pipette and tip combo. | ||
|
|
||
| If an entry does not exist, it will be created. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this true? If
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops you are right. Will fix it |
||
| """ | ||
| pipette_name, tiprack_uri = self._get_pipette_and_tiprack_names( | ||
| pipette, tip_rack | ||
| ) | ||
| try: | ||
| self._by_pipette_setting[pipette_name].update( | ||
| {tiprack_uri: transfer_properties} | ||
| ) | ||
| except KeyError: | ||
| self._by_pipette_setting[pipette_name] = {tiprack_uri: transfer_properties} | ||
|
|
||
| def get_for( | ||
| self, pipette: Union[str, InstrumentContext], tip_rack: Union[str, Labware] | ||
| ) -> TransferProperties: | ||
| """Get liquid class transfer properties for the specified pipette and tip.""" | ||
| pipette_name, tiprack_uri = self._get_pipette_and_tiprack_names( | ||
| pipette, tip_rack | ||
| ) | ||
|
|
||
| try: | ||
| settings_for_pipette = self._by_pipette_setting[pipette_name] | ||
| except KeyError: | ||
| raise NoLiquidClassPropertyError( | ||
| f"No properties found for {pipette_name} in {self._name} liquid class" | ||
| ) | ||
| try: | ||
| transfer_properties = settings_for_pipette[tiprack_uri] | ||
| except KeyError: | ||
| raise NoLiquidClassPropertyError( | ||
| f"No properties found for {tiprack_uri} for {pipette_name} in {self._name} liquid class" | ||
| ) | ||
| return transfer_properties | ||
|
|
||
| @staticmethod | ||
| def _get_pipette_and_tiprack_names( | ||
| pipette: Union[str, InstrumentContext], | ||
| tip_rack: Union[str, Labware], | ||
| ) -> Tuple[str, str]: | ||
| """Return the pipette and tip rack name strings from the given pipette and tip rack.""" | ||
| from . import InstrumentContext, Labware | ||
|
|
||
| if isinstance(pipette, InstrumentContext): | ||
|
|
@@ -96,17 +154,4 @@ def get_for( | |
| f"{tip_rack} should either be a tiprack Labware object" | ||
| f" or a tiprack URI string." | ||
| ) | ||
|
|
||
| try: | ||
| settings_for_pipette = self._by_pipette_setting[pipette_name] | ||
| except KeyError: | ||
| raise NoLiquidClassPropertyError( | ||
| f"No properties found for {pipette_name} in {self._name} liquid class" | ||
| ) | ||
| try: | ||
| transfer_properties = settings_for_pipette[tiprack_uri] | ||
| except KeyError: | ||
| raise NoLiquidClassPropertyError( | ||
| f"No properties found for {tiprack_uri} for {pipette_name} in {self._name} liquid class" | ||
| ) | ||
| return transfer_properties | ||
| return pipette_name, tiprack_uri | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,6 @@ | |
| from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING | ||
|
|
||
| from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExist | ||
|
|
||
| from opentrons.protocol_engine import commands as cmd | ||
| from opentrons.protocol_engine.commands import LoadModuleResult | ||
|
|
||
| from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3 | ||
| from opentrons_shared_data.labware.labware_definition import ( | ||
| labware_definition_type_adapter, | ||
|
|
@@ -35,7 +31,8 @@ | |
| from opentrons.protocols.api_support.util import AxisMaxSpeeds | ||
| from opentrons.protocols.api_support.types import APIVersion | ||
|
|
||
|
|
||
| from opentrons.protocol_engine import commands as cmd | ||
| from opentrons.protocol_engine.commands import LoadModuleResult | ||
| from opentrons.protocol_engine import ( | ||
| DeckSlotLocation, | ||
| AddressableAreaLocation, | ||
|
|
@@ -1071,7 +1068,7 @@ def define_liquid( | |
| display_color=(liquid.displayColor.root if liquid.displayColor else None), | ||
| ) | ||
|
|
||
| def define_liquid_class(self, name: str, version: int = 1) -> LiquidClass: | ||
| def define_liquid_class(self, name: str, version: int) -> LiquidClass: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, why did you take out @jbleon95's default of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm passing the default version in the caller for |
||
| """Define a liquid class for use in transfer functions.""" | ||
| try: | ||
| # Check if we have already loaded this liquid class' definition | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from copy import deepcopy | ||
| from typing import ( | ||
| Callable, | ||
| Dict, | ||
|
|
@@ -13,6 +14,11 @@ | |
| ) | ||
|
|
||
| from opentrons_shared_data.labware.types import LabwareDefinition | ||
| from opentrons_shared_data.liquid_classes.liquid_class_definition import ( | ||
| TransferProperties as SharedTransferProperties, | ||
| ) | ||
| from opentrons_shared_data.liquid_classes import DEFAULT_LC_VERSION, definition_exists | ||
| from opentrons_shared_data.liquid_classes.types import TransferPropertiesDict | ||
| from opentrons_shared_data.pipette.types import PipetteNameType | ||
|
|
||
| from opentrons.types import Mount, Location, DeckLocation, DeckSlotName, StagingSlotName | ||
|
|
@@ -48,6 +54,7 @@ | |
| ) | ||
| from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated | ||
| from opentrons.protocol_engine.errors import LabwareMovementNotAllowedError | ||
| from ._liquid_properties import build_transfer_properties | ||
|
|
||
| from ._types import OffDeckType | ||
| from .core.common import ModuleCore, LabwareCore, ProtocolCore | ||
|
|
@@ -1376,7 +1383,67 @@ def define_liquid_class( | |
|
|
||
| :meta private: | ||
| """ | ||
| return self._core.define_liquid_class(name=name) | ||
| return self._core.define_liquid_class(name=name, version=DEFAULT_LC_VERSION) | ||
|
|
||
| @requires_version(2, 24) | ||
| def define_custom_liquid_class( | ||
| self, | ||
| name: str, | ||
| properties: Dict[str, Dict[str, TransferPropertiesDict]], | ||
| base_liquid_class: Optional[LiquidClass] = None, | ||
| display_name: Optional[str] = None, | ||
| ) -> LiquidClass: | ||
| """Define a custom liquid class, either a completely new one or based on an existing one. | ||
|
|
||
| Args: | ||
| name: The name to give to the new liquid class. Cannot use names of existing in-built liquid classes. | ||
| properties: A dict of transfer properties per tip per pipette. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, let's describe what pipette name and how the tiprack name should be formatted |
||
| Accepts a nested dictionary in the following format: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| { | ||
| <pipette_name>: { | ||
| <tiprack_uri>: <properties in the shape of TransferPropertiesDict> | ||
|
|
||
| # TransferPropertiesDict is a dictionary representation of the | ||
| # transfer properties returned by the `LiquidClass.get_for(..)` function. | ||
| }} | ||
|
|
||
| base_liquid_class: A LiquidClass to base this liquid class on. The properties | ||
| specified in transfer_properties will override any existing ones | ||
| for the specified pipettes & tips. | ||
| display_name: An optional human-readable name for the liquid. If not provided, | ||
| will default to title-cased name. | ||
|
|
||
| """ | ||
| if definition_exists(name, DEFAULT_LC_VERSION): | ||
| raise ValueError( | ||
| f"Liquid class named {name} already exists. Please specify a different name." | ||
| ) | ||
| new_liquid_class: LiquidClass | ||
| if base_liquid_class: | ||
| # If base liquid is provided, copy to new class | ||
| # and replace the entries mentioned in transfer props arg | ||
| new_liquid_class = deepcopy(base_liquid_class) | ||
| else: | ||
| new_liquid_class = LiquidClass.create_from( | ||
| name=name, | ||
| display_name=display_name or name.title(), | ||
| by_pipette_setting={}, | ||
| ) | ||
| for pipette, by_tiprack_props in properties.items(): | ||
| for tiprack, transfer_props in by_tiprack_props.items(): | ||
| new_liquid_class.update_for( | ||
| pipette=pipette, | ||
| tip_rack=tiprack, | ||
| transfer_properties=build_transfer_properties( | ||
| transfer_properties=SharedTransferProperties.model_validate( | ||
| transfer_props | ||
| ) | ||
| ), | ||
| ) | ||
| return new_liquid_class | ||
|
|
||
| @property | ||
| @requires_version(2, 5) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include in the doc-string (or a comment) that this is keyed by pipette name and tiprack and the specific variety of strings we use for those?