From 56166f646397a682ac31a538bf34adfe880ce24e Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 21 Nov 2022 17:17:13 -0500 Subject: [PATCH 1/7] feat(shared-data): properly load new pipette configurations from shared data We have a completely new shape for pipette configurations that need to be loaded in. --- .../opentrons/config/ot3_pipette_config.py | 193 ++++++++++++++++++ .../config/test_ot3_pipette_config.py | 31 +++ .../pipette/constants.py | 8 + .../pipette/load_data.py | 68 ++++++ .../pipette/pipette_definition.py | 142 +++++++++++++ .../opentrons_shared_data/pipette/types.py | 64 ++++++ .../python/tests/pipette/test_load_data.py | 27 +++ .../python/tests/pipette/test_types.py | 41 ++++ 8 files changed, 574 insertions(+) create mode 100644 api/src/opentrons/config/ot3_pipette_config.py create mode 100644 api/tests/opentrons/config/test_ot3_pipette_config.py create mode 100644 shared-data/python/opentrons_shared_data/pipette/constants.py create mode 100644 shared-data/python/opentrons_shared_data/pipette/load_data.py create mode 100644 shared-data/python/opentrons_shared_data/pipette/pipette_definition.py create mode 100644 shared-data/python/opentrons_shared_data/pipette/types.py create mode 100644 shared-data/python/tests/pipette/test_load_data.py create mode 100644 shared-data/python/tests/pipette/test_types.py diff --git a/api/src/opentrons/config/ot3_pipette_config.py b/api/src/opentrons/config/ot3_pipette_config.py new file mode 100644 index 00000000000..878a3a68b70 --- /dev/null +++ b/api/src/opentrons/config/ot3_pipette_config.py @@ -0,0 +1,193 @@ +from typing import List, Tuple, Dict +from dataclasses import dataclass, field +from opentrons_shared_data.pipette import load_data, pipette_definition +from opentrons_shared_data.pipette.types import ( + PipetteTipType, + PipetteChannelType, + PipetteModelType, + PipetteVersionType, +) + +DEFAULT_CALIBRATION_OFFSET = [0.0, 0.0, 0.0] + + +@dataclass(frozen=True) +class PartialTipConfigurations: + supported: bool + configurations: List[int] = field(default_factory=list) + + @classmethod + def from_pydantic( + cls, configs: pipette_definition.PartialTipDefinition + ) -> "PartialTipConfigurations": + return cls( + supported=configs.partialTipSupported, + configurations=configs.availableConfigurations, + ) + + +@dataclass(frozen=True) +class TipMotorConfigurations: + current: float + speed: float + + @classmethod + def from_pydantic( + cls, configs: pipette_definition.TipHandlingConfigurations + ) -> "TipMotorConfigurations": + return cls(current=configs.current, speed=configs.speed) + + +@dataclass(frozen=True) +class PickUpTipConfigurations: + current: float + speed: float + increment: float + distance: float + presses: int + + @classmethod + def from_pydantic( + cls, configs: pipette_definition.PickUpTipConfigurations + ) -> "PickUpTipConfigurations": + return cls( + current=configs.current, + speed=configs.speed, + increment=configs.increment, + distance=configs.distance, + presses=configs.presses, + ) + + +@dataclass(frozen=True) +class PlungerMotorCurrent: + idle: float + run: float + + @classmethod + def from_pydantic( + cls, configs: pipette_definition.MotorConfigurations + ) -> "PlungerMotorCurrent": + return cls(idle=configs.idle, run=configs.run) + + +@dataclass(frozen=True) +class PlungerPositions: + top: float + bottom: float + blowout: float + drop: float + + @classmethod + def from_pydantic( + cls, configs: pipette_definition.PlungerPositions + ) -> "PlungerPositions": + return cls( + top=configs.top, + bottom=configs.bottom, + blowout=configs.blowout, + drop=configs.drop, + ) + + +@dataclass(frozen=True) +class TipSpecificConfigurations: + default_aspirate_flowrate: float + default_dispense_flowrate: float + default_blowout_flowrate: float + aspirate: Dict[str, List[Tuple[float, float, float]]] + dispense: Dict[str, List[Tuple[float, float, float]]] + + @classmethod + def from_pydantic( + cls, configs: pipette_definition.SupportedTipsDefinition + ) -> "TipSpecificConfigurations": + return cls( + default_aspirate_flowrate=configs.defaultAspirateFlowRate, + default_dispense_flowrate=configs.defaultDispenseFlowRate, + default_blowout_flowrate=configs.defaultBlowOutFlowRate, + aspirate=configs.aspirate, + dispense=configs.dispense, + ) + + +@dataclass(frozen=True) +class SharedPipetteConfigurations: + display_name: str + pipette_type: PipetteModelType + pipette_version: PipetteVersionType + channels: PipetteChannelType + plunger_positions: PlungerPositions + pickup_configurations: PickUpTipConfigurations + droptip_configurations: TipMotorConfigurations + tip_handling_configurations: Dict[PipetteTipType, TipSpecificConfigurations] + partial_tip_configuration: PartialTipConfigurations + nozzle_offset: List[float] + plunger_current: PlungerMotorCurrent + min_volume: float + max_volume: float + + +@dataclass(frozen=True) +class OT2PipetteConfigurations(SharedPipetteConfigurations): + tip_length: float # TODO(seth): remove + # TODO: Replace entirely with tip length calibration + tip_overlap: Dict[str, float] + + +@dataclass(frozen=True) +class OT3PipetteConfigurations(SharedPipetteConfigurations): + sensors: List[str] + supported_tipracks: List[str] + + +def _build_tip_handling_configurations( + tip_configurations: Dict[str, pipette_definition.SupportedTipsDefinition] +) -> Dict[PipetteTipType, TipSpecificConfigurations]: + tip_handling_configurations = {} + for tip_type, tip_specs in tip_configurations.items(): + tip_handling_configurations[ + PipetteTipType[tip_type] + ] = TipSpecificConfigurations.from_pydantic(tip_specs) + return tip_handling_configurations + + +def load_ot3_pipette( + pipette_model: str, number_of_channels: int, version: float +) -> OT3PipetteConfigurations: + requested_model = PipetteModelType.convert_from_model(pipette_model) + requested_channels = PipetteChannelType.convert_from_channels(number_of_channels) + requested_version = PipetteVersionType.convert_from_float(version) + pipette_definition = load_data.load_definition( + requested_model, requested_channels, requested_version + ) + return OT3PipetteConfigurations( + display_name=pipette_definition.physical.displayName, + pipette_type=requested_model, + pipette_version=requested_version, + channels=requested_channels, + plunger_positions=PlungerPositions.from_pydantic( + pipette_definition.physical.plungerPositionsConfigurations + ), + pickup_configurations=PickUpTipConfigurations.from_pydantic( + pipette_definition.physical.pickUpTipConfigurations + ), + droptip_configurations=TipMotorConfigurations.from_pydantic( + pipette_definition.physical.dropTipConfigurations + ), + tip_handling_configurations=_build_tip_handling_configurations( + pipette_definition.liquid.supportedTips + ), + partial_tip_configuration=PartialTipConfigurations.from_pydantic( + pipette_definition.physical.partialTipConfigurations + ), + nozzle_offset=pipette_definition.geometry.nozzleOffset, + plunger_current=PlungerMotorCurrent.from_pydantic( + pipette_definition.physical.plungerMotorConfigurations + ), + min_volume=pipette_definition.liquid.minVolume, + max_volume=pipette_definition.liquid.maxVolume, + # TODO we need to properly load in the amount of sensors for each pipette. + sensors=pipette_definition.physical.availableSensors.sensors, + supported_tipracks=pipette_definition.liquid.defaultTipracks, + ) diff --git a/api/tests/opentrons/config/test_ot3_pipette_config.py b/api/tests/opentrons/config/test_ot3_pipette_config.py new file mode 100644 index 00000000000..5d0d0be59a4 --- /dev/null +++ b/api/tests/opentrons/config/test_ot3_pipette_config.py @@ -0,0 +1,31 @@ +import pytest + +from opentrons_shared_data.pipette.types import PipetteTipType +from opentrons.config.ot3_pipette_config import ( + load_ot3_pipette, + TipSpecificConfigurations, +) + + +def test_multiple_tip_configurations() -> None: + loaded_configuration = load_ot3_pipette("p1000", 8, 1.0) + assert list(loaded_configuration.tip_handling_configurations.keys()) == list( + PipetteTipType + ) + assert isinstance( + loaded_configuration.tip_handling_configurations[PipetteTipType.t50], + TipSpecificConfigurations, + ) + + +@pytest.mark.parametrize( + argnames=["model", "channels", "version"], + argvalues=[["p50", 8, 1.0], ["p1000", 96, 1.0], ["p50", 1, 1.0]], +) +def test_load_full_pipette_configurations( + model: str, channels: int, version: float +) -> None: + loaded_configuration = load_ot3_pipette(model, channels, version) + assert loaded_configuration.pipette_version.major == int(version) + assert loaded_configuration.pipette_type.value == model + assert loaded_configuration.channels.as_int == channels diff --git a/shared-data/python/opentrons_shared_data/pipette/constants.py b/shared-data/python/opentrons_shared_data/pipette/constants.py new file mode 100644 index 00000000000..cc423daaa68 --- /dev/null +++ b/shared-data/python/opentrons_shared_data/pipette/constants.py @@ -0,0 +1,8 @@ +from typing_extensions import Literal + +PLUNGER_CURRENT_MINIMUM = 0.1 +PLUNGER_CURRENT_MAXIMUM = 1.5 + + +PipetteModelMajorVersion = Literal[1] +PipetteModelMinorVersion = Literal[0, 1, 2, 3] diff --git a/shared-data/python/opentrons_shared_data/pipette/load_data.py b/shared-data/python/opentrons_shared_data/pipette/load_data.py new file mode 100644 index 00000000000..e5d773e1b25 --- /dev/null +++ b/shared-data/python/opentrons_shared_data/pipette/load_data.py @@ -0,0 +1,68 @@ +import json +import os + +from typing import Dict, Any +from functools import lru_cache + +from .. import load_shared_data, get_shared_data_root + +from . import types +from .pipette_definition import ( + PipetteConfigurations, + PipetteLiquidPropertiesDefinition, + PipetteGeometryDefinition, + PipettePhysicalPropertiesDefinition, +) + + +LoadedConfiguration = Dict[types.PipetteChannelType, Dict[types.PipetteModelType, Any]] + + +def _build_configuration_dictionary( + rel_path, version: types.PipetteVersionType +) -> LoadedConfiguration: + _dict = {} + for pipette_type in types.PipetteChannelType: + pipette_type_path = get_shared_data_root() / rel_path / pipette_type.value + _dict[pipette_type] = {} + for dir_name in os.scandir(pipette_type_path): + model_key = types.PipetteModelType.convert_from_model(dir_name.name) + _dict[pipette_type][model_key] = json.loads( + load_shared_data( + f"{pipette_type_path}/{dir_name.name}/{version.major}.json" + ) + ) + return _dict + + +@lru_cache(maxsize=None) +def _geometry(version: types.PipetteVersionType) -> LoadedConfiguration: + return _build_configuration_dictionary("pipette/definitions/2/geometry", version) + + +@lru_cache(maxsize=None) +def _liquid(version: types.PipetteVersionType) -> LoadedConfiguration: + return _build_configuration_dictionary("pipette/definitions/2/liquid", version) + + +@lru_cache(maxsize=None) +def _physical(version: types.PipetteVersionType) -> LoadedConfiguration: + return _build_configuration_dictionary("pipette/definitions/2/general", version) + + +def load_definition( + max_volume: types.PipetteModelType, + channels: types.PipetteChannelType, + version: types.PipetteVersionType, +) -> PipetteConfigurations: + return PipetteConfigurations( + liquid=PipetteLiquidPropertiesDefinition.parse_obj( + _liquid(version)[channels][max_volume] + ), + physical=PipettePhysicalPropertiesDefinition.parse_obj( + _physical(version)[channels][max_volume] + ), + geometry=PipetteGeometryDefinition.parse_obj( + _geometry(version)[channels][max_volume] + ), + ) diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py new file mode 100644 index 00000000000..9ac1a6b46db --- /dev/null +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -0,0 +1,142 @@ +from typing import List, Dict +from pydantic import BaseModel, Field + + +class SupportedTipsDefinition(BaseModel): + """Tip parameters available for every tip size.""" + + defaultAspirateFlowRate: float = Field( + ..., description="The flowrate used in aspirations by default." + ) + defaultDispenseFlowRate: float = Field( + ..., description="The flowrate used in dispenses by default." + ) + defaultBlowOutFlowRate: float = Field( + ..., description="The flowrate used in blowouts by default." + ) + aspirate: Dict[str, List] = Field( + ..., description="The default pipetting functions list for aspirate." + ) + dispense: Dict[str, List] = Field( + ..., description="The default pipetting functions list for dispensing." + ) + + +class MotorConfigurations(BaseModel): + idle: float = Field( + ..., description="The plunger motor current to use during idle states." + ) + run: float = Field( + ..., description="The plunger motor current to use during active states." + ) + + +class PlungerPositions(BaseModel): + top: float = Field( + ..., + description="The plunger position that describes max available volume of a pipette in mm.", + ) + bottom: float = Field( + ..., + description="The plunger position that describes min available volume of a pipette in mm.", + ) + blowout: float = Field( + ..., description="The plunger position past 0 volume to blow out liquid." + ) + drop: float = Field(..., description="The plunger position used to drop tips.") + + +class TipHandlingConfigurations(BaseModel): + current: float = Field( + ..., + description="Either the z motor current needed for picking up tip or the plunger motor current for dropping tip off the nozzle.", + ) + speed: float = Field( + ..., + description="The speed to move the z or plunger axis for tip pickup or drop off.", + ) + + +class PickUpTipConfigurations(TipHandlingConfigurations): + presses: int = Field( + ..., description="The number of tries required to force pick up a tip." + ) + increment: float = Field( + ..., + description="The increment to move the pipette down for force tip pickup retries.", + ) + distance: float = Field( + ..., description="The distance to begin a pick up tip from." + ) + + +class AvailableSensorDefinition(BaseModel): + """The number and type of sensors available in the pipette.""" + + sensors: List[str] = Field(..., description="") + + +class PartialTipDefinition(BaseModel): + partialTipSupported: bool = Field( + ..., description="Whether partial tip pick up is supported." + ) + availableConfigurations: List[int] = Field( + default=None, + description="A list of the types of partial tip configurations supported, listed by channel ints", + ) + + +class PipettePhysicalPropertiesDefinition(BaseModel): + """The physical properties definition of a pipette.""" + + displayName: str = Field( + ..., description="The display or full product name of the pipette." + ) + model: str = Field( + ..., description="The pipette model type (related to number of channels)" + ) + displayCategory: str = Field(..., description="The product model of the pipette.") + pickUpTipConfigurations: PickUpTipConfigurations + dropTipConfigurations: TipHandlingConfigurations + plungerMotorConfigurations: MotorConfigurations + plungerPositionsConfigurations: PlungerPositions + availableSensors: AvailableSensorDefinition + partialTipConfigurations: PartialTipDefinition + channels: int = Field( + ..., description="The maximum number of channels on the pipette." + ) + + +class PipetteGeometryDefinition(BaseModel): + """The geometry properties definition of a pipette.""" + + nozzleOffset: List[float] + pathTo3D: str = Field( + ..., + description="The shared data relative path to the 3D representation of the pipette model.", + ) + + +class PipetteLiquidPropertiesDefinition(BaseModel): + """The liquid properties definition of a pipette.""" + + supportedTips: Dict[str, SupportedTipsDefinition] + maxVolume: float = Field( + ..., description="The maximum supported volume of the pipette." + ) + minVolume: float = Field( + ..., description="The minimum supported volume of the pipette." + ) + defaultTipracks: List[str] = Field( + ..., + description="A list of default tiprack paths.", + regex="opentrons/[a-z0-9._]+/[0-9]", + ) + + +class PipetteConfigurations(BaseModel): + """The full pipette configurations of a given model and version.""" + + geometry: PipetteGeometryDefinition + physical: PipettePhysicalPropertiesDefinition + liquid: PipetteLiquidPropertiesDefinition diff --git a/shared-data/python/opentrons_shared_data/pipette/types.py b/shared-data/python/opentrons_shared_data/pipette/types.py new file mode 100644 index 00000000000..05750a7b1f3 --- /dev/null +++ b/shared-data/python/opentrons_shared_data/pipette/types.py @@ -0,0 +1,64 @@ +from enum import Enum +from dataclasses import dataclass + +from . import constants + + +class UnsupportedNumberOfChannels(Exception): + pass + + +class PipetteTipType(Enum): + t50 = "t50" + t200 = "t200" + t1000 = "t1000" + + +class PipetteChannelType(Enum): + SINGLE_CHANNEL = "single_channel" + EIGHT_CHANNEL = "eight_channel" + NINETY_SIX_CHANNEL = "ninety_six_channel" + + @classmethod + def convert_from_channels(cls, channels: int) -> "PipetteChannelType": + if channels == 96: + return cls.NINETY_SIX_CHANNEL + elif channels == 8: + return cls.EIGHT_CHANNEL + elif channels == 1: + return cls.SINGLE_CHANNEL + else: + raise UnsupportedNumberOfChannels( + f"A pipette with {channels} channels is not available at this time." + ) + + @property + def as_int(self) -> int: + if self.value == self.NINETY_SIX_CHANNEL.value: + return 96 + elif self.value == self.EIGHT_CHANNEL.value: + return 8 + elif self.value == self.SINGLE_CHANNEL.value: + return 1 + return 0 + + +class PipetteModelType(Enum): + P50 = "p50" + P1000 = "p1000" + + @classmethod + def convert_from_model(cls, model: str) -> "PipetteModelType": + return cls[model.upper()] + + +@dataclass(frozen=True) +class PipetteVersionType: + major: constants.PipetteModelMajorVersion + minor: constants.PipetteModelMinorVersion + + @classmethod + def convert_from_float(cls, version: float) -> "PipetteVersionType": + cls.major = int(version // 1) + cls.minor = int(round((version % 1), 2) * 10) + return cls diff --git a/shared-data/python/tests/pipette/test_load_data.py b/shared-data/python/tests/pipette/test_load_data.py new file mode 100644 index 00000000000..79a2e5511d5 --- /dev/null +++ b/shared-data/python/tests/pipette/test_load_data.py @@ -0,0 +1,27 @@ +import pytest +import json +from opentrons_shared_data.pipette import load_data, types + +from opentrons_shared_data import load_shared_data + + +@pytest.mark.xfail +def test_load_pipette_definition() -> None: + # TODO we should make sure that numbers that are supposed to be floats + # in the configuration are actually floats. For now, we will mark this + # test as expected fail. + pipette_config = load_data.load_definition( + types.PipetteModelType.P50, + types.PipetteChannelType.SINGLE_CHANNEL, + types.PipetteVersionType(major=1, minor=0), + ) + + assert pipette_config.liquid.dict() == json.loads( + load_shared_data("pipette/definitions/2/liquid/single_channel/p50/1.json") + ).pop("$otSharedSchema") + assert pipette_config.geometry.dict() == json.loads( + load_shared_data("pipette/definitions/2/geometry/single_channel/p50/1.json") + ) + assert pipette_config.physical.dict() == json.loads( + load_shared_data("pipette/definitions/2/general/single_channel/p50/1.json") + ) diff --git a/shared-data/python/tests/pipette/test_types.py b/shared-data/python/tests/pipette/test_types.py new file mode 100644 index 00000000000..1b9d1aa1cd8 --- /dev/null +++ b/shared-data/python/tests/pipette/test_types.py @@ -0,0 +1,41 @@ +import pytest +from opentrons_shared_data.pipette.types import ( + PipetteChannelType, + PipetteModelType, + PipetteVersionType, + UnsupportedNumberOfChannels, +) + + +@pytest.mark.parametrize( + argnames=["model", "expected_enum"], + argvalues=[["p50", PipetteModelType.P50], ["p1000", PipetteModelType.P1000]], +) +def test_model_enum(model: str, expected_enum: PipetteModelType) -> None: + assert expected_enum == PipetteModelType.convert_from_model(model) + + +@pytest.mark.parametrize(argnames="channels", argvalues=[1, 8, 96]) +def test_channel_enum(channels: int) -> None: + channel_type = PipetteChannelType.convert_from_channels(channels) + assert channels == channel_type.as_int + + +def test_incorrect_values() -> None: + with pytest.raises(KeyError): + PipetteModelType.convert_from_model("p100") + + with pytest.raises(UnsupportedNumberOfChannels): + PipetteChannelType.convert_from_channels(99) + + +@pytest.mark.parametrize( + argnames=["version", "major", "minor"], + argvalues=[[1.0, 1, 0], [1.3, 1, 3], [3.9, 3, 9]], +) +def test_version_enum(version: float, major: int, minor: int) -> None: + version_type = PipetteVersionType.convert_from_float(version) + conversion_test = round(version % 1, 2) + print(conversion_test) + assert version_type.major == major + assert version_type.minor == minor From 199a27593598e1bfbd582d00c3308315a429f9de Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Wed, 30 Nov 2022 15:24:05 -0500 Subject: [PATCH 2/7] fix shared data linting --- .../opentrons_shared_data/pipette/load_data.py | 4 ++-- .../pipette/pipette_definition.py | 4 ++-- .../python/opentrons_shared_data/pipette/types.py | 15 +++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/shared-data/python/opentrons_shared_data/pipette/load_data.py b/shared-data/python/opentrons_shared_data/pipette/load_data.py index e5d773e1b25..8f866afd2d3 100644 --- a/shared-data/python/opentrons_shared_data/pipette/load_data.py +++ b/shared-data/python/opentrons_shared_data/pipette/load_data.py @@ -19,9 +19,9 @@ def _build_configuration_dictionary( - rel_path, version: types.PipetteVersionType + rel_path: str, version: types.PipetteVersionType ) -> LoadedConfiguration: - _dict = {} + _dict: LoadedConfiguration = {} for pipette_type in types.PipetteChannelType: pipette_type_path = get_shared_data_root() / rel_path / pipette_type.value _dict[pipette_type] = {} diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index 9ac1a6b46db..9658e3d212e 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -14,10 +14,10 @@ class SupportedTipsDefinition(BaseModel): defaultBlowOutFlowRate: float = Field( ..., description="The flowrate used in blowouts by default." ) - aspirate: Dict[str, List] = Field( + aspirate: Dict[str, List[float]] = Field( ..., description="The default pipetting functions list for aspirate." ) - dispense: Dict[str, List] = Field( + dispense: Dict[str, List[float]] = Field( ..., description="The default pipetting functions list for dispensing." ) diff --git a/shared-data/python/opentrons_shared_data/pipette/types.py b/shared-data/python/opentrons_shared_data/pipette/types.py index 05750a7b1f3..145e117e542 100644 --- a/shared-data/python/opentrons_shared_data/pipette/types.py +++ b/shared-data/python/opentrons_shared_data/pipette/types.py @@ -1,3 +1,4 @@ +from typing import cast from enum import Enum from dataclasses import dataclass @@ -34,11 +35,11 @@ def convert_from_channels(cls, channels: int) -> "PipetteChannelType": @property def as_int(self) -> int: - if self.value == self.NINETY_SIX_CHANNEL.value: + if self.value == self.NINETY_SIX_CHANNEL: return 96 - elif self.value == self.EIGHT_CHANNEL.value: + elif self.value == self.EIGHT_CHANNEL: return 8 - elif self.value == self.SINGLE_CHANNEL.value: + elif self.value == self.SINGLE_CHANNEL: return 1 return 0 @@ -59,6 +60,8 @@ class PipetteVersionType: @classmethod def convert_from_float(cls, version: float) -> "PipetteVersionType": - cls.major = int(version // 1) - cls.minor = int(round((version % 1), 2) * 10) - return cls + major = cast(constants.PipetteModelMajorVersion, int(version // 1)) + minor = cast( + constants.PipetteModelMinorVersion, int(round((version % 1), 2) * 10) + ) + return cls(major=major, minor=minor) From c0ae07ff64fd52971d8879dea999424e0da04aba Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Wed, 30 Nov 2022 16:02:04 -0500 Subject: [PATCH 3/7] fix more linting issues --- api/src/opentrons/config/ot3_pipette_config.py | 6 +++--- shared-data/python/opentrons_shared_data/pipette/types.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/src/opentrons/config/ot3_pipette_config.py b/api/src/opentrons/config/ot3_pipette_config.py index 878a3a68b70..69470c20934 100644 --- a/api/src/opentrons/config/ot3_pipette_config.py +++ b/api/src/opentrons/config/ot3_pipette_config.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Dict +from typing import List, Dict from dataclasses import dataclass, field from opentrons_shared_data.pipette import load_data, pipette_definition from opentrons_shared_data.pipette.types import ( @@ -95,8 +95,8 @@ class TipSpecificConfigurations: default_aspirate_flowrate: float default_dispense_flowrate: float default_blowout_flowrate: float - aspirate: Dict[str, List[Tuple[float, float, float]]] - dispense: Dict[str, List[Tuple[float, float, float]]] + aspirate: Dict[str, List[float]] + dispense: Dict[str, List[float]] @classmethod def from_pydantic( diff --git a/shared-data/python/opentrons_shared_data/pipette/types.py b/shared-data/python/opentrons_shared_data/pipette/types.py index 145e117e542..a1840725bba 100644 --- a/shared-data/python/opentrons_shared_data/pipette/types.py +++ b/shared-data/python/opentrons_shared_data/pipette/types.py @@ -35,11 +35,11 @@ def convert_from_channels(cls, channels: int) -> "PipetteChannelType": @property def as_int(self) -> int: - if self.value == self.NINETY_SIX_CHANNEL: + if self.value == "ninety_six_channel": return 96 - elif self.value == self.EIGHT_CHANNEL: + elif self.value == "eight_channel": return 8 - elif self.value == self.SINGLE_CHANNEL: + elif self.value == "single_channel": return 1 return 0 From cf453049c18ed62a50bb2936755463aa41b334e4 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Wed, 30 Nov 2022 19:54:55 -0500 Subject: [PATCH 4/7] only use pydantic models --- .../opentrons/config/ot3_pipette_config.py | 179 +----------------- .../config/test_ot3_pipette_config.py | 15 +- .../pipette/load_data.py | 23 +-- .../pipette/pipette_definition.py | 127 +++++++++---- .../python/tests/pipette/test_load_data.py | 18 +- 5 files changed, 116 insertions(+), 246 deletions(-) diff --git a/api/src/opentrons/config/ot3_pipette_config.py b/api/src/opentrons/config/ot3_pipette_config.py index 69470c20934..dfbf64d74fd 100644 --- a/api/src/opentrons/config/ot3_pipette_config.py +++ b/api/src/opentrons/config/ot3_pipette_config.py @@ -1,8 +1,5 @@ -from typing import List, Dict -from dataclasses import dataclass, field from opentrons_shared_data.pipette import load_data, pipette_definition from opentrons_shared_data.pipette.types import ( - PipetteTipType, PipetteChannelType, PipetteModelType, PipetteVersionType, @@ -11,183 +8,13 @@ DEFAULT_CALIBRATION_OFFSET = [0.0, 0.0, 0.0] -@dataclass(frozen=True) -class PartialTipConfigurations: - supported: bool - configurations: List[int] = field(default_factory=list) - - @classmethod - def from_pydantic( - cls, configs: pipette_definition.PartialTipDefinition - ) -> "PartialTipConfigurations": - return cls( - supported=configs.partialTipSupported, - configurations=configs.availableConfigurations, - ) - - -@dataclass(frozen=True) -class TipMotorConfigurations: - current: float - speed: float - - @classmethod - def from_pydantic( - cls, configs: pipette_definition.TipHandlingConfigurations - ) -> "TipMotorConfigurations": - return cls(current=configs.current, speed=configs.speed) - - -@dataclass(frozen=True) -class PickUpTipConfigurations: - current: float - speed: float - increment: float - distance: float - presses: int - - @classmethod - def from_pydantic( - cls, configs: pipette_definition.PickUpTipConfigurations - ) -> "PickUpTipConfigurations": - return cls( - current=configs.current, - speed=configs.speed, - increment=configs.increment, - distance=configs.distance, - presses=configs.presses, - ) - - -@dataclass(frozen=True) -class PlungerMotorCurrent: - idle: float - run: float - - @classmethod - def from_pydantic( - cls, configs: pipette_definition.MotorConfigurations - ) -> "PlungerMotorCurrent": - return cls(idle=configs.idle, run=configs.run) - - -@dataclass(frozen=True) -class PlungerPositions: - top: float - bottom: float - blowout: float - drop: float - - @classmethod - def from_pydantic( - cls, configs: pipette_definition.PlungerPositions - ) -> "PlungerPositions": - return cls( - top=configs.top, - bottom=configs.bottom, - blowout=configs.blowout, - drop=configs.drop, - ) - - -@dataclass(frozen=True) -class TipSpecificConfigurations: - default_aspirate_flowrate: float - default_dispense_flowrate: float - default_blowout_flowrate: float - aspirate: Dict[str, List[float]] - dispense: Dict[str, List[float]] - - @classmethod - def from_pydantic( - cls, configs: pipette_definition.SupportedTipsDefinition - ) -> "TipSpecificConfigurations": - return cls( - default_aspirate_flowrate=configs.defaultAspirateFlowRate, - default_dispense_flowrate=configs.defaultDispenseFlowRate, - default_blowout_flowrate=configs.defaultBlowOutFlowRate, - aspirate=configs.aspirate, - dispense=configs.dispense, - ) - - -@dataclass(frozen=True) -class SharedPipetteConfigurations: - display_name: str - pipette_type: PipetteModelType - pipette_version: PipetteVersionType - channels: PipetteChannelType - plunger_positions: PlungerPositions - pickup_configurations: PickUpTipConfigurations - droptip_configurations: TipMotorConfigurations - tip_handling_configurations: Dict[PipetteTipType, TipSpecificConfigurations] - partial_tip_configuration: PartialTipConfigurations - nozzle_offset: List[float] - plunger_current: PlungerMotorCurrent - min_volume: float - max_volume: float - - -@dataclass(frozen=True) -class OT2PipetteConfigurations(SharedPipetteConfigurations): - tip_length: float # TODO(seth): remove - # TODO: Replace entirely with tip length calibration - tip_overlap: Dict[str, float] - - -@dataclass(frozen=True) -class OT3PipetteConfigurations(SharedPipetteConfigurations): - sensors: List[str] - supported_tipracks: List[str] - - -def _build_tip_handling_configurations( - tip_configurations: Dict[str, pipette_definition.SupportedTipsDefinition] -) -> Dict[PipetteTipType, TipSpecificConfigurations]: - tip_handling_configurations = {} - for tip_type, tip_specs in tip_configurations.items(): - tip_handling_configurations[ - PipetteTipType[tip_type] - ] = TipSpecificConfigurations.from_pydantic(tip_specs) - return tip_handling_configurations - - def load_ot3_pipette( pipette_model: str, number_of_channels: int, version: float -) -> OT3PipetteConfigurations: +) -> pipette_definition.PipetteConfigurations: requested_model = PipetteModelType.convert_from_model(pipette_model) requested_channels = PipetteChannelType.convert_from_channels(number_of_channels) requested_version = PipetteVersionType.convert_from_float(version) - pipette_definition = load_data.load_definition( + + return load_data.load_definition( requested_model, requested_channels, requested_version ) - return OT3PipetteConfigurations( - display_name=pipette_definition.physical.displayName, - pipette_type=requested_model, - pipette_version=requested_version, - channels=requested_channels, - plunger_positions=PlungerPositions.from_pydantic( - pipette_definition.physical.plungerPositionsConfigurations - ), - pickup_configurations=PickUpTipConfigurations.from_pydantic( - pipette_definition.physical.pickUpTipConfigurations - ), - droptip_configurations=TipMotorConfigurations.from_pydantic( - pipette_definition.physical.dropTipConfigurations - ), - tip_handling_configurations=_build_tip_handling_configurations( - pipette_definition.liquid.supportedTips - ), - partial_tip_configuration=PartialTipConfigurations.from_pydantic( - pipette_definition.physical.partialTipConfigurations - ), - nozzle_offset=pipette_definition.geometry.nozzleOffset, - plunger_current=PlungerMotorCurrent.from_pydantic( - pipette_definition.physical.plungerMotorConfigurations - ), - min_volume=pipette_definition.liquid.minVolume, - max_volume=pipette_definition.liquid.maxVolume, - # TODO we need to properly load in the amount of sensors for each pipette. - sensors=pipette_definition.physical.availableSensors.sensors, - supported_tipracks=pipette_definition.liquid.defaultTipracks, - ) diff --git a/api/tests/opentrons/config/test_ot3_pipette_config.py b/api/tests/opentrons/config/test_ot3_pipette_config.py index 5d0d0be59a4..aa0eeac7016 100644 --- a/api/tests/opentrons/config/test_ot3_pipette_config.py +++ b/api/tests/opentrons/config/test_ot3_pipette_config.py @@ -1,20 +1,16 @@ import pytest from opentrons_shared_data.pipette.types import PipetteTipType -from opentrons.config.ot3_pipette_config import ( - load_ot3_pipette, - TipSpecificConfigurations, -) +from opentrons_shared_data.pipette.pipette_definition import SupportedTipsDefinition +from opentrons.config.ot3_pipette_config import load_ot3_pipette def test_multiple_tip_configurations() -> None: loaded_configuration = load_ot3_pipette("p1000", 8, 1.0) - assert list(loaded_configuration.tip_handling_configurations.keys()) == list( - PipetteTipType - ) + assert list(loaded_configuration.supported_tips.keys()) == list(PipetteTipType) assert isinstance( - loaded_configuration.tip_handling_configurations[PipetteTipType.t50], - TipSpecificConfigurations, + loaded_configuration.supported_tips[PipetteTipType.t50], + SupportedTipsDefinition, ) @@ -26,6 +22,5 @@ def test_load_full_pipette_configurations( model: str, channels: int, version: float ) -> None: loaded_configuration = load_ot3_pipette(model, channels, version) - assert loaded_configuration.pipette_version.major == int(version) assert loaded_configuration.pipette_type.value == model assert loaded_configuration.channels.as_int == channels diff --git a/shared-data/python/opentrons_shared_data/pipette/load_data.py b/shared-data/python/opentrons_shared_data/pipette/load_data.py index 8f866afd2d3..b3621501b8b 100644 --- a/shared-data/python/opentrons_shared_data/pipette/load_data.py +++ b/shared-data/python/opentrons_shared_data/pipette/load_data.py @@ -7,12 +7,7 @@ from .. import load_shared_data, get_shared_data_root from . import types -from .pipette_definition import ( - PipetteConfigurations, - PipetteLiquidPropertiesDefinition, - PipetteGeometryDefinition, - PipettePhysicalPropertiesDefinition, -) +from .pipette_definition import PipetteConfigurations LoadedConfiguration = Dict[types.PipetteChannelType, Dict[types.PipetteModelType, Any]] @@ -55,14 +50,10 @@ def load_definition( channels: types.PipetteChannelType, version: types.PipetteVersionType, ) -> PipetteConfigurations: - return PipetteConfigurations( - liquid=PipetteLiquidPropertiesDefinition.parse_obj( - _liquid(version)[channels][max_volume] - ), - physical=PipettePhysicalPropertiesDefinition.parse_obj( - _physical(version)[channels][max_volume] - ), - geometry=PipetteGeometryDefinition.parse_obj( - _geometry(version)[channels][max_volume] - ), + geometry_dict = _geometry(version)[channels][max_volume] + physical_dict = _physical(version)[channels][max_volume] + liquid_dict = _liquid(version)[channels][max_volume] + + return PipetteConfigurations.parse_obj( + {**geometry_dict, **physical_dict, **liquid_dict} ) diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index 9658e3d212e..49c446fe5e7 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -1,23 +1,30 @@ -from typing import List, Dict -from pydantic import BaseModel, Field +from typing import List, Dict, Tuple +from pydantic import BaseModel, Field, validator +from .types import PipetteModelType, PipetteTipType, PipetteChannelType class SupportedTipsDefinition(BaseModel): """Tip parameters available for every tip size.""" - defaultAspirateFlowRate: float = Field( - ..., description="The flowrate used in aspirations by default." + default_aspirate_flowrate: float = Field( + ..., + description="The flowrate used in aspirations by default.", + alias="defaultAspirateFlowRate", ) - defaultDispenseFlowRate: float = Field( - ..., description="The flowrate used in dispenses by default." + default_dispense_flowrate: float = Field( + ..., + description="The flowrate used in dispenses by default.", + alias="defaultDispenseFlowRate", ) - defaultBlowOutFlowRate: float = Field( - ..., description="The flowrate used in blowouts by default." + default_blowout_flowrate: float = Field( + ..., + description="The flowrate used in blowouts by default.", + alias="defaultBlowOutFlowRate", ) - aspirate: Dict[str, List[float]] = Field( + aspirate: Dict[str, List[Tuple[float, float, float]]] = Field( ..., description="The default pipetting functions list for aspirate." ) - dispense: Dict[str, List[float]] = Field( + dispense: Dict[str, List[Tuple[float, float, float]]] = Field( ..., description="The default pipetting functions list for dispensing." ) @@ -77,66 +84,112 @@ class AvailableSensorDefinition(BaseModel): class PartialTipDefinition(BaseModel): - partialTipSupported: bool = Field( - ..., description="Whether partial tip pick up is supported." + partial_tip_supported: bool = Field( + ..., + description="Whether partial tip pick up is supported.", + alias="partialTipSupported", ) - availableConfigurations: List[int] = Field( + available_configurations: List[int] = Field( default=None, description="A list of the types of partial tip configurations supported, listed by channel ints", + alias="availableConfigurations", ) class PipettePhysicalPropertiesDefinition(BaseModel): """The physical properties definition of a pipette.""" - displayName: str = Field( - ..., description="The display or full product name of the pipette." + display_name: str = Field( + ..., + description="The display or full product name of the pipette.", + alias="displayName", + ) + pipette_type: PipetteModelType = Field( + ..., + description="The pipette model type (related to number of channels).", + alias="model", + ) + display_category: str = Field( + ..., description="The product model of the pipette.", alias="displayCategory" + ) + pick_up_tip_configurations: PickUpTipConfigurations = Field( + ..., alias="pickUpTipConfigurations" ) - model: str = Field( - ..., description="The pipette model type (related to number of channels)" + drop_tip_configurations: TipHandlingConfigurations = Field( + ..., alias="dropTipConfigurations" ) - displayCategory: str = Field(..., description="The product model of the pipette.") - pickUpTipConfigurations: PickUpTipConfigurations - dropTipConfigurations: TipHandlingConfigurations - plungerMotorConfigurations: MotorConfigurations - plungerPositionsConfigurations: PlungerPositions - availableSensors: AvailableSensorDefinition - partialTipConfigurations: PartialTipDefinition - channels: int = Field( + plunger_motor_configurations: MotorConfigurations = Field( + ..., alias="plungerMotorConfigurations" + ) + plunger_positions_configurations: PlungerPositions = Field( + ..., alias="plungerPositionsConfigurations" + ) + available_sensors: AvailableSensorDefinition = Field(..., alias="availableSensors") + partial_tip_configurations: PartialTipDefinition = Field( + ..., alias="partialTipConfigurations" + ) + channels: PipetteChannelType = Field( ..., description="The maximum number of channels on the pipette." ) + @validator("pipette_type", pre=True) + def convert_pipette_model_string(cls, v: str) -> PipetteModelType: + return PipetteModelType.convert_from_model(v) + + @validator("channels", pre=True) + def convert_channels(cls, v: int) -> PipetteChannelType: + return PipetteChannelType.convert_from_channels(v) + class PipetteGeometryDefinition(BaseModel): """The geometry properties definition of a pipette.""" - nozzleOffset: List[float] - pathTo3D: str = Field( + nozzle_offset: List[float] = Field(..., alias="nozzleOffset") + path_to_3D: str = Field( ..., description="The shared data relative path to the 3D representation of the pipette model.", + alias="pathTo3D", ) class PipetteLiquidPropertiesDefinition(BaseModel): """The liquid properties definition of a pipette.""" - supportedTips: Dict[str, SupportedTipsDefinition] - maxVolume: float = Field( - ..., description="The maximum supported volume of the pipette." + supported_tips: Dict[PipetteTipType, SupportedTipsDefinition] = Field( + ..., alias="supportedTips" ) - minVolume: float = Field( - ..., description="The minimum supported volume of the pipette." + max_volume: float = Field( + ..., + description="The maximum supported volume of the pipette.", + alias="maxVolume", + ) + min_volume: float = Field( + ..., + description="The minimum supported volume of the pipette.", + alias="minVolume", ) - defaultTipracks: List[str] = Field( + default_tipracks: List[str] = Field( ..., description="A list of default tiprack paths.", regex="opentrons/[a-z0-9._]+/[0-9]", + alias="defaultTipracks", ) + @validator("supported_tips", pre=True) + def convert_aspirate_key_to_channel_type( + cls, v: Dict[str, SupportedTipsDefinition] + ) -> Dict[PipetteTipType, SupportedTipsDefinition]: + return {PipetteTipType[key]: value for key, value in v.items()} + -class PipetteConfigurations(BaseModel): +class PipetteConfigurations( + PipetteGeometryDefinition, + PipettePhysicalPropertiesDefinition, + PipetteLiquidPropertiesDefinition, +): """The full pipette configurations of a given model and version.""" - geometry: PipetteGeometryDefinition - physical: PipettePhysicalPropertiesDefinition - liquid: PipetteLiquidPropertiesDefinition + pass + # geometry: PipetteGeometryDefinition + # physical: PipettePhysicalPropertiesDefinition + # liquid: PipetteLiquidPropertiesDefinition diff --git a/shared-data/python/tests/pipette/test_load_data.py b/shared-data/python/tests/pipette/test_load_data.py index 79a2e5511d5..cb6d7a3b3cb 100644 --- a/shared-data/python/tests/pipette/test_load_data.py +++ b/shared-data/python/tests/pipette/test_load_data.py @@ -15,13 +15,17 @@ def test_load_pipette_definition() -> None: types.PipetteChannelType.SINGLE_CHANNEL, types.PipetteVersionType(major=1, minor=0), ) - - assert pipette_config.liquid.dict() == json.loads( + combined_dict = json.loads( load_shared_data("pipette/definitions/2/liquid/single_channel/p50/1.json") - ).pop("$otSharedSchema") - assert pipette_config.geometry.dict() == json.loads( - load_shared_data("pipette/definitions/2/geometry/single_channel/p50/1.json") ) - assert pipette_config.physical.dict() == json.loads( - load_shared_data("pipette/definitions/2/general/single_channel/p50/1.json") + combined_dict.update( + json.loads( + load_shared_data("pipette/definitions/2/geometry/single_channel/p50/1.json") + ) + ) + combined_dict.update( + json.loads( + load_shared_data("pipette/definitions/2/general/single_channel/p50/1.json") + ) ) + assert pipette_config.dict() == combined_dict From 1861fd7e2ddebc73c7ca8db3666fb9a6de86df17 Mon Sep 17 00:00:00 2001 From: Laura Cox <31892318+Laura-Danielle@users.noreply.github.com> Date: Thu, 1 Dec 2022 14:34:25 -0500 Subject: [PATCH 5/7] feat(shared-data): support major and minor versions in pipette configs (#11799) --- .../eight_channel/p1000/{1.json => 1_0.json} | 2 +- .../eight_channel/p50/{1.json => 1_0.json} | 2 +- .../p1000/{1.json => 1_0.json} | 2 +- .../single_channel/p1000/{1.json => 1_0.json} | 2 +- .../single_channel/p50/{1.json => 1_0.json} | 2 +- .../eight_channel/p1000/{1.json => 1_0.json} | 0 .../eight_channel/p50/{1.json => 1_0.json} | 0 .../p1000/{1.json => 1_0.json} | 0 .../single_channel/p1000/{1.json => 1_0.json} | 0 .../single_channel/p50/{1.json => 1_0.json} | 0 .../eight_channel/p1000/{1.json => 1_0.json} | 0 .../eight_channel/p50/{1.json => 1_0.json} | 0 .../p1000/{1.json => 1_0.json} | 0 .../single_channel/p1000/{1.json => 1_0.json} | 0 .../single_channel/p50/{1.json => 1_0.json} | 0 .../pipette/load_data.py | 2 +- .../pipette/pipette_definition.py | 3 -- .../python/tests/pipette/test_load_data.py | 33 ++++++++++--------- 18 files changed, 23 insertions(+), 25 deletions(-) rename shared-data/pipette/definitions/2/general/eight_channel/p1000/{1.json => 1_0.json} (96%) rename shared-data/pipette/definitions/2/general/eight_channel/p50/{1.json => 1_0.json} (96%) rename shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/{1.json => 1_0.json} (96%) rename shared-data/pipette/definitions/2/general/single_channel/p1000/{1.json => 1_0.json} (97%) rename shared-data/pipette/definitions/2/general/single_channel/p50/{1.json => 1_0.json} (97%) rename shared-data/pipette/definitions/2/geometry/eight_channel/p1000/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/geometry/eight_channel/p50/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/geometry/single_channel/p1000/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/geometry/single_channel/p50/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/liquid/eight_channel/p1000/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/liquid/eight_channel/p50/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/liquid/ninety_six_channel/p1000/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/liquid/single_channel/p1000/{1.json => 1_0.json} (100%) rename shared-data/pipette/definitions/2/liquid/single_channel/p50/{1.json => 1_0.json} (100%) diff --git a/shared-data/pipette/definitions/2/general/eight_channel/p1000/1.json b/shared-data/pipette/definitions/2/general/eight_channel/p1000/1_0.json similarity index 96% rename from shared-data/pipette/definitions/2/general/eight_channel/p1000/1.json rename to shared-data/pipette/definitions/2/general/eight_channel/p1000/1_0.json index 332f958f8bd..1eb2478cf83 100644 --- a/shared-data/pipette/definitions/2/general/eight_channel/p1000/1.json +++ b/shared-data/pipette/definitions/2/general/eight_channel/p1000/1_0.json @@ -1,7 +1,7 @@ { "$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json", "displayName": "P1000 Eight Channel GEN3", - "model": "eightChannel", + "model": "p1000", "displayCategory": "GEN3", "pickUpTipConfigurations": { "current": 0.5, diff --git a/shared-data/pipette/definitions/2/general/eight_channel/p50/1.json b/shared-data/pipette/definitions/2/general/eight_channel/p50/1_0.json similarity index 96% rename from shared-data/pipette/definitions/2/general/eight_channel/p50/1.json rename to shared-data/pipette/definitions/2/general/eight_channel/p50/1_0.json index 908afabf307..50d177c5619 100644 --- a/shared-data/pipette/definitions/2/general/eight_channel/p50/1.json +++ b/shared-data/pipette/definitions/2/general/eight_channel/p50/1_0.json @@ -1,7 +1,7 @@ { "$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json", "displayName": "P50 Eight Channel GEN3", - "model": "eightChannel", + "model": "p50", "displayCategory": "GEN3", "pickUpTipConfigurations": { "current": 0.5, diff --git a/shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/1.json b/shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/1_0.json similarity index 96% rename from shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/1.json rename to shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/1_0.json index 7a85eb62291..7a2897d3cbd 100644 --- a/shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/1.json +++ b/shared-data/pipette/definitions/2/general/ninety_six_channel/p1000/1_0.json @@ -1,7 +1,7 @@ { "$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json", "displayName": "P1000 96 Channel GEN3", - "model": "ninetySixChannel", + "model": "p1000", "displayCategory": "GEN3", "pickUpTipConfigurations": { "current": 0.4, diff --git a/shared-data/pipette/definitions/2/general/single_channel/p1000/1.json b/shared-data/pipette/definitions/2/general/single_channel/p1000/1_0.json similarity index 97% rename from shared-data/pipette/definitions/2/general/single_channel/p1000/1.json rename to shared-data/pipette/definitions/2/general/single_channel/p1000/1_0.json index f3cf7fd5294..222db4dd1bc 100644 --- a/shared-data/pipette/definitions/2/general/single_channel/p1000/1.json +++ b/shared-data/pipette/definitions/2/general/single_channel/p1000/1_0.json @@ -1,7 +1,7 @@ { "$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json", "displayName": "P1000 One Channel GEN3", - "model": "oneChannel", + "model": "p1000", "displayCategory": "GEN3", "pickUpTipConfigurations": { "current": 0.15, diff --git a/shared-data/pipette/definitions/2/general/single_channel/p50/1.json b/shared-data/pipette/definitions/2/general/single_channel/p50/1_0.json similarity index 97% rename from shared-data/pipette/definitions/2/general/single_channel/p50/1.json rename to shared-data/pipette/definitions/2/general/single_channel/p50/1_0.json index c36215977cb..4ef9e60a2a3 100644 --- a/shared-data/pipette/definitions/2/general/single_channel/p50/1.json +++ b/shared-data/pipette/definitions/2/general/single_channel/p50/1_0.json @@ -1,7 +1,7 @@ { "$otSharedSchema": "#/pipette/schemas/2/pipettePropertiesSchema.json", "displayName": "P50 One Channel GEN3", - "model": "oneChannel", + "model": "p50", "displayCategory": "GEN3", "pickUpTipConfigurations": { "current": 0.15, diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1.json rename to shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1_0.json diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/geometry/eight_channel/p50/1.json rename to shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_0.json diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1.json rename to shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1_0.json diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/geometry/single_channel/p1000/1.json rename to shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_0.json diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/geometry/single_channel/p50/1.json rename to shared-data/pipette/definitions/2/geometry/single_channel/p50/1_0.json diff --git a/shared-data/pipette/definitions/2/liquid/eight_channel/p1000/1.json b/shared-data/pipette/definitions/2/liquid/eight_channel/p1000/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/liquid/eight_channel/p1000/1.json rename to shared-data/pipette/definitions/2/liquid/eight_channel/p1000/1_0.json diff --git a/shared-data/pipette/definitions/2/liquid/eight_channel/p50/1.json b/shared-data/pipette/definitions/2/liquid/eight_channel/p50/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/liquid/eight_channel/p50/1.json rename to shared-data/pipette/definitions/2/liquid/eight_channel/p50/1_0.json diff --git a/shared-data/pipette/definitions/2/liquid/ninety_six_channel/p1000/1.json b/shared-data/pipette/definitions/2/liquid/ninety_six_channel/p1000/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/liquid/ninety_six_channel/p1000/1.json rename to shared-data/pipette/definitions/2/liquid/ninety_six_channel/p1000/1_0.json diff --git a/shared-data/pipette/definitions/2/liquid/single_channel/p1000/1.json b/shared-data/pipette/definitions/2/liquid/single_channel/p1000/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/liquid/single_channel/p1000/1.json rename to shared-data/pipette/definitions/2/liquid/single_channel/p1000/1_0.json diff --git a/shared-data/pipette/definitions/2/liquid/single_channel/p50/1.json b/shared-data/pipette/definitions/2/liquid/single_channel/p50/1_0.json similarity index 100% rename from shared-data/pipette/definitions/2/liquid/single_channel/p50/1.json rename to shared-data/pipette/definitions/2/liquid/single_channel/p50/1_0.json diff --git a/shared-data/python/opentrons_shared_data/pipette/load_data.py b/shared-data/python/opentrons_shared_data/pipette/load_data.py index b3621501b8b..7a654201c44 100644 --- a/shared-data/python/opentrons_shared_data/pipette/load_data.py +++ b/shared-data/python/opentrons_shared_data/pipette/load_data.py @@ -24,7 +24,7 @@ def _build_configuration_dictionary( model_key = types.PipetteModelType.convert_from_model(dir_name.name) _dict[pipette_type][model_key] = json.loads( load_shared_data( - f"{pipette_type_path}/{dir_name.name}/{version.major}.json" + f"{pipette_type_path}/{dir_name.name}/{version.major}_{version.minor}.json" ) ) return _dict diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index 49c446fe5e7..e7a86128edb 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -190,6 +190,3 @@ class PipetteConfigurations( """The full pipette configurations of a given model and version.""" pass - # geometry: PipetteGeometryDefinition - # physical: PipettePhysicalPropertiesDefinition - # liquid: PipetteLiquidPropertiesDefinition diff --git a/shared-data/python/tests/pipette/test_load_data.py b/shared-data/python/tests/pipette/test_load_data.py index cb6d7a3b3cb..a27504bac28 100644 --- a/shared-data/python/tests/pipette/test_load_data.py +++ b/shared-data/python/tests/pipette/test_load_data.py @@ -1,31 +1,32 @@ -import pytest import json from opentrons_shared_data.pipette import load_data, types from opentrons_shared_data import load_shared_data -@pytest.mark.xfail def test_load_pipette_definition() -> None: - # TODO we should make sure that numbers that are supposed to be floats - # in the configuration are actually floats. For now, we will mark this - # test as expected fail. pipette_config = load_data.load_definition( types.PipetteModelType.P50, types.PipetteChannelType.SINGLE_CHANNEL, types.PipetteVersionType(major=1, minor=0), ) - combined_dict = json.loads( - load_shared_data("pipette/definitions/2/liquid/single_channel/p50/1.json") + liquid = json.loads( + load_shared_data("pipette/definitions/2/liquid/single_channel/p50/1_0.json") ) - combined_dict.update( - json.loads( - load_shared_data("pipette/definitions/2/geometry/single_channel/p50/1.json") - ) + geometry = json.loads( + load_shared_data("pipette/definitions/2/geometry/single_channel/p50/1_0.json") ) - combined_dict.update( - json.loads( - load_shared_data("pipette/definitions/2/general/single_channel/p50/1.json") - ) + general = json.loads( + load_shared_data("pipette/definitions/2/general/single_channel/p50/1_0.json") + ) + + assert pipette_config.channels.as_int == general["channels"] + assert pipette_config.pipette_type.value == general["model"] + assert pipette_config.nozzle_offset == geometry["nozzleOffset"] + + assert ( + pipette_config.supported_tips[ + types.PipetteTipType.t50 + ].default_aspirate_flowrate + == liquid["supportedTips"]["t50"]["defaultAspirateFlowRate"] ) - assert pipette_config.dict() == combined_dict From 093a846267760796b438705d4b5d129cc61a22c4 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 1 Dec 2022 15:25:35 -0500 Subject: [PATCH 6/7] pr change requests --- .../opentrons/config/ot3_pipette_config.py | 11 +-- .../config/test_ot3_pipette_config.py | 6 +- .../pipette/constants.py | 8 -- .../pipette/load_data.py | 78 ++++++++++++------- .../pipette/pipette_definition.py | 52 ++++++++++++- .../opentrons_shared_data/pipette/types.py | 67 ---------------- .../python/tests/pipette/test_load_data.py | 18 +++-- ...st_types.py => test_pipette_definition.py} | 19 ++--- 8 files changed, 125 insertions(+), 134 deletions(-) delete mode 100644 shared-data/python/opentrons_shared_data/pipette/constants.py delete mode 100644 shared-data/python/opentrons_shared_data/pipette/types.py rename shared-data/python/tests/pipette/{test_types.py => test_pipette_definition.py} (57%) diff --git a/api/src/opentrons/config/ot3_pipette_config.py b/api/src/opentrons/config/ot3_pipette_config.py index dfbf64d74fd..553c7caa61b 100644 --- a/api/src/opentrons/config/ot3_pipette_config.py +++ b/api/src/opentrons/config/ot3_pipette_config.py @@ -1,8 +1,9 @@ -from opentrons_shared_data.pipette import load_data, pipette_definition -from opentrons_shared_data.pipette.types import ( +from opentrons_shared_data.pipette import load_data +from opentrons_shared_data.pipette.pipette_definition import ( PipetteChannelType, PipetteModelType, PipetteVersionType, + PipetteConfigurations, ) DEFAULT_CALIBRATION_OFFSET = [0.0, 0.0, 0.0] @@ -10,9 +11,9 @@ def load_ot3_pipette( pipette_model: str, number_of_channels: int, version: float -) -> pipette_definition.PipetteConfigurations: - requested_model = PipetteModelType.convert_from_model(pipette_model) - requested_channels = PipetteChannelType.convert_from_channels(number_of_channels) +) -> PipetteConfigurations: + requested_model = PipetteModelType(pipette_model) + requested_channels = PipetteChannelType(number_of_channels) requested_version = PipetteVersionType.convert_from_float(version) return load_data.load_definition( diff --git a/api/tests/opentrons/config/test_ot3_pipette_config.py b/api/tests/opentrons/config/test_ot3_pipette_config.py index aa0eeac7016..89be25d53a5 100644 --- a/api/tests/opentrons/config/test_ot3_pipette_config.py +++ b/api/tests/opentrons/config/test_ot3_pipette_config.py @@ -1,7 +1,9 @@ import pytest -from opentrons_shared_data.pipette.types import PipetteTipType -from opentrons_shared_data.pipette.pipette_definition import SupportedTipsDefinition +from opentrons_shared_data.pipette.pipette_definition import ( + SupportedTipsDefinition, + PipetteTipType, +) from opentrons.config.ot3_pipette_config import load_ot3_pipette diff --git a/shared-data/python/opentrons_shared_data/pipette/constants.py b/shared-data/python/opentrons_shared_data/pipette/constants.py deleted file mode 100644 index cc423daaa68..00000000000 --- a/shared-data/python/opentrons_shared_data/pipette/constants.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing_extensions import Literal - -PLUNGER_CURRENT_MINIMUM = 0.1 -PLUNGER_CURRENT_MAXIMUM = 1.5 - - -PipetteModelMajorVersion = Literal[1] -PipetteModelMinorVersion = Literal[0, 1, 2, 3] diff --git a/shared-data/python/opentrons_shared_data/pipette/load_data.py b/shared-data/python/opentrons_shared_data/pipette/load_data.py index 7a654201c44..45b2cd97c06 100644 --- a/shared-data/python/opentrons_shared_data/pipette/load_data.py +++ b/shared-data/python/opentrons_shared_data/pipette/load_data.py @@ -1,58 +1,76 @@ import json -import os from typing import Dict, Any +from typing_extensions import Literal from functools import lru_cache from .. import load_shared_data, get_shared_data_root -from . import types -from .pipette_definition import PipetteConfigurations +from .pipette_definition import ( + PipetteConfigurations, + PipetteChannelType, + PipetteVersionType, + PipetteModelType, +) -LoadedConfiguration = Dict[types.PipetteChannelType, Dict[types.PipetteModelType, Any]] +LoadedConfiguration = Dict[PipetteChannelType, Dict[PipetteModelType, Any]] -def _build_configuration_dictionary( - rel_path: str, version: types.PipetteVersionType +def _get_configuration_dictionary( + config_type: Literal["general", "geometry", "liquid"], + channels: PipetteChannelType, + max_volume: PipetteModelType, + version: PipetteVersionType, ) -> LoadedConfiguration: - _dict: LoadedConfiguration = {} - for pipette_type in types.PipetteChannelType: - pipette_type_path = get_shared_data_root() / rel_path / pipette_type.value - _dict[pipette_type] = {} - for dir_name in os.scandir(pipette_type_path): - model_key = types.PipetteModelType.convert_from_model(dir_name.name) - _dict[pipette_type][model_key] = json.loads( - load_shared_data( - f"{pipette_type_path}/{dir_name.name}/{version.major}_{version.minor}.json" - ) - ) - return _dict + config_path = ( + get_shared_data_root() + / "pipette" + / "definitions" + / "2" + / config_type + / channels.name.lower() + / max_volume.value + / f"{version.major}_{version.minor}.json" + ) + return json.loads(load_shared_data(config_path)) @lru_cache(maxsize=None) -def _geometry(version: types.PipetteVersionType) -> LoadedConfiguration: - return _build_configuration_dictionary("pipette/definitions/2/geometry", version) +def _geometry( + channels: PipetteChannelType, + max_volume: PipetteModelType, + version: PipetteVersionType, +) -> LoadedConfiguration: + return _get_configuration_dictionary("geometry", channels, max_volume, version) @lru_cache(maxsize=None) -def _liquid(version: types.PipetteVersionType) -> LoadedConfiguration: - return _build_configuration_dictionary("pipette/definitions/2/liquid", version) +def _liquid( + channels: PipetteChannelType, + max_volume: PipetteModelType, + version: PipetteVersionType, +) -> LoadedConfiguration: + return _get_configuration_dictionary("liquid", channels, max_volume, version) @lru_cache(maxsize=None) -def _physical(version: types.PipetteVersionType) -> LoadedConfiguration: - return _build_configuration_dictionary("pipette/definitions/2/general", version) +def _physical( + channels: PipetteChannelType, + max_volume: PipetteModelType, + version: PipetteVersionType, +) -> LoadedConfiguration: + return _get_configuration_dictionary("general", channels, max_volume, version) def load_definition( - max_volume: types.PipetteModelType, - channels: types.PipetteChannelType, - version: types.PipetteVersionType, + max_volume: PipetteModelType, + channels: PipetteChannelType, + version: PipetteVersionType, ) -> PipetteConfigurations: - geometry_dict = _geometry(version)[channels][max_volume] - physical_dict = _physical(version)[channels][max_volume] - liquid_dict = _liquid(version)[channels][max_volume] + geometry_dict = _geometry(channels, max_volume, version) + physical_dict = _physical(channels, max_volume, version) + liquid_dict = _liquid(channels, max_volume, version) return PipetteConfigurations.parse_obj( {**geometry_dict, **physical_dict, **liquid_dict} diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index e7a86128edb..7b7ca353c52 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -1,6 +1,50 @@ -from typing import List, Dict, Tuple +from typing_extensions import Literal +from typing import List, Dict, Tuple, cast from pydantic import BaseModel, Field, validator -from .types import PipetteModelType, PipetteTipType, PipetteChannelType +from enum import Enum +from dataclasses import dataclass + +PLUNGER_CURRENT_MINIMUM = 0.1 +PLUNGER_CURRENT_MAXIMUM = 1.5 + + +PipetteModelMajorVersion = Literal[1] +PipetteModelMinorVersion = Literal[0, 1, 2, 3] + + +class PipetteTipType(Enum): + t50 = "t50" + t200 = "t200" + t1000 = "t1000" + + +class PipetteChannelType(Enum): + SINGLE_CHANNEL = 1 + EIGHT_CHANNEL = 8 + NINETY_SIX_CHANNEL = 96 + + @property + def as_int(self) -> int: + return self.value + + +class PipetteModelType(Enum): + p50 = "p50" + p1000 = "p1000" + + +@dataclass(frozen=True) +class PipetteVersionType: + major: PipetteModelMajorVersion + minor: PipetteModelMinorVersion + + @classmethod + def convert_from_float(cls, version: float) -> "PipetteVersionType": + major = cast(PipetteModelMajorVersion, int(version // 1)) + minor = cast( + PipetteModelMinorVersion, int(round((version % 1), 2) * 10) + ) + return cls(major=major, minor=minor) class SupportedTipsDefinition(BaseModel): @@ -134,11 +178,11 @@ class PipettePhysicalPropertiesDefinition(BaseModel): @validator("pipette_type", pre=True) def convert_pipette_model_string(cls, v: str) -> PipetteModelType: - return PipetteModelType.convert_from_model(v) + return PipetteModelType(v) @validator("channels", pre=True) def convert_channels(cls, v: int) -> PipetteChannelType: - return PipetteChannelType.convert_from_channels(v) + return PipetteChannelType(v) class PipetteGeometryDefinition(BaseModel): diff --git a/shared-data/python/opentrons_shared_data/pipette/types.py b/shared-data/python/opentrons_shared_data/pipette/types.py deleted file mode 100644 index a1840725bba..00000000000 --- a/shared-data/python/opentrons_shared_data/pipette/types.py +++ /dev/null @@ -1,67 +0,0 @@ -from typing import cast -from enum import Enum -from dataclasses import dataclass - -from . import constants - - -class UnsupportedNumberOfChannels(Exception): - pass - - -class PipetteTipType(Enum): - t50 = "t50" - t200 = "t200" - t1000 = "t1000" - - -class PipetteChannelType(Enum): - SINGLE_CHANNEL = "single_channel" - EIGHT_CHANNEL = "eight_channel" - NINETY_SIX_CHANNEL = "ninety_six_channel" - - @classmethod - def convert_from_channels(cls, channels: int) -> "PipetteChannelType": - if channels == 96: - return cls.NINETY_SIX_CHANNEL - elif channels == 8: - return cls.EIGHT_CHANNEL - elif channels == 1: - return cls.SINGLE_CHANNEL - else: - raise UnsupportedNumberOfChannels( - f"A pipette with {channels} channels is not available at this time." - ) - - @property - def as_int(self) -> int: - if self.value == "ninety_six_channel": - return 96 - elif self.value == "eight_channel": - return 8 - elif self.value == "single_channel": - return 1 - return 0 - - -class PipetteModelType(Enum): - P50 = "p50" - P1000 = "p1000" - - @classmethod - def convert_from_model(cls, model: str) -> "PipetteModelType": - return cls[model.upper()] - - -@dataclass(frozen=True) -class PipetteVersionType: - major: constants.PipetteModelMajorVersion - minor: constants.PipetteModelMinorVersion - - @classmethod - def convert_from_float(cls, version: float) -> "PipetteVersionType": - major = cast(constants.PipetteModelMajorVersion, int(version // 1)) - minor = cast( - constants.PipetteModelMinorVersion, int(round((version % 1), 2) * 10) - ) - return cls(major=major, minor=minor) diff --git a/shared-data/python/tests/pipette/test_load_data.py b/shared-data/python/tests/pipette/test_load_data.py index a27504bac28..3a740678398 100644 --- a/shared-data/python/tests/pipette/test_load_data.py +++ b/shared-data/python/tests/pipette/test_load_data.py @@ -1,14 +1,20 @@ import json -from opentrons_shared_data.pipette import load_data, types +from opentrons_shared_data.pipette import load_data +from opentrons_shared_data.pipette.pipette_definition import ( + PipetteChannelType, + PipetteModelType, + PipetteVersionType, + PipetteTipType, +) from opentrons_shared_data import load_shared_data def test_load_pipette_definition() -> None: pipette_config = load_data.load_definition( - types.PipetteModelType.P50, - types.PipetteChannelType.SINGLE_CHANNEL, - types.PipetteVersionType(major=1, minor=0), + PipetteModelType.p50, + PipetteChannelType.SINGLE_CHANNEL, + PipetteVersionType(major=1, minor=0), ) liquid = json.loads( load_shared_data("pipette/definitions/2/liquid/single_channel/p50/1_0.json") @@ -25,8 +31,6 @@ def test_load_pipette_definition() -> None: assert pipette_config.nozzle_offset == geometry["nozzleOffset"] assert ( - pipette_config.supported_tips[ - types.PipetteTipType.t50 - ].default_aspirate_flowrate + pipette_config.supported_tips[PipetteTipType.t50].default_aspirate_flowrate == liquid["supportedTips"]["t50"]["defaultAspirateFlowRate"] ) diff --git a/shared-data/python/tests/pipette/test_types.py b/shared-data/python/tests/pipette/test_pipette_definition.py similarity index 57% rename from shared-data/python/tests/pipette/test_types.py rename to shared-data/python/tests/pipette/test_pipette_definition.py index 1b9d1aa1cd8..0366fa1354c 100644 --- a/shared-data/python/tests/pipette/test_types.py +++ b/shared-data/python/tests/pipette/test_pipette_definition.py @@ -1,32 +1,31 @@ import pytest -from opentrons_shared_data.pipette.types import ( +from opentrons_shared_data.pipette.pipette_definition import ( PipetteChannelType, PipetteModelType, PipetteVersionType, - UnsupportedNumberOfChannels, ) @pytest.mark.parametrize( argnames=["model", "expected_enum"], - argvalues=[["p50", PipetteModelType.P50], ["p1000", PipetteModelType.P1000]], + argvalues=[["p50", PipetteModelType.p50], ["p1000", PipetteModelType.p1000]], ) def test_model_enum(model: str, expected_enum: PipetteModelType) -> None: - assert expected_enum == PipetteModelType.convert_from_model(model) + assert expected_enum == PipetteModelType(model) @pytest.mark.parametrize(argnames="channels", argvalues=[1, 8, 96]) def test_channel_enum(channels: int) -> None: - channel_type = PipetteChannelType.convert_from_channels(channels) + channel_type = PipetteChannelType(channels) assert channels == channel_type.as_int def test_incorrect_values() -> None: - with pytest.raises(KeyError): - PipetteModelType.convert_from_model("p100") + with pytest.raises(ValueError): + PipetteModelType("p100") - with pytest.raises(UnsupportedNumberOfChannels): - PipetteChannelType.convert_from_channels(99) + with pytest.raises(ValueError): + PipetteChannelType(99) @pytest.mark.parametrize( @@ -35,7 +34,5 @@ def test_incorrect_values() -> None: ) def test_version_enum(version: float, major: int, minor: int) -> None: version_type = PipetteVersionType.convert_from_float(version) - conversion_test = round(version % 1, 2) - print(conversion_test) assert version_type.major == major assert version_type.minor == minor From b2a4bb150ac3c613e73f64713a6b53508f5bd69f Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 1 Dec 2022 16:23:22 -0500 Subject: [PATCH 7/7] fixup test --- .../pipette/pipette_definition.py | 4 +--- .../python/tests/pipette/test_load_data.py | 20 ++++--------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index 7b7ca353c52..ebe02c72232 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -41,9 +41,7 @@ class PipetteVersionType: @classmethod def convert_from_float(cls, version: float) -> "PipetteVersionType": major = cast(PipetteModelMajorVersion, int(version // 1)) - minor = cast( - PipetteModelMinorVersion, int(round((version % 1), 2) * 10) - ) + minor = cast(PipetteModelMinorVersion, int(round((version % 1), 2) * 10)) return cls(major=major, minor=minor) diff --git a/shared-data/python/tests/pipette/test_load_data.py b/shared-data/python/tests/pipette/test_load_data.py index 3a740678398..4514584894b 100644 --- a/shared-data/python/tests/pipette/test_load_data.py +++ b/shared-data/python/tests/pipette/test_load_data.py @@ -1,4 +1,3 @@ -import json from opentrons_shared_data.pipette import load_data from opentrons_shared_data.pipette.pipette_definition import ( PipetteChannelType, @@ -7,8 +6,6 @@ PipetteTipType, ) -from opentrons_shared_data import load_shared_data - def test_load_pipette_definition() -> None: pipette_config = load_data.load_definition( @@ -16,21 +13,12 @@ def test_load_pipette_definition() -> None: PipetteChannelType.SINGLE_CHANNEL, PipetteVersionType(major=1, minor=0), ) - liquid = json.loads( - load_shared_data("pipette/definitions/2/liquid/single_channel/p50/1_0.json") - ) - geometry = json.loads( - load_shared_data("pipette/definitions/2/geometry/single_channel/p50/1_0.json") - ) - general = json.loads( - load_shared_data("pipette/definitions/2/general/single_channel/p50/1_0.json") - ) - assert pipette_config.channels.as_int == general["channels"] - assert pipette_config.pipette_type.value == general["model"] - assert pipette_config.nozzle_offset == geometry["nozzleOffset"] + assert pipette_config.channels.as_int == 1 + assert pipette_config.pipette_type.value == "p50" + assert pipette_config.nozzle_offset == [-8.0, -22.0, -259.15] assert ( pipette_config.supported_tips[PipetteTipType.t50].default_aspirate_flowrate - == liquid["supportedTips"]["t50"]["defaultAspirateFlowRate"] + == 8.0 )