Skip to content

Commit 1edb0fb

Browse files
refactor(api): Delete dead PipetteStore code and type nozzle maps as non-Optional (#16481)
1 parent df99ac4 commit 1edb0fb

File tree

6 files changed

+90
-54
lines changed

6 files changed

+90
-54
lines changed

api/src/opentrons/protocol_engine/state/pipettes.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,17 @@ class StaticPipetteConfig:
9696
nozzle_offset_z: float
9797
pipette_bounding_box_offsets: PipetteBoundingBoxOffsets
9898
bounding_nozzle_offsets: BoundingNozzlesOffsets
99-
default_nozzle_map: NozzleMap
99+
default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove?
100100
lld_settings: Optional[Dict[str, Dict[str, float]]]
101101

102102

103103
@dataclasses.dataclass
104104
class PipetteState:
105105
"""Basic pipette data state and getter methods."""
106106

107+
# todo(mm, 2024-10-14): It's getting difficult to ensure that all of these
108+
# attributes are populated at the appropriate times. Refactor to a
109+
# single dict-of-many-things instead of many dicts-of-single-things.
107110
pipettes_by_id: Dict[str, LoadedPipette]
108111
aspirated_volume_by_id: Dict[str, Optional[float]]
109112
current_location: Optional[CurrentPipetteLocation]
@@ -112,7 +115,7 @@ class PipetteState:
112115
movement_speed_by_id: Dict[str, Optional[float]]
113116
static_config_by_id: Dict[str, StaticPipetteConfig]
114117
flow_rates_by_id: Dict[str, FlowRates]
115-
nozzle_configuration_by_id: Dict[str, Optional[NozzleMap]]
118+
nozzle_configuration_by_id: Dict[str, NozzleMap]
116119
liquid_presence_detection_by_id: Dict[str, bool]
117120

118121

@@ -167,11 +170,6 @@ def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None:
167170
self._state.aspirated_volume_by_id[pipette_id] = None
168171
self._state.movement_speed_by_id[pipette_id] = None
169172
self._state.attached_tip_by_id[pipette_id] = None
170-
static_config = self._state.static_config_by_id.get(pipette_id)
171-
if static_config:
172-
self._state.nozzle_configuration_by_id[
173-
pipette_id
174-
] = static_config.default_nozzle_map
175173

176174
def _update_tip_state(self, state_update: update_types.StateUpdate) -> None:
177175
if state_update.pipette_tip_state != update_types.NO_CHANGE:
@@ -632,31 +630,23 @@ def get_plunger_axis(self, pipette_id: str) -> MotorAxis:
632630

633631
def get_nozzle_layout_type(self, pipette_id: str) -> NozzleConfigurationType:
634632
"""Get the current set nozzle layout configuration."""
635-
nozzle_map_for_pipette = self._state.nozzle_configuration_by_id.get(pipette_id)
636-
if nozzle_map_for_pipette:
637-
return nozzle_map_for_pipette.configuration
638-
else:
639-
return NozzleConfigurationType.FULL
633+
nozzle_map_for_pipette = self._state.nozzle_configuration_by_id[pipette_id]
634+
return nozzle_map_for_pipette.configuration
640635

641636
def get_is_partially_configured(self, pipette_id: str) -> bool:
642637
"""Determine if the provided pipette is partially configured."""
643638
return self.get_nozzle_layout_type(pipette_id) != NozzleConfigurationType.FULL
644639

645-
def get_primary_nozzle(self, pipette_id: str) -> Optional[str]:
640+
def get_primary_nozzle(self, pipette_id: str) -> str:
646641
"""Get the primary nozzle, if any, related to the given pipette's nozzle configuration."""
647-
nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id)
648-
return nozzle_map.starting_nozzle if nozzle_map else None
642+
nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
643+
return nozzle_map.starting_nozzle
649644

650645
def _get_critical_point_offset_without_tip(
651646
self, pipette_id: str, critical_point: Optional[CriticalPoint]
652647
) -> Point:
653648
"""Get the offset of the specified critical point from pipette's mount position."""
654-
nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id)
655-
# Nozzle map is unavailable only when there's no pipette loaded
656-
# so this is merely for satisfying the type checker
657-
assert (
658-
nozzle_map is not None
659-
), "Error getting critical point offset. Nozzle map not found."
649+
nozzle_map = self._state.nozzle_configuration_by_id[pipette_id]
660650
match critical_point:
661651
case CriticalPoint.INSTRUMENT_XY_CENTER:
662652
return nozzle_map.instrument_xy_center_offset

api/src/opentrons/protocol_engine/state/update_types.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ class AddressableArea:
6666

6767
@dataclasses.dataclass
6868
class PipetteLocationUpdate:
69-
"""Represents an update to perform on a pipette's location."""
69+
"""An update to a pipette's location."""
7070

7171
pipette_id: str
72+
"""The ID of the already-loaded pipette."""
7273

7374
new_location: Well | AddressableArea | None | NoChangeType
7475
"""The pipette's new logical location.
@@ -82,19 +83,30 @@ class PipetteLocationUpdate:
8283

8384
@dataclasses.dataclass
8485
class LabwareLocationUpdate:
85-
"""Represents an update to perform on a labware's location."""
86+
"""An update to a labware's location."""
8687

8788
labware_id: str
89+
"""The ID of the already-loaded labware."""
8890

8991
new_location: LabwareLocation
90-
"""The labware's new logical location."""
92+
"""The labware's new location."""
9193

9294
offset_id: typing.Optional[str]
95+
"""The ID of the labware's new offset, for its new location."""
9396

9497

9598
@dataclasses.dataclass
96-
class LoadedLabwareUpdate(LabwareLocationUpdate):
97-
"""Update loaded labware."""
99+
class LoadedLabwareUpdate:
100+
"""An update that loads a new labware."""
101+
102+
labware_id: str
103+
"""The unique ID of the new labware."""
104+
105+
new_location: LabwareLocation
106+
"""The labware's initial location."""
107+
108+
offset_id: typing.Optional[str]
109+
"""The ID of the labware's offset."""
98110

99111
display_name: typing.Optional[str]
100112

@@ -103,20 +115,30 @@ class LoadedLabwareUpdate(LabwareLocationUpdate):
103115

104116
@dataclasses.dataclass
105117
class LoadPipetteUpdate:
106-
"""Update loaded pipette."""
118+
"""An update that loads a new pipette.
119+
120+
NOTE: Currently, if this is provided, a PipetteConfigUpdate must always be
121+
provided alongside it to fully initialize everything.
122+
"""
107123

108124
pipette_id: str
125+
"""The unique ID of the new pipette."""
126+
109127
pipette_name: PipetteNameType
110128
mount: MountType
111129
liquid_presence_detection: typing.Optional[bool]
112130

113131

114132
@dataclasses.dataclass
115133
class PipetteConfigUpdate:
116-
"""Update pipette config."""
134+
"""An update to a pipette's config."""
117135

118136
pipette_id: str
137+
"""The ID of the already-loaded pipette."""
138+
139+
# todo(mm, 2024-10-14): Does serial_number belong in LoadPipetteUpdate?
119140
serial_number: str
141+
120142
config: pipette_data_provider.LoadedStaticPipetteData
121143

122144

@@ -237,7 +259,7 @@ def set_labware_location(
237259
new_location: LabwareLocation,
238260
new_offset_id: str | None,
239261
) -> None:
240-
"""Set labware location."""
262+
"""Set a labware's location. See `LabwareLocationUpdate`."""
241263
self.labware_location = LabwareLocationUpdate(
242264
labware_id=labware_id,
243265
new_location=new_location,
@@ -252,7 +274,7 @@ def set_loaded_labware(
252274
display_name: typing.Optional[str],
253275
location: LabwareLocation,
254276
) -> None:
255-
"""Add loaded labware to state."""
277+
"""Add a new labware to state. See `LoadedLabwareUpdate`."""
256278
self.loaded_labware = LoadedLabwareUpdate(
257279
definition=definition,
258280
labware_id=labware_id,
@@ -268,7 +290,7 @@ def set_load_pipette(
268290
mount: MountType,
269291
liquid_presence_detection: typing.Optional[bool],
270292
) -> None:
271-
"""Add loaded pipette to state."""
293+
"""Add a new pipette to state. See `LoadPipetteUpdate`."""
272294
self.loaded_pipette = LoadPipetteUpdate(
273295
pipette_id=pipette_id,
274296
pipette_name=pipette_name,
@@ -282,29 +304,29 @@ def update_pipette_config(
282304
config: pipette_data_provider.LoadedStaticPipetteData,
283305
serial_number: str,
284306
) -> None:
285-
"""Update pipette config."""
307+
"""Update a pipette's config. See `PipetteConfigUpdate`."""
286308
self.pipette_config = PipetteConfigUpdate(
287309
pipette_id=pipette_id, config=config, serial_number=serial_number
288310
)
289311

290312
def update_pipette_nozzle(self, pipette_id: str, nozzle_map: NozzleMap) -> None:
291-
"""Update pipette nozzle map."""
313+
"""Update a pipette's nozzle map. See `PipetteNozzleMapUpdate`."""
292314
self.pipette_nozzle_map = PipetteNozzleMapUpdate(
293315
pipette_id=pipette_id, nozzle_map=nozzle_map
294316
)
295317

296318
def update_pipette_tip_state(
297319
self, pipette_id: str, tip_geometry: typing.Optional[TipGeometry]
298320
) -> None:
299-
"""Update tip state."""
321+
"""Update a pipette's tip state. See `PipetteTipStateUpdate`."""
300322
self.pipette_tip_state = PipetteTipStateUpdate(
301323
pipette_id=pipette_id, tip_geometry=tip_geometry
302324
)
303325

304326
def mark_tips_as_used(
305327
self, pipette_id: str, labware_id: str, well_name: str
306328
) -> None:
307-
"""Mark tips in a tip rack as used. See `MarkTipsUsedState`."""
329+
"""Mark tips in a tip rack as used. See `TipsUsedUpdate`."""
308330
self.tips_used = TipsUsedUpdate(
309331
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
310332
)

api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Test for the ProtocolEngine-based instrument API core."""
2-
from typing import cast, Optional, Union
2+
from typing import cast, Optional
33

44
from opentrons_shared_data.errors.exceptions import PipetteLiquidNotFoundError
55
import pytest
@@ -1227,17 +1227,14 @@ def test_configure_nozzle_layout(
12271227
argnames=["pipette_channels", "nozzle_layout", "primary_nozzle", "expected_result"],
12281228
argvalues=[
12291229
(96, NozzleConfigurationType.FULL, "A1", True),
1230-
(96, NozzleConfigurationType.FULL, None, True),
12311230
(96, NozzleConfigurationType.ROW, "A1", True),
12321231
(96, NozzleConfigurationType.COLUMN, "A1", True),
12331232
(96, NozzleConfigurationType.COLUMN, "A12", True),
12341233
(96, NozzleConfigurationType.SINGLE, "H12", True),
12351234
(96, NozzleConfigurationType.SINGLE, "A1", True),
12361235
(8, NozzleConfigurationType.FULL, "A1", True),
1237-
(8, NozzleConfigurationType.FULL, None, True),
12381236
(8, NozzleConfigurationType.SINGLE, "H1", True),
12391237
(8, NozzleConfigurationType.SINGLE, "A1", True),
1240-
(1, NozzleConfigurationType.FULL, None, True),
12411238
],
12421239
)
12431240
def test_is_tip_tracking_available(
@@ -1246,7 +1243,7 @@ def test_is_tip_tracking_available(
12461243
subject: InstrumentCore,
12471244
pipette_channels: int,
12481245
nozzle_layout: NozzleConfigurationType,
1249-
primary_nozzle: Union[str, None],
1246+
primary_nozzle: str,
12501247
expected_result: bool,
12511248
) -> None:
12521249
"""It should return whether tip tracking is available based on nozzle configuration."""

api/tests/opentrons/protocol_engine/state/test_pipette_store.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,25 +191,52 @@ def test_location_state_update(subject: PipetteStore) -> None:
191191
)
192192

193193

194-
def test_handles_load_pipette(subject: PipetteStore) -> None:
194+
def test_handles_load_pipette(
195+
subject: PipetteStore,
196+
supported_tip_fixture: pipette_definition.SupportedTipsDefinition,
197+
) -> None:
195198
"""It should add the pipette data to the state."""
196-
command = create_load_pipette_command(
199+
dummy_command = create_succeeded_command()
200+
201+
load_pipette_update = update_types.LoadPipetteUpdate(
197202
pipette_id="pipette-id",
198203
pipette_name=PipetteNameType.P300_SINGLE,
199204
mount=MountType.LEFT,
205+
liquid_presence_detection=None,
206+
)
207+
208+
config = LoadedStaticPipetteData(
209+
model="pipette-model",
210+
display_name="pipette name",
211+
min_volume=1.23,
212+
max_volume=4.56,
213+
channels=7,
214+
flow_rates=FlowRates(
215+
default_aspirate={"a": 1},
216+
default_dispense={"b": 2},
217+
default_blow_out={"c": 3},
218+
),
219+
tip_configuration_lookup_table={4: supported_tip_fixture},
220+
nominal_tip_overlap={"default": 5},
221+
home_position=8.9,
222+
nozzle_offset_z=10.11,
223+
nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE),
224+
back_left_corner_offset=Point(x=1, y=2, z=3),
225+
front_right_corner_offset=Point(x=4, y=5, z=6),
226+
pipette_lld_settings={},
227+
)
228+
config_update = update_types.PipetteConfigUpdate(
229+
pipette_id="pipette-id",
230+
config=config,
231+
serial_number="pipette-serial",
200232
)
201233

202234
subject.handle_action(
203235
SucceedCommandAction(
204236
private_result=None,
205-
command=command,
237+
command=dummy_command,
206238
state_update=update_types.StateUpdate(
207-
loaded_pipette=update_types.LoadPipetteUpdate(
208-
pipette_id="pipette-id",
209-
pipette_name=PipetteNameType.P300_SINGLE,
210-
mount=MountType.LEFT,
211-
liquid_presence_detection=None,
212-
)
239+
loaded_pipette=load_pipette_update, pipette_config=config_update
213240
),
214241
)
215242
)

api/tests/opentrons/protocol_engine/state/test_pipette_view.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def get_pipette_view(
6565
movement_speed_by_id: Optional[Dict[str, Optional[float]]] = None,
6666
static_config_by_id: Optional[Dict[str, StaticPipetteConfig]] = None,
6767
flow_rates_by_id: Optional[Dict[str, FlowRates]] = None,
68-
nozzle_layout_by_id: Optional[Dict[str, Optional[NozzleMap]]] = None,
68+
nozzle_layout_by_id: Optional[Dict[str, NozzleMap]] = None,
6969
liquid_presence_detection_by_id: Optional[Dict[str, bool]] = None,
7070
) -> PipetteView:
7171
"""Get a pipette view test subject with the specified state."""

api/tests/opentrons/protocol_engine/state/test_tip_state.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,24 +1371,24 @@ def _reconfigure_nozzle_layout(start: str, back_l: str, front_r: str) -> NozzleM
13711371
return nozzle_map
13721372

13731373
map = _reconfigure_nozzle_layout("A1", "A1", "A1")
1374-
for x in range(96):
1374+
for _ in range(96):
13751375
_get_next_and_pickup(map)
13761376
assert _get_next_and_pickup(map) is None
13771377

13781378
subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware"))
13791379
map = _reconfigure_nozzle_layout("A12", "A12", "A12")
1380-
for x in range(96):
1380+
for _ in range(96):
13811381
_get_next_and_pickup(map)
13821382
assert _get_next_and_pickup(map) is None
13831383

13841384
subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware"))
13851385
map = _reconfigure_nozzle_layout("H1", "H1", "H1")
1386-
for x in range(96):
1386+
for _ in range(96):
13871387
_get_next_and_pickup(map)
13881388
assert _get_next_and_pickup(map) is None
13891389

13901390
subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware"))
13911391
map = _reconfigure_nozzle_layout("H12", "H12", "H12")
1392-
for x in range(96):
1392+
for _ in range(96):
13931393
_get_next_and_pickup(map)
13941394
assert _get_next_and_pickup(map) is None

0 commit comments

Comments
 (0)