opentrons 8.6.0a3__py3-none-any.whl → 8.6.0a5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
opentrons/_version.py CHANGED
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '8.6.0a3'
21
- __version_tuple__ = version_tuple = (8, 6, 0, 'a3')
31
+ __version__ = version = '8.6.0a5'
32
+ __version_tuple__ = version_tuple = (8, 6, 0, 'a5')
33
+
34
+ __commit_id__ = commit_id = None
@@ -120,8 +120,12 @@ class AsyncSerial:
120
120
  """
121
121
  if self._reset_buffer_before_write:
122
122
  self._serial.reset_input_buffer()
123
- self._serial.write(data=data)
124
- self._serial.flush()
123
+ self._serial.write(data)
124
+ # flush can fail after entering dfu mode, ignore any exceptions.
125
+ try:
126
+ self._serial.flush()
127
+ except Exception:
128
+ pass
125
129
 
126
130
  async def open(self) -> None:
127
131
  """
@@ -1,4 +1,5 @@
1
1
  """Check a deck layout for conflicts."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from dataclasses import dataclass
@@ -97,7 +98,19 @@ class ThermocyclerModule(_Module):
97
98
 
98
99
  @dataclass
99
100
  class OtherModule(_Module):
100
- """A module that's not a Heater-Shaker or Thermocycler."""
101
+ """A module that's not a Heater-Shaker or Thermocycler or stacker."""
102
+
103
+
104
+ @dataclass
105
+ class FlexStackerModule(_Module):
106
+ """A stacker where nothing else is known to be in the "location" in column 3 that it lives."""
107
+
108
+
109
+ @dataclass
110
+ class FlexStackerModuleKindaButSomethingElseReally(_Module):
111
+ """A stacker where something else is also in the "location" in column 3 that it lives."""
112
+
113
+ original_item: "DeckItem"
101
114
 
102
115
 
103
116
  DeckItem = Union[
@@ -107,6 +120,8 @@ DeckItem = Union[
107
120
  ThermocyclerModule,
108
121
  OtherModule,
109
122
  TrashBin,
123
+ FlexStackerModule,
124
+ FlexStackerModuleKindaButSomethingElseReally,
110
125
  ]
111
126
 
112
127
 
@@ -121,6 +136,19 @@ class _NothingAllowed(NamedTuple):
121
136
  return False
122
137
 
123
138
 
139
+ class _NothingButStackerAllowed(NamedTuple):
140
+ """Nothing (in the Odyssean sense) is allowed in this slot."""
141
+
142
+ location: Union[DeckSlotName, StagingSlotName]
143
+ source_item: DeckItem
144
+ source_location: Union[DeckSlotName, StagingSlotName]
145
+
146
+ def is_allowed(self, item: DeckItem) -> bool:
147
+ if isinstance(item, FlexStackerModule):
148
+ return True
149
+ return False
150
+
151
+
124
152
  class _MaxHeight(NamedTuple):
125
153
  """Nothing over a certain height is allowed in this slot."""
126
154
 
@@ -169,6 +197,7 @@ _DeckRestriction = Union[
169
197
  _MaxHeight,
170
198
  _NoModule,
171
199
  _NoHeaterShakerModule,
200
+ _NothingButStackerAllowed,
172
201
  ]
173
202
  """A restriction on what is allowed in a given slot."""
174
203
 
@@ -293,16 +322,10 @@ def _create_ot2_restrictions( # noqa: C901
293
322
  return restrictions
294
323
 
295
324
 
296
- def _create_flex_restrictions(
325
+ def _create_flex_restrictions( # noqa: C901
297
326
  item: DeckItem, location: Union[DeckSlotName, StagingSlotName]
298
327
  ) -> List[_DeckRestriction]:
299
- restrictions: List[_DeckRestriction] = [
300
- _NothingAllowed(
301
- location=location,
302
- source_item=item,
303
- source_location=location,
304
- )
305
- ]
328
+ restrictions: List[_DeckRestriction] = []
306
329
 
307
330
  if isinstance(item, (HeaterShakerModule, OtherModule)):
308
331
  if isinstance(location, StagingSlotName):
@@ -321,6 +344,58 @@ def _create_flex_restrictions(
321
344
  source_location=location,
322
345
  )
323
346
  )
347
+ restrictions.append(
348
+ _NothingAllowed(
349
+ location=location,
350
+ source_item=item,
351
+ source_location=location,
352
+ )
353
+ )
354
+ elif isinstance(item, FlexStackerModule):
355
+ if location not in _flex_slots_allowing_stacker():
356
+ raise DeckConflictError("Cannot place a Flex Stacker outside of column 3.")
357
+ # this is a typing assertion; the check above guarantees this is true
358
+ assert isinstance(location, DeckSlotName)
359
+ adjacent_staging_slot = get_adjacent_staging_slot(location)
360
+ # this is a typing assertion; the check above guarantees this isn't none
361
+ assert adjacent_staging_slot is not None
362
+ # nothing goes in the staging slot in the row the stacker is in, because the stacker is in the
363
+ # way (different from blocking you because of the design of the caddy).
364
+ restrictions.append(
365
+ _NothingAllowed(
366
+ location=adjacent_staging_slot,
367
+ source_item=item,
368
+ source_location=location,
369
+ )
370
+ )
371
+ # note that the stacker does NOT block use of the "slot" that it is "loaded in" because
372
+ # it is actually loaded in that cutout, and you can put a deck slot on top just fine
373
+ elif isinstance(item, FlexStackerModuleKindaButSomethingElseReally):
374
+ if location not in _flex_slots_allowing_stacker():
375
+ raise DeckConflictError("Cannot place a Flex Stacker outside of column 3.")
376
+ # this is a typing assertion; the check above guarantees this is true
377
+ assert isinstance(location, DeckSlotName)
378
+ adjacent_staging_slot = get_adjacent_staging_slot(location)
379
+ assert adjacent_staging_slot is not None
380
+ # nothing goes in the staging slot in the row the stacker is in, because the stacker is in the
381
+ # way (different from blocking you because of the design of the caddy).
382
+ restrictions.append(
383
+ _NothingAllowed(
384
+ location=adjacent_staging_slot,
385
+ source_item=item,
386
+ source_location=location,
387
+ )
388
+ )
389
+ # while the stacker on its own doesn't block use of the "slot" that it is "loaded in",
390
+ # the kind of "flex stacker" that actually means "you loaded a labware in column 3 where a
391
+ # stacker is sort of also "loaded" in column 3" does
392
+ restrictions.append(
393
+ _NothingAllowed(
394
+ location=location,
395
+ source_item=item.original_item,
396
+ source_location=location,
397
+ )
398
+ )
324
399
 
325
400
  elif isinstance(item, ThermocyclerModule):
326
401
  for covered_location in _flex_slots_covered_by_thermocycler():
@@ -331,6 +406,27 @@ def _create_flex_restrictions(
331
406
  source_location=location,
332
407
  )
333
408
  )
409
+ restrictions.append(
410
+ _NothingAllowed(
411
+ location=location,
412
+ source_item=item,
413
+ source_location=location,
414
+ )
415
+ )
416
+ elif location in _flex_slots_allowing_stacker():
417
+ restrictions.append(
418
+ _NothingButStackerAllowed(
419
+ location=location,
420
+ source_item=item,
421
+ source_location=location,
422
+ )
423
+ )
424
+ else:
425
+ restrictions.append(
426
+ _NothingAllowed(
427
+ location=location, source_item=item, source_location=location
428
+ )
429
+ )
334
430
 
335
431
  return restrictions
336
432
 
@@ -390,6 +486,15 @@ def _flex_slots_covered_by_thermocycler() -> Set[DeckSlotName]:
390
486
  return {DeckSlotName.SLOT_B1, DeckSlotName.SLOT_A1}
391
487
 
392
488
 
489
+ def _flex_slots_allowing_stacker() -> Set[DeckSlotName]:
490
+ return {
491
+ DeckSlotName.SLOT_A3,
492
+ DeckSlotName.SLOT_B3,
493
+ DeckSlotName.SLOT_C3,
494
+ DeckSlotName.SLOT_D3,
495
+ }
496
+
497
+
393
498
  def _is_ot2_fixed_trash(item: DeckItem) -> bool:
394
499
  return (isinstance(item, Labware) and item.is_fixed_trash) or isinstance(
395
500
  item, TrashBin
@@ -176,8 +176,12 @@ def check(
176
176
  for existing_location, existing_item in itertools.chain(
177
177
  mapped_existing_labware, mapped_existing_modules, mapped_disposal_locations
178
178
  ):
179
- assert existing_location not in existing_items
180
- existing_items[existing_location] = existing_item
179
+ if existing_location not in existing_items:
180
+ existing_items[existing_location] = existing_item
181
+ else:
182
+ existing_items[existing_location] = _check_pair_compatibility(
183
+ existing_items[existing_location], existing_item, existing_location
184
+ )
181
185
 
182
186
  wrapped_deck_conflict.check(
183
187
  existing_items=existing_items,
@@ -187,6 +191,49 @@ def check(
187
191
  )
188
192
 
189
193
 
194
+ def _check_pair_compatibility(
195
+ item1: wrapped_deck_conflict.DeckItem,
196
+ item2: wrapped_deck_conflict.DeckItem,
197
+ location: Union[DeckSlotName, StagingSlotName],
198
+ ) -> wrapped_deck_conflict.DeckItem:
199
+ # if this is a stacker and something that can also "go" where a stacker "goes" (like a labware or magblock)
200
+ # then we build a combo; otherwise, we raise an error. this error in theory should never happen because to
201
+ # have the configuration that causes the error, it has to have passed the wrapped deck conflict checking,
202
+ # so there would be a bug in there, which is of course impossible.
203
+
204
+ def _check_pair_compat_once(
205
+ item1: wrapped_deck_conflict.DeckItem, item2: wrapped_deck_conflict.DeckItem
206
+ ) -> bool:
207
+ if isinstance(item1, wrapped_deck_conflict.FlexStackerModule) and isinstance(
208
+ item2,
209
+ (wrapped_deck_conflict.MagneticBlockModule, wrapped_deck_conflict.Labware),
210
+ ):
211
+ return True
212
+ return False
213
+
214
+ if _check_pair_compat_once(item1, item2) or _check_pair_compat_once(item2, item1):
215
+ not_stacker = (
216
+ item1
217
+ if not isinstance(item1, wrapped_deck_conflict.FlexStackerModule)
218
+ else item2
219
+ )
220
+ # type-only assertion: trash bins are not alowed in _check_pair_compat_once and
221
+ # so we would never get here
222
+ assert not isinstance(not_stacker, wrapped_deck_conflict.TrashBin)
223
+ return wrapped_deck_conflict.FlexStackerModuleKindaButSomethingElseReally(
224
+ name_for_errors=not_stacker.name_for_errors,
225
+ highest_z_including_labware=(
226
+ not_stacker.highest_z
227
+ if isinstance(not_stacker, wrapped_deck_conflict.Labware)
228
+ else not_stacker.highest_z_including_labware
229
+ ),
230
+ original_item=not_stacker,
231
+ )
232
+ raise wrapped_deck_conflict.DeckConflictError(
233
+ f"{item1.name_for_errors} and {item2.name_for_errors} cannot both be loaded in {location}"
234
+ )
235
+
236
+
190
237
  def _map_labware(
191
238
  engine_state: StateView,
192
239
  labware_id: str,
@@ -304,8 +351,13 @@ def _map_module(
304
351
  ),
305
352
  )
306
353
  elif module_type == ModuleType.FLEX_STACKER:
307
- # TODO: This is a placeholder. We need to implement this.
308
- return None
354
+ return (
355
+ mapped_location,
356
+ wrapped_deck_conflict.FlexStackerModule(
357
+ name_for_errors=name_for_errors,
358
+ highest_z_including_labware=highest_z_including_labware,
359
+ ),
360
+ )
309
361
  else:
310
362
  return (
311
363
  mapped_location,
@@ -72,6 +72,8 @@ _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN = APIVersion(2,
72
72
  """The version after which partial nozzle configurations of single, row, and partial column layouts became available."""
73
73
  _AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
74
74
  """The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking."""
75
+ _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN = APIVersion(2, 25)
76
+ """The version after which the user can supply liquid class transfers with non-assigned tip racks."""
75
77
 
76
78
 
77
79
  AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
@@ -1796,6 +1798,7 @@ class InstrumentContext(publisher.CommandPublisher):
1796
1798
  return_tip: bool = False,
1797
1799
  group_wells: bool = True,
1798
1800
  keep_last_tip: Optional[bool] = None,
1801
+ tip_racks: Optional[List[labware.Labware]] = None,
1799
1802
  ) -> InstrumentContext:
1800
1803
  """Move a particular type of liquid from one well or group of wells to another.
1801
1804
 
@@ -1832,6 +1835,12 @@ class InstrumentContext(publisher.CommandPublisher):
1832
1835
  :param keep_last_tip: When ``True``, the pipette keeps the last tip used in the transfer attached. When
1833
1836
  ``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
1834
1837
  ``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
1838
+ :param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
1839
+ up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
1840
+ value of ``InstrumentContext.tip_racks``.
1841
+
1842
+ .. versionchanged:: 2.25
1843
+ Added the ``tip_racks`` parameter.
1835
1844
 
1836
1845
  """
1837
1846
  if volume == 0.0:
@@ -1841,12 +1850,22 @@ class InstrumentContext(publisher.CommandPublisher):
1841
1850
  )
1842
1851
  return self
1843
1852
 
1853
+ if (
1854
+ tip_racks is not None
1855
+ and self.api_version < _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN
1856
+ ):
1857
+ raise APIVersionError(
1858
+ api_element="tip_racks",
1859
+ until_version="2.25",
1860
+ current_version=f"{self.api_version}",
1861
+ )
1862
+
1844
1863
  transfer_args = verify_and_normalize_transfer_args(
1845
1864
  source=source,
1846
1865
  dest=dest,
1847
1866
  tip_policy=new_tip,
1848
1867
  last_tip_well=self._get_current_tip_source_well(),
1849
- tip_racks=self._tip_racks,
1868
+ tip_racks=tip_racks or self._tip_racks,
1850
1869
  nozzle_map=self._core.get_nozzle_map(),
1851
1870
  group_wells_for_multi_channel=group_wells,
1852
1871
  current_volume=self.current_volume,
@@ -1875,6 +1894,9 @@ class InstrumentContext(publisher.CommandPublisher):
1875
1894
  for well in transfer_args.dest
1876
1895
  ]
1877
1896
 
1897
+ for tip_rack in transfer_args.tip_racks:
1898
+ instrument.validate_tiprack(self.name, tip_rack, _log)
1899
+
1878
1900
  with publisher.publish_context(
1879
1901
  broker=self.broker,
1880
1902
  command=cmds.transfer_with_liquid_class(
@@ -1924,6 +1946,7 @@ class InstrumentContext(publisher.CommandPublisher):
1924
1946
  return_tip: bool = False,
1925
1947
  group_wells: bool = True,
1926
1948
  keep_last_tip: Optional[bool] = None,
1949
+ tip_racks: Optional[List[labware.Labware]] = None,
1927
1950
  ) -> InstrumentContext:
1928
1951
  """
1929
1952
  Distribute a particular type of liquid from one well to a group of wells.
@@ -1957,6 +1980,12 @@ class InstrumentContext(publisher.CommandPublisher):
1957
1980
  :param keep_last_tip: When ``True``, the pipette keeps the last tip used in the distribute attached. When
1958
1981
  ``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
1959
1982
  ``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
1983
+ :param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
1984
+ up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
1985
+ value of ``InstrumentContext.tip_racks``.
1986
+
1987
+ .. versionchanged:: 2.25
1988
+ Added the ``tip_racks`` parameter.
1960
1989
 
1961
1990
  """
1962
1991
  if volume == 0.0:
@@ -1966,12 +1995,22 @@ class InstrumentContext(publisher.CommandPublisher):
1966
1995
  )
1967
1996
  return self
1968
1997
 
1998
+ if (
1999
+ tip_racks is not None
2000
+ and self.api_version < _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN
2001
+ ):
2002
+ raise APIVersionError(
2003
+ api_element="tip_racks",
2004
+ until_version="2.25",
2005
+ current_version=f"{self.api_version}",
2006
+ )
2007
+
1969
2008
  transfer_args = verify_and_normalize_transfer_args(
1970
2009
  source=source,
1971
2010
  dest=dest,
1972
2011
  tip_policy=new_tip,
1973
2012
  last_tip_well=self._get_current_tip_source_well(),
1974
- tip_racks=self._tip_racks,
2013
+ tip_racks=tip_racks or self._tip_racks,
1975
2014
  nozzle_map=self._core.get_nozzle_map(),
1976
2015
  group_wells_for_multi_channel=group_wells,
1977
2016
  current_volume=self.current_volume,
@@ -2004,6 +2043,9 @@ class InstrumentContext(publisher.CommandPublisher):
2004
2043
  f" 'once', 'never' and 'always'."
2005
2044
  )
2006
2045
 
2046
+ for tip_rack in transfer_args.tip_racks:
2047
+ instrument.validate_tiprack(self.name, tip_rack, _log)
2048
+
2007
2049
  verified_source = transfer_args.source[0]
2008
2050
  with publisher.publish_context(
2009
2051
  broker=self.broker,
@@ -2057,6 +2099,7 @@ class InstrumentContext(publisher.CommandPublisher):
2057
2099
  return_tip: bool = False,
2058
2100
  group_wells: bool = True,
2059
2101
  keep_last_tip: Optional[bool] = None,
2102
+ tip_racks: Optional[List[labware.Labware]] = None,
2060
2103
  ) -> InstrumentContext:
2061
2104
  """
2062
2105
  Consolidate a particular type of liquid from a group of wells to one well.
@@ -2091,6 +2134,12 @@ class InstrumentContext(publisher.CommandPublisher):
2091
2134
  :param keep_last_tip: When ``True``, the pipette keeps the last tip used in the consolidate attached. When
2092
2135
  ``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
2093
2136
  ``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
2137
+ :param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
2138
+ up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
2139
+ value of ``InstrumentContext.tip_racks``.
2140
+
2141
+ .. versionchanged:: 2.25
2142
+ Added the ``tip_racks`` parameter.
2094
2143
 
2095
2144
  """
2096
2145
  if volume == 0.0:
@@ -2100,12 +2149,22 @@ class InstrumentContext(publisher.CommandPublisher):
2100
2149
  )
2101
2150
  return self
2102
2151
 
2152
+ if (
2153
+ tip_racks is not None
2154
+ and self.api_version < _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN
2155
+ ):
2156
+ raise APIVersionError(
2157
+ api_element="tip_racks",
2158
+ until_version="2.25",
2159
+ current_version=f"{self.api_version}",
2160
+ )
2161
+
2103
2162
  transfer_args = verify_and_normalize_transfer_args(
2104
2163
  source=source,
2105
2164
  dest=dest,
2106
2165
  tip_policy=new_tip,
2107
2166
  last_tip_well=self._get_current_tip_source_well(),
2108
- tip_racks=self._tip_racks,
2167
+ tip_racks=tip_racks or self._tip_racks,
2109
2168
  nozzle_map=self._core.get_nozzle_map(),
2110
2169
  group_wells_for_multi_channel=group_wells,
2111
2170
  current_volume=self.current_volume,
@@ -2141,6 +2200,9 @@ class InstrumentContext(publisher.CommandPublisher):
2141
2200
  f" 'once', 'never' and 'always'."
2142
2201
  )
2143
2202
 
2203
+ for tip_rack in transfer_args.tip_racks:
2204
+ instrument.validate_tiprack(self.name, tip_rack, _log)
2205
+
2144
2206
  with publisher.publish_context(
2145
2207
  broker=self.broker,
2146
2208
  command=cmds.consolidate_with_liquid_class(
@@ -1,4 +1,5 @@
1
1
  """Movement command handling."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import logging
@@ -84,7 +85,7 @@ class MovementHandler:
84
85
  operation_volume: Optional[float] = None,
85
86
  ) -> Point:
86
87
  """Move to a specific well."""
87
- self._state_store.labware.raise_if_labware_inaccessible_by_pipette(
88
+ self._state_store.geometry.raise_if_labware_inaccessible_by_pipette(
88
89
  labware_id=labware_id
89
90
  )
90
91
 
@@ -56,3 +56,13 @@ def is_deck_slot(addressable_area_name: str) -> bool:
56
56
  def is_abs_reader(addressable_area_name: str) -> bool:
57
57
  """Check if an addressable area is an absorbance plate reader area."""
58
58
  return "absorbanceReaderV1" in addressable_area_name
59
+
60
+
61
+ def is_stacker_shuttle(addressable_area_name: str) -> bool:
62
+ """Check if an addressable area is a flex stacker shuttle area."""
63
+ return addressable_area_name in [
64
+ "flexStackerModuleV1A4",
65
+ "flexStackerModuleV1B4",
66
+ "flexStackerModuleV1C4",
67
+ "flexStackerModuleV1D4",
68
+ ]
@@ -1122,7 +1122,9 @@ class GeometryView:
1122
1122
  "providesAddressableAreas"
1123
1123
  ][
1124
1124
  deck_configuration_provider.get_cutout_id_by_deck_slot_name(
1125
- DeckSlotName.SLOT_C2
1125
+ DeckSlotName.SLOT_C2.to_equivalent_for_robot_type(
1126
+ self._config.robot_type
1127
+ )
1126
1128
  )
1127
1129
  ][
1128
1130
  0
@@ -2359,3 +2361,48 @@ class GeometryView:
2359
2361
  return pending_labware[labware_id]
2360
2362
  except KeyError as ke:
2361
2363
  raise lnle from ke
2364
+
2365
+ def raise_if_labware_inaccessible_by_pipette( # noqa: C901
2366
+ self, labware_id: str
2367
+ ) -> None:
2368
+ """Raise an error if the specified location cannot be reached via a pipette."""
2369
+ labware = self._labware.get(labware_id)
2370
+ labware_location = labware.location
2371
+ if isinstance(labware_location, OnLabwareLocation):
2372
+ return self.raise_if_labware_inaccessible_by_pipette(
2373
+ labware_location.labwareId
2374
+ )
2375
+ elif labware.lid_id is not None:
2376
+ raise errors.LocationNotAccessibleByPipetteError(
2377
+ f"Cannot move pipette to {labware.loadName} "
2378
+ "because labware is currently covered by a lid."
2379
+ )
2380
+ elif isinstance(labware_location, AddressableAreaLocation):
2381
+ if fixture_validation.is_staging_slot(labware_location.addressableAreaName):
2382
+ raise errors.LocationNotAccessibleByPipetteError(
2383
+ f"Cannot move pipette to {labware.loadName},"
2384
+ f" labware is on staging slot {labware_location.addressableAreaName}"
2385
+ )
2386
+ elif fixture_validation.is_stacker_shuttle(
2387
+ labware_location.addressableAreaName
2388
+ ):
2389
+ raise errors.LocationNotAccessibleByPipetteError(
2390
+ f"Cannot move pipette to {labware.loadName} because it is on a stacker shuttle"
2391
+ )
2392
+ elif (
2393
+ labware_location == OFF_DECK_LOCATION or labware_location == SYSTEM_LOCATION
2394
+ ):
2395
+ raise errors.LocationNotAccessibleByPipetteError(
2396
+ f"Cannot move pipette to {labware.loadName}, labware is off-deck."
2397
+ )
2398
+ elif isinstance(labware_location, ModuleLocation):
2399
+ module = self._modules.get(labware_location.moduleId)
2400
+ if ModuleModel.is_flex_stacker(module.model):
2401
+ raise errors.LocationNotAccessibleByPipetteError(
2402
+ f"Cannot move pipette to {labware.loadName}, labware is on a stacker shuttle"
2403
+ )
2404
+
2405
+ elif isinstance(labware_location, InStackerHopperLocation):
2406
+ raise errors.LocationNotAccessibleByPipetteError(
2407
+ f"Cannot move pipette to {labware.loadName}, labware is in a stacker hopper"
2408
+ )
@@ -58,7 +58,6 @@ from ..types import (
58
58
  LabwareMovementOffsetData,
59
59
  OnDeckLabwareLocation,
60
60
  OFF_DECK_LOCATION,
61
- SYSTEM_LOCATION,
62
61
  )
63
62
  from ..actions import (
64
63
  Action,
@@ -1036,32 +1035,6 @@ class LabwareView:
1036
1035
  """Check if labware is a lid."""
1037
1036
  return LabwareRole.lid in self.get_definition(labware_id).allowedRoles
1038
1037
 
1039
- def raise_if_labware_inaccessible_by_pipette(self, labware_id: str) -> None:
1040
- """Raise an error if the specified location cannot be reached via a pipette."""
1041
- labware = self.get(labware_id)
1042
- labware_location = labware.location
1043
- if isinstance(labware_location, OnLabwareLocation):
1044
- return self.raise_if_labware_inaccessible_by_pipette(
1045
- labware_location.labwareId
1046
- )
1047
- elif labware.lid_id is not None:
1048
- raise errors.LocationNotAccessibleByPipetteError(
1049
- f"Cannot move pipette to {labware.loadName} "
1050
- "because labware is currently covered by a lid."
1051
- )
1052
- elif isinstance(labware_location, AddressableAreaLocation):
1053
- if fixture_validation.is_staging_slot(labware_location.addressableAreaName):
1054
- raise errors.LocationNotAccessibleByPipetteError(
1055
- f"Cannot move pipette to {labware.loadName},"
1056
- f" labware is on staging slot {labware_location.addressableAreaName}"
1057
- )
1058
- elif (
1059
- labware_location == OFF_DECK_LOCATION or labware_location == SYSTEM_LOCATION
1060
- ):
1061
- raise errors.LocationNotAccessibleByPipetteError(
1062
- f"Cannot move pipette to {labware.loadName}, labware is off-deck."
1063
- )
1064
-
1065
1038
  def raise_if_labware_in_location(
1066
1039
  self,
1067
1040
  location: OnDeckLabwareLocation,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opentrons
3
- Version: 8.6.0a3
3
+ Version: 8.6.0a5
4
4
  Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
5
5
  Project-URL: opentrons.com, https://www.opentrons.com
6
6
  Project-URL: Source Code On Github, https://github.com/Opentrons/opentrons/tree/edge/api
@@ -24,7 +24,7 @@ Requires-Dist: click<9,>=8.0.0
24
24
  Requires-Dist: importlib-metadata>=1.0; python_version < '3.8'
25
25
  Requires-Dist: jsonschema<4.18.0,>=3.0.1
26
26
  Requires-Dist: numpy<2,>=1.20.0
27
- Requires-Dist: opentrons-shared-data==8.6.0a3
27
+ Requires-Dist: opentrons-shared-data==8.6.0a5
28
28
  Requires-Dist: packaging>=21.0
29
29
  Requires-Dist: pydantic-settings<3,>=2
30
30
  Requires-Dist: pydantic<3,>=2.0.0
@@ -32,6 +32,6 @@ Requires-Dist: pyserial>=3.5
32
32
  Requires-Dist: pyusb==1.2.1
33
33
  Requires-Dist: typing-extensions<5,>=4.0.0
34
34
  Provides-Extra: flex-hardware
35
- Requires-Dist: opentrons-hardware[flex]==8.6.0a3; extra == 'flex-hardware'
35
+ Requires-Dist: opentrons-hardware[flex]==8.6.0a5; extra == 'flex-hardware'
36
36
  Provides-Extra: ot2-hardware
37
- Requires-Dist: opentrons-hardware==8.6.0a3; extra == 'ot2-hardware'
37
+ Requires-Dist: opentrons-hardware==8.6.0a5; extra == 'ot2-hardware'
@@ -1,5 +1,5 @@
1
1
  opentrons/__init__.py,sha256=TQ_Ca_zzAM3iLzAysWKkFkQHG8-imihxDPQbLCYrf-E,4533
2
- opentrons/_version.py,sha256=xD_T0KGoygCgIVCg4T4APgS97uLDmrAh1HwkwJPEeNk,519
2
+ opentrons/_version.py,sha256=DwgORFlCYMpHF3Y70AkjCmEVbBrAoGaMakKYbXsdrE8,712
3
3
  opentrons/execute.py,sha256=Y88qICDiHWQjU0L4Ou7DI5OXXu7zZcdkUvNUYmZqIfc,29282
4
4
  opentrons/legacy_broker.py,sha256=XnuEBBlrHCThc31RFW2UR0tGqctqWZ-CZ9vSC4L9whU,1553
5
5
  opentrons/ordered_set.py,sha256=g-SB3qA14yxHu9zjGyc2wC7d2TUCBE6fKZlHAtbPzI8,4082
@@ -51,7 +51,7 @@ opentrons/drivers/absorbance_reader/hid_protocol.py,sha256=OM6Ogkl1Lw3d501rfHcRI
51
51
  opentrons/drivers/absorbance_reader/simulator.py,sha256=MiFQgnVNq2R35T3u59idcKlrj6SEFllt8tdupfH5jKQ,2419
52
52
  opentrons/drivers/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  opentrons/drivers/asyncio/communication/__init__.py,sha256=b-rd_I0ecbexGm6b9T91JLfFUrCyui9V1N1j-fzy0SQ,523
54
- opentrons/drivers/asyncio/communication/async_serial.py,sha256=oL92Uh3IGP3IEcP2814YaT4_uHbwjLPRVZtkoID0LmQ,5311
54
+ opentrons/drivers/asyncio/communication/async_serial.py,sha256=Tzv_uXvMYhJF2LXsJWDRA3hdg5_qCo3863zvn7Y66WY,5439
55
55
  opentrons/drivers/asyncio/communication/errors.py,sha256=-4pNGVKE83VUPNt1UTBLDzKtty3LxAhUNp-9yLENqyw,2678
56
56
  opentrons/drivers/asyncio/communication/serial_connection.py,sha256=cWcISWe0FfoZj63oYVQD-KkwBojzMTVrzJ64-Gd1u90,18876
57
57
  opentrons/drivers/flex_stacker/__init__.py,sha256=LiM0onRlgC-JfFBd0QseQU0-3WuuIxa7GNFj7Douiq8,351
@@ -217,7 +217,7 @@ opentrons/legacy_commands/robot_commands.py,sha256=c51gVAh-98PxhxmEL_3P80rejkaY5
217
217
  opentrons/legacy_commands/types.py,sha256=dtmHB2VOtsQHFzhdoZgjJZZc8pNGcCnleA5zsi5GTo0,28904
218
218
  opentrons/motion_planning/__init__.py,sha256=Gma3SLAvKbL7QuhVGtL9zFx5vlk_7YBF0TjYZQSiv9s,755
219
219
  opentrons/motion_planning/adjacent_slots_getters.py,sha256=z7HkfC8ymAdGHdFq-sC_1_cERX_v29b9x4HKtJ6gp9I,5390
220
- opentrons/motion_planning/deck_conflict.py,sha256=gJG0dCQOvdEP-rr9EbVSGJCQPDXgvd04Jn4crGEbYLo,12604
220
+ opentrons/motion_planning/deck_conflict.py,sha256=gIAQYJbSKEn5XM_AoBVCOdp-acQsc0nF5FHaeuF53vg,16757
221
221
  opentrons/motion_planning/errors.py,sha256=-TOop0-NWaWb6KNYRoYLpWMca_kwsxxXEf31WuDSGls,948
222
222
  opentrons/motion_planning/types.py,sha256=C4jXv5b02iBQmePMLrujgvHwqvEphBWtY18MPfKQpj4,1188
223
223
  opentrons/motion_planning/waypoints.py,sha256=45J1TBfvDKEczEog1-H16GoX3PhFBlYKyNzE2Sl10U8,8341
@@ -233,7 +233,7 @@ opentrons/protocol_api/config.py,sha256=r9lyvXjagTX_g3q5FGURPpcz2IA9sSF7Oa_1mKx-
233
233
  opentrons/protocol_api/create_protocol_context.py,sha256=wwsZje0L__oDnu1Yrihau320_f-ASloR9eL1QCtkOh8,7612
234
234
  opentrons/protocol_api/deck.py,sha256=94vFceg1SC1bAGd7TvC1ZpYwnJR-VlzurEZ6jkacYeg,8910
235
235
  opentrons/protocol_api/disposal_locations.py,sha256=NRiSGmDR0LnbyEkWSOM-o64uR2fUoB1NWJG7Y7SsJSs,7920
236
- opentrons/protocol_api/instrument_context.py,sha256=kPb8LPBh_GUENybQcSZNzPaY7SBSF501bD2mmFjaKKc,138275
236
+ opentrons/protocol_api/instrument_context.py,sha256=cgszbfSJbEr1p9QywHIoQjDBTdnakwH3R-iTDb4nuNI,141122
237
237
  opentrons/protocol_api/labware.py,sha256=ZP4QuGadoDp6xtyToupXVSJFnsx4NfWcskRQAH3iU4Y,61443
238
238
  opentrons/protocol_api/module_contexts.py,sha256=zhm2FfciAs2K73cVZ_8OwKcFKAU7VAiTOnCRnQaki_E,57712
239
239
  opentrons/protocol_api/module_validation_and_errors.py,sha256=ljst-M_KK78GnyG3pyZ_6yoYkMY3HORS1QyQyWrme-U,2250
@@ -252,7 +252,7 @@ opentrons/protocol_api/core/well.py,sha256=Lf89YYEyq-ahRSRIFJw42vxIP8Fw6kzIUh9K1
252
252
  opentrons/protocol_api/core/well_grid.py,sha256=BU28DKaBgEU_JdZ6pEzrwNxmuh6TkO4zlg7Pq1Rf5Xk,1516
253
253
  opentrons/protocol_api/core/engine/__init__.py,sha256=B_5T7zgkWDb1mXPg4NbT-wBkQaK-WVokMMnJRNu7xiM,582
254
254
  opentrons/protocol_api/core/engine/_default_labware_versions.py,sha256=hLKAp33c_eNLpvT5qUt8AOYy6PHhM_JnkdOXaMl80jA,7541
255
- opentrons/protocol_api/core/engine/deck_conflict.py,sha256=q3JViIAHDthIqq6ce7h2gxw3CHRfYsm5kkwzuXB-Gnc,12334
255
+ opentrons/protocol_api/core/engine/deck_conflict.py,sha256=j3AJ-sYr5e-06UgeGueUldx9B3fn7AhN738kJxZO_vw,14693
256
256
  opentrons/protocol_api/core/engine/exceptions.py,sha256=aZgNrmYEeuPZm21nX_KZYtvyjv5h_zPjxxgPkEV7_bw,725
257
257
  opentrons/protocol_api/core/engine/instrument.py,sha256=lDrCqOZm7ceu1E50EYiSh7qTDMci5cgw4bM-AvLyMjE,98890
258
258
  opentrons/protocol_api/core/engine/labware.py,sha256=-2oygShyRZo1-R0UOoBtXniFOXfc3cSdGM-qa_l9wh8,8610
@@ -427,7 +427,7 @@ opentrons/protocol_engine/execution/gantry_mover.py,sha256=LFTPmzuGRuP6IspgXxIEy
427
427
  opentrons/protocol_engine/execution/hardware_stopper.py,sha256=ZlhVYEdFfuKqp5slZBkustXcRPy5fJsw2rmfYzHuJkQ,6127
428
428
  opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py,sha256=BSFLzSSeELAYZCrCUfJZx5DdlrwU06Ur92TYd0T-hzM,9084
429
429
  opentrons/protocol_engine/execution/labware_movement.py,sha256=Bl-Nx3y5-zMlsxL3fcXV04OyiU1JFyqPJTS1Fir_XkA,12962
430
- opentrons/protocol_engine/execution/movement.py,sha256=jgpMoYLV3WhLQ7wNL09LDpua7pgWb5Njf-kg5RTAbyw,12747
430
+ opentrons/protocol_engine/execution/movement.py,sha256=ZU4K4OHnzZYCZbk3RwfSOC6C3T2jtBlvYMppJhJSF08,12749
431
431
  opentrons/protocol_engine/execution/pipetting.py,sha256=cnJYbLiJ2QD1xziD8dkRm0mZG3xOk00klW8Ff8rgSG4,22199
432
432
  opentrons/protocol_engine/execution/queue_worker.py,sha256=LM753TrQzJoKUSIrtcaHDOWLe58zcpx-fUOLVpyDlHM,3302
433
433
  opentrons/protocol_engine/execution/rail_lights.py,sha256=eiJT6oI_kFk7rFuFkZzISZiLNnpf7Kkh86Kyk9wQ_Jo,590
@@ -442,7 +442,7 @@ opentrons/protocol_engine/resources/__init__.py,sha256=yvGFYpmLoxHYQff_IwiaEH9vi
442
442
  opentrons/protocol_engine/resources/deck_configuration_provider.py,sha256=K3_FKHNpeM1_kTjHGBbrMPaCZsbEEOUaY8licOd6Xh8,9411
443
443
  opentrons/protocol_engine/resources/deck_data_provider.py,sha256=63c-Hmwy5IbVSoAL3hYoZxizxwzCqbB2KgJptpLX3Bc,3001
444
444
  opentrons/protocol_engine/resources/file_provider.py,sha256=6btMCDN7NsyFlV7Icy5vDO7xsgbmtkeAM_KCuQ-GvRo,5903
445
- opentrons/protocol_engine/resources/fixture_validation.py,sha256=WBGWFTmBwLPjOBFeqJYxnaSRHvo4pwxvdhT4XUW_FMY,1857
445
+ opentrons/protocol_engine/resources/fixture_validation.py,sha256=WyGjMjc-DGiNTojesXdwFfCyF3KYl7R2jmEk7teLyxQ,2166
446
446
  opentrons/protocol_engine/resources/labware_data_provider.py,sha256=i0otj_dACWHK23mBGjXGwTJtE4sooov2_YQOMIulzJo,3836
447
447
  opentrons/protocol_engine/resources/labware_validation.py,sha256=6UkWktVvGNpOrRov4vEZ2A8qbjJMuKlisSQvr4Z749A,2747
448
448
  opentrons/protocol_engine/resources/model_utils.py,sha256=C3OHUi-OtuFUm3dS5rApSU3EJ0clnaCZEyBku5sTjzA,941
@@ -461,9 +461,9 @@ opentrons/protocol_engine/state/commands.py,sha256=y5WE2pKmnMalgHFHEiBnBurO2TZ9w
461
461
  opentrons/protocol_engine/state/config.py,sha256=7jSGxC6Vqj1eA8fqZ2I3zjlxVXg8pxvcBYMztRIx9Mg,1515
462
462
  opentrons/protocol_engine/state/files.py,sha256=w8xxxg8HY0RqKKEGSfHWfrjV54Gb02O3dwtisJ-9j8E,1753
463
463
  opentrons/protocol_engine/state/fluid_stack.py,sha256=uwkf0qYk1UX5iU52xmk-e3yLPK8OG-TtMCcBqrkVFpM,5932
464
- opentrons/protocol_engine/state/geometry.py,sha256=WmS1hg7F4Ng3vn7HQS91DIWPvuT3nKgBE69E-0IGha4,101642
464
+ opentrons/protocol_engine/state/geometry.py,sha256=o8tefXS_Jekdt82dW4HHVJSnsxCsTFJuG6ogtri2wTY,104065
465
465
  opentrons/protocol_engine/state/inner_well_math_utils.py,sha256=UhemsPpcuKwVc-iGXI2-v--miOGNunAnAVznJTVADlQ,20598
466
- opentrons/protocol_engine/state/labware.py,sha256=CRY84JCj9Y31aA-QWgqboql_PRD7QFZh1ja6Iboo0Wg,61308
466
+ opentrons/protocol_engine/state/labware.py,sha256=bmNOa6vDFXa-Iow4x_1zNa3tIcNkUvu1OCg3ED2E4i4,59936
467
467
  opentrons/protocol_engine/state/liquid_classes.py,sha256=u_z75UYdiFAKG0yB3mr1il4T3qaS0Sotq8sL7KLODP8,2990
468
468
  opentrons/protocol_engine/state/liquids.py,sha256=NoesktcQdJUjIVmet1uqqJPf-rzbo4SGemXwQC295W0,2338
469
469
  opentrons/protocol_engine/state/modules.py,sha256=2P_3Ks_9hWvtVgttnUqS5wcmWLmMzn2Tjp8CosnFXYM,60737
@@ -594,8 +594,8 @@ opentrons/util/linal.py,sha256=IlKAP9HkNBBgULeSf4YVwSKHdx9jnCjSr7nvDvlRALg,5753
594
594
  opentrons/util/logging_config.py,sha256=7et4YYuQdWdq_e50U-8vFS_QyNBRgdnqPGAQJm8qrIo,9954
595
595
  opentrons/util/logging_queue_handler.py,sha256=ZsSJwy-oV8DXwpYiZisQ1PbYwmK2cOslD46AcyJ1E4I,2484
596
596
  opentrons/util/performance_helpers.py,sha256=ew7H8XD20iS6-2TJAzbQeyzStZkkE6PzHt_Adx3wbZQ,5172
597
- opentrons-8.6.0a3.dist-info/METADATA,sha256=2OzHrnc75fEw1gcKzSg87PJzcTgAY75e8BhN5PijOwI,1607
598
- opentrons-8.6.0a3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
599
- opentrons-8.6.0a3.dist-info/entry_points.txt,sha256=fTa6eGCYkvOtv0ov-KVE8LLGetgb35LQLF9x85OWPVw,106
600
- opentrons-8.6.0a3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
601
- opentrons-8.6.0a3.dist-info/RECORD,,
597
+ opentrons-8.6.0a5.dist-info/METADATA,sha256=2vlfyq7YCEWGMNFuGW00_MMEhCX9KPsoQ0CdJub1poM,1607
598
+ opentrons-8.6.0a5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
599
+ opentrons-8.6.0a5.dist-info/entry_points.txt,sha256=fTa6eGCYkvOtv0ov-KVE8LLGetgb35LQLF9x85OWPVw,106
600
+ opentrons-8.6.0a5.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
601
+ opentrons-8.6.0a5.dist-info/RECORD,,