opentrons 8.4.0a5__py2.py3-none-any.whl → 8.4.0a7__py2.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.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

@@ -3,7 +3,7 @@ from typing import Optional, Union
3
3
 
4
4
  from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
5
5
 
6
- from opentrons.types import Point
6
+ from opentrons.types import Point, Mount, MountType
7
7
 
8
8
  from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset
9
9
  from opentrons.protocol_engine import commands as cmd
@@ -13,6 +13,7 @@ from opentrons.protocol_engine.types.liquid_level_detection import (
13
13
  SimulatedProbeResult,
14
14
  LiquidTrackingType,
15
15
  )
16
+ from opentrons.protocol_engine.errors import PipetteNotAttachedError
16
17
 
17
18
  from . import point_calculations
18
19
  from . import stringify
@@ -181,15 +182,25 @@ class WellCore(AbstractWellCore):
181
182
 
182
183
  def estimate_liquid_height_after_pipetting(
183
184
  self,
185
+ mount: Mount | str,
184
186
  operation_volume: float,
185
187
  ) -> LiquidTrackingType:
186
188
  """Return an estimate of liquid height after pipetting without raising an error."""
187
189
  labware_id = self.labware_id
188
190
  well_name = self._name
191
+ if isinstance(mount, Mount):
192
+ mount_type = MountType.from_hw_mount(mount)
193
+ else:
194
+ mount_type = MountType(mount)
195
+ pipette_from_mount = self._engine_client.state.pipettes.get_by_mount(mount_type)
196
+ if pipette_from_mount is None:
197
+ raise PipetteNotAttachedError(f"No pipette present on mount {mount}")
198
+ pipette_id = pipette_from_mount.id
189
199
  starting_liquid_height = self.current_liquid_height()
190
200
  projected_final_height = self._engine_client.state.geometry.get_well_height_after_liquid_handling_no_error(
191
201
  labware_id=labware_id,
192
202
  well_name=well_name,
203
+ pipette_id=pipette_id,
193
204
  initial_height=starting_liquid_height,
194
205
  volume=operation_volume,
195
206
  )
@@ -5,7 +5,7 @@ from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
5
5
 
6
6
  from opentrons.protocols.api_support.util import APIVersionError
7
7
 
8
- from opentrons.types import Point
8
+ from opentrons.types import Point, Mount
9
9
 
10
10
  from opentrons.protocol_engine.types.liquid_level_detection import (
11
11
  SimulatedProbeResult,
@@ -129,6 +129,7 @@ class LegacyWellCore(AbstractWellCore):
129
129
 
130
130
  def estimate_liquid_height_after_pipetting(
131
131
  self,
132
+ mount: Mount | str,
132
133
  operation_volume: float,
133
134
  ) -> LiquidTrackingType:
134
135
  """Estimate what the liquid height will be after pipetting, without raising an error."""
@@ -3,7 +3,7 @@
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import TypeVar, Optional, Union
5
5
 
6
- from opentrons.types import Point
6
+ from opentrons.types import Point, Mount
7
7
  from opentrons.protocol_engine.types import LiquidTrackingType
8
8
 
9
9
  from .._liquid import Liquid
@@ -91,6 +91,7 @@ class AbstractWellCore(ABC):
91
91
  @abstractmethod
92
92
  def estimate_liquid_height_after_pipetting(
93
93
  self,
94
+ mount: Mount | str,
94
95
  operation_volume: float,
95
96
  ) -> LiquidTrackingType:
96
97
  """Estimate what the liquid height will be after pipetting, without raising an error."""
@@ -1528,6 +1528,9 @@ class InstrumentContext(publisher.CommandPublisher):
1528
1528
  ) -> InstrumentContext:
1529
1529
  """Move a particular type of liquid from one well or group of wells to another.
1530
1530
 
1531
+ ..
1532
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1533
+
1531
1534
  :param liquid_class: The type of liquid to move. You must specify the liquid class,
1532
1535
  even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
1533
1536
  source contains.
@@ -1553,7 +1556,7 @@ class InstrumentContext(publisher.CommandPublisher):
1553
1556
  :param return_tip: Whether to drop used tips in their original locations
1554
1557
  in the tip rack, instead of the trash.
1555
1558
 
1556
- :meta private:
1559
+ :meta private:
1557
1560
  """
1558
1561
  if volume == 0.0:
1559
1562
  _log.info(
@@ -1635,6 +1638,9 @@ class InstrumentContext(publisher.CommandPublisher):
1635
1638
  """
1636
1639
  Distribute a particular type of liquid from one well to a group of wells.
1637
1640
 
1641
+ ..
1642
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1643
+
1638
1644
  :param liquid_class: The type of liquid to move. You must specify the liquid class,
1639
1645
  even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
1640
1646
  source contains.
@@ -1657,7 +1663,7 @@ class InstrumentContext(publisher.CommandPublisher):
1657
1663
  :param return_tip: Whether to drop used tips in their original locations
1658
1664
  in the tip rack, instead of the trash.
1659
1665
 
1660
- :meta private:
1666
+ :meta private:
1661
1667
  """
1662
1668
  if volume == 0.0:
1663
1669
  _log.info(
@@ -1748,6 +1754,9 @@ class InstrumentContext(publisher.CommandPublisher):
1748
1754
  """
1749
1755
  Consolidate a particular type of liquid from a group of wells to one well.
1750
1756
 
1757
+ ..
1758
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1759
+
1751
1760
  :param liquid_class: The type of liquid to move. You must specify the liquid class,
1752
1761
  even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
1753
1762
  source contains.
@@ -1770,7 +1779,7 @@ class InstrumentContext(publisher.CommandPublisher):
1770
1779
  :param return_tip: Whether to drop used tips in their original locations
1771
1780
  in the tip rack, instead of the trash.
1772
1781
 
1773
- :meta private:
1782
+ :meta private:
1774
1783
  """
1775
1784
  if volume == 0.0:
1776
1785
  _log.info(
@@ -1944,19 +1953,19 @@ class InstrumentContext(publisher.CommandPublisher):
1944
1953
 
1945
1954
  return self
1946
1955
 
1947
- @requires_version(2, 22)
1956
+ @requires_version(2, 23)
1948
1957
  def resin_tip_seal(
1949
1958
  self,
1950
1959
  location: Union[labware.Well, labware.Labware],
1951
1960
  ) -> InstrumentContext:
1952
1961
  """Seal resin tips onto the pipette.
1953
1962
 
1954
- The location provided should contain resin tips. Sealing the
1955
- tip will perform a `pick up` action but there will be no tip tracking
1956
- associated with the pipette.
1963
+ The location provided should contain resin tips. The pipette will attach itself
1964
+ to the resin tips but does not check any tip presence sensors. Before the pipette
1965
+ seals to the tips, the plunger will rise to the top of its working range so that
1966
+ it can perform a :py:func:`resin_tip_dispense` immediately.
1957
1967
 
1958
1968
  :param location: A location containing resin tips, must be a Labware or a Well.
1959
-
1960
1969
  :type location: :py:class:`~.types.Location`
1961
1970
  """
1962
1971
  if isinstance(location, labware.Labware):
@@ -1976,7 +1985,7 @@ class InstrumentContext(publisher.CommandPublisher):
1976
1985
  )
1977
1986
  return self
1978
1987
 
1979
- @requires_version(2, 22)
1988
+ @requires_version(2, 23)
1980
1989
  def resin_tip_unseal(
1981
1990
  self,
1982
1991
  location: Union[labware.Well, labware.Labware],
@@ -2014,31 +2023,62 @@ class InstrumentContext(publisher.CommandPublisher):
2014
2023
 
2015
2024
  return self
2016
2025
 
2017
- @requires_version(2, 22)
2026
+ @requires_version(2, 23)
2018
2027
  def resin_tip_dispense(
2019
2028
  self,
2020
2029
  location: types.Location,
2021
2030
  volume: Optional[float] = None,
2022
2031
  rate: Optional[float] = None,
2023
2032
  ) -> InstrumentContext:
2024
- """Dispense a volume from resin tips into a labware.
2033
+ """Push liquid out of resin tips that are currently sealed to a pipette.
2034
+
2035
+ The volume and rate parameters for this function control the motion of the plunger
2036
+ to create a desired pressure profile inside the pipette chamber. Unlike a regular
2037
+ dispense action, the volume and rate do not correspond to liquid volume or flow rate
2038
+ dispensed from the resin tips. Select your values for volume and flow rate based on
2039
+ experimentation with the resin tips to create a pressure profile.
2040
+
2041
+ The common way to use this function is as follows:
2042
+
2043
+ #. Seal resin tips to the pipette using :py:meth:`InstrumentContext.resin_tip_seal`.
2025
2044
 
2026
- The location provided should contain resin tips labware as well as a
2027
- receptical for dispensed liquid. Dispensing from tip will perform a
2028
- `dispense` action of the specified volume at a desired flow rate.
2045
+ #. Use :py:meth:`InstrumentContext.resin_tip_dispense` to displace an experimentally
2046
+ derived volume at an experimentally derived rate to create an experimentally derived
2047
+ target pressure inside the pipette.
2029
2048
 
2030
- :param location: A location containing resin tips.
2049
+ #. Use :py:meth:`ProtocolContext.delay` to wait an experimentally derived amount of
2050
+ time for the pressure inside the pipette to push liquid into and through the resin tip
2051
+ and out the other side.
2052
+
2053
+ #. As liquid passes through the resin tip, the pressure inside the pipette will
2054
+ fall. If not all liquid has been dispensed from the resin tip, repeat steps 2
2055
+ and 3.
2056
+
2057
+ #. Unseal resin tips from the pipette using :py:meth:`InstrumentContext.resin_tip_unseal`.
2058
+
2059
+ Flex pipette pressure sensors will raise an overpressure when a differential pressure
2060
+ inside the pipette chamber above sensor limits is detected. You may need to disable the
2061
+ pressure sensor to create the required pressure profile.
2062
+
2063
+ .. warning::
2064
+ Building excessive pressure inside the pipette chamber (significantly above the sensor
2065
+ limit) with the pressure sensors disabled can damage the pipette.
2066
+
2067
+
2068
+ :param location: Tells the robot where to dispense.
2031
2069
  :type location: :py:class:`~.types.Location`
2032
2070
 
2033
- :param volume: Will default to maximum, recommended to use the default.
2034
- The volume, in µL, that the pipette will prepare to handle.
2071
+ :param volume: The volume that the plunger should displace, in µL. Does not directly relate
2072
+ to the volume of liquid that will be dispensed.
2035
2073
  :type volume: float
2036
2074
 
2037
- :param rate: Will default to 10.0, recommended to use the default. How quickly
2038
- a pipette dispenses liquid. The speed in µL/s is calculated as
2039
- ``rate`` multiplied by :py:attr:`flow_rate.dispense<flow_rate>`.
2040
- :type rate: float
2075
+ :param rate: How quickly the plunger moves to displace the commanded volume. The plunger speed
2076
+ in µL/s is calculated as ``rate`` multiplied by
2077
+ :py:attr:`flow_rate.dispense<flow_rate>`. This rate does not directly relate to
2078
+ the flow rate of liquid out of the resin tip.
2041
2079
 
2080
+ The default value of ``10.0`` is recommended.
2081
+ :type rate: float
2042
2082
  """
2043
2083
  well: Optional[labware.Well] = None
2044
2084
  last_location = self._get_last_location_by_api_version()
@@ -2654,11 +2694,12 @@ class InstrumentContext(publisher.CommandPublisher):
2654
2694
  def measure_liquid_height(self, well: labware.Well) -> LiquidTrackingType:
2655
2695
  """Check the height of the liquid within a well.
2656
2696
 
2657
- :returns: The height, in mm, of the liquid from the deck.
2697
+ :returns: The height, in mm, of the liquid from the bottom of the well.
2658
2698
  """
2659
2699
  self._raise_if_pressure_not_supported_by_pipette()
2660
2700
  loc = well.top()
2661
- return self._core.liquid_probe_without_recovery(well._core, loc)
2701
+ self._core.liquid_probe_with_recovery(well._core, loc)
2702
+ return well.current_liquid_height()
2662
2703
 
2663
2704
  def _raise_if_configuration_not_supported_by_pipette(
2664
2705
  self, style: NozzleLayout
@@ -39,6 +39,7 @@ from opentrons.types import (
39
39
  Point,
40
40
  NozzleMapInterface,
41
41
  MeniscusTrackingTarget,
42
+ Mount,
42
43
  )
43
44
  from opentrons.protocols.api_support.types import APIVersion
44
45
  from opentrons.protocols.api_support.util import (
@@ -348,6 +349,7 @@ class Well:
348
349
  @requires_version(2, 21)
349
350
  def estimate_liquid_height_after_pipetting(
350
351
  self,
352
+ mount: Mount | str,
351
353
  operation_volume: float,
352
354
  ) -> LiquidTrackingType:
353
355
  """Check the height of the liquid within a well.
@@ -360,7 +362,7 @@ class Well:
360
362
  """
361
363
 
362
364
  projected_final_height = self._core.estimate_liquid_height_after_pipetting(
363
- operation_volume=operation_volume,
365
+ operation_volume=operation_volume, mount=mount
364
366
  )
365
367
  return projected_final_height
366
368
 
@@ -1363,6 +1363,8 @@ class ProtocolContext(CommandPublisher):
1363
1363
  ) -> LiquidClass:
1364
1364
  """
1365
1365
  Define a liquid class for use in the protocol.
1366
+ ..
1367
+ This is intended for Opentrons internal use only and is not a guaranteed API.
1366
1368
 
1367
1369
  :meta private:
1368
1370
  """
@@ -199,6 +199,7 @@ class HardwarePipettingHandler(PipettingHandler):
199
199
  labware_id=labware_id,
200
200
  well_name=well_name,
201
201
  operation_volume=volume * -1,
202
+ pipette_id=pipette_id,
202
203
  )
203
204
  if isinstance(aspirate_z_distance, SimulatedProbeResult):
204
205
  raise InvalidLiquidHeightFound(
@@ -234,6 +235,7 @@ class HardwarePipettingHandler(PipettingHandler):
234
235
  labware_id=labware_id,
235
236
  well_name=well_name,
236
237
  operation_volume=volume,
238
+ pipette_id=pipette_id,
237
239
  )
238
240
  if isinstance(dispense_z_distance, SimulatedProbeResult):
239
241
  raise InvalidLiquidHeightFound(
@@ -1,6 +1,6 @@
1
1
  """Tip pickup and drop procedures."""
2
2
 
3
- from typing import Optional, Dict
3
+ from typing import Optional, Dict, Tuple
4
4
  from typing_extensions import Protocol as TypingProtocol
5
5
 
6
6
  from opentrons.hardware_control import HardwareControlAPI
@@ -201,6 +201,18 @@ async def _available_for_nozzle_layout( # noqa: C901
201
201
  }
202
202
 
203
203
 
204
+ def tip_on_left_side_96(back_left_nozzle: str) -> bool:
205
+ """Return if there is a tip on the left edge of the 96 channel."""
206
+ left_most_column = int(back_left_nozzle[1:])
207
+ return left_most_column == 1
208
+
209
+
210
+ def tip_on_right_side_96(front_right_nozzle: str) -> bool:
211
+ """Return if there is a tip on the left edge of the 96 channel."""
212
+ right_most_column = int(front_right_nozzle[1:])
213
+ return right_most_column == 12
214
+
215
+
204
216
  class HardwareTipHandler(TipHandler):
205
217
  """Pick up and drop tips, using the Hardware API."""
206
218
 
@@ -237,6 +249,50 @@ class HardwareTipHandler(TipHandler):
237
249
  channels, style, primary_nozzle, front_right_nozzle, back_left_nozzle
238
250
  )
239
251
 
252
+ def get_tip_presence_config(
253
+ self, pipette_id: str
254
+ ) -> Tuple[bool, Optional[InstrumentProbeType]]:
255
+ """Return the supported settings for tip presence on a given pipette depending on it's current nozzle map."""
256
+ follow_singular_sensor = None
257
+
258
+ unsupported_layout_types_96 = [NozzleConfigurationType.SINGLE]
259
+ # NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor
260
+ supported_partial_nozzle_minimum = 4
261
+
262
+ nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration(
263
+ pipette_id=pipette_id
264
+ )
265
+
266
+ match self._state_view.pipettes.get_channels(pipette_id):
267
+ case 1:
268
+ tip_presence_supported = True
269
+ case 8:
270
+ tip_presence_supported = (
271
+ nozzle_configuration.tip_count >= supported_partial_nozzle_minimum
272
+ )
273
+ case 96:
274
+ tip_presence_supported = (
275
+ nozzle_configuration.configuration
276
+ not in unsupported_layout_types_96
277
+ and nozzle_configuration.tip_count
278
+ >= supported_partial_nozzle_minimum
279
+ )
280
+ if (
281
+ nozzle_configuration.configuration != NozzleConfigurationType.FULL
282
+ and tip_presence_supported
283
+ ):
284
+ use_left = tip_on_left_side_96(nozzle_configuration.back_left)
285
+ use_right = tip_on_right_side_96(nozzle_configuration.front_right)
286
+ if not (use_left and use_right):
287
+ if use_left:
288
+ follow_singular_sensor = InstrumentProbeType.PRIMARY
289
+ else:
290
+ follow_singular_sensor = InstrumentProbeType.SECONDARY
291
+ case _:
292
+ raise ValueError("Unknown pipette type.")
293
+
294
+ return (tip_presence_supported, follow_singular_sensor)
295
+
240
296
  async def pick_up_tip(
241
297
  self,
242
298
  pipette_id: str,
@@ -266,9 +322,18 @@ class HardwareTipHandler(TipHandler):
266
322
  await self._hardware_api.tip_pickup_moves(
267
323
  mount=hw_mount, presses=None, increment=None
268
324
  )
269
- if do_not_ignore_tip_presence:
325
+
326
+ tip_presence_supported, follow_singular_sensor = self.get_tip_presence_config(
327
+ pipette_id
328
+ )
329
+
330
+ if do_not_ignore_tip_presence and tip_presence_supported:
270
331
  try:
271
- await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
332
+ await self.verify_tip_presence(
333
+ pipette_id,
334
+ TipPresenceStatus.PRESENT,
335
+ follow_singular_sensor=follow_singular_sensor,
336
+ )
272
337
  except TipNotAttachedError as e:
273
338
  raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
274
339
 
@@ -350,30 +415,6 @@ class HardwareTipHandler(TipHandler):
350
415
  follow_singular_sensor: Optional[InstrumentProbeType] = None,
351
416
  ) -> None:
352
417
  """See documentation on abstract base class."""
353
- nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration(
354
- pipette_id=pipette_id
355
- )
356
-
357
- # Configuration metrics by which tip presence checking is ignored
358
- unsupported_pipette_types = [8, 96]
359
- unsupported_layout_types = [
360
- NozzleConfigurationType.SINGLE,
361
- NozzleConfigurationType.COLUMN,
362
- ]
363
- # NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor
364
- supported_partial_nozzle_minimum = 4
365
-
366
- if (
367
- nozzle_configuration is not None
368
- and self._state_view.pipettes.get_channels(pipette_id)
369
- in unsupported_pipette_types
370
- and nozzle_configuration.configuration in unsupported_layout_types
371
- and len(nozzle_configuration.map_store) < supported_partial_nozzle_minimum
372
- ):
373
- # Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette.
374
- # This is due in part to a press distance tolerance which creates a risk case for false positives. In the case of single tip, the mechanical tolerance
375
- # for presses with 100% success is below the minimum average achieved press distance for a given multi channel pipette in that configuration.
376
- return
377
418
  try:
378
419
  ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api)
379
420
  hw_mount = self._get_hw_mount(pipette_id)
@@ -371,7 +371,7 @@ def find_volume_at_well_height(
371
371
  max_height = volumetric_capacity[-1][0]
372
372
  if target_height < 0 or target_height > max_height:
373
373
  raise InvalidLiquidHeightFound(
374
- "Invalid target height {target_height} mm; max well height is {max_height} mm."
374
+ f"Invalid target height {target_height} mm; max well height is {max_height} mm."
375
375
  )
376
376
  # volumes in volumetric_capacity are relative to each frustum,
377
377
  # so we have to find the volume of all the full sections enclosed
@@ -7,6 +7,7 @@ from numpy.typing import NDArray
7
7
  from typing import Optional, List, Tuple, Union, cast, TypeVar, Dict, Set
8
8
  from dataclasses import dataclass
9
9
  from functools import cached_property
10
+ from math import isclose
10
11
 
11
12
  from opentrons.types import (
12
13
  Point,
@@ -487,7 +488,7 @@ class GeometryView:
487
488
  raise OperationLocationNotInWellError(
488
489
  f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location that could be below the bottom of the well"
489
490
  )
490
- elif z_offset < 0:
491
+ elif z_offset < 0 and not isclose(z_offset, 0, abs_tol=0.0000001):
491
492
  if isinstance(well_location, LiquidHandlingWellLocation):
492
493
  raise OperationLocationNotInWellError(
493
494
  f"Specifying {well_location.origin} with an offset of {well_location.offset} and a volume offset of {well_location.volumeOffset} results in an operation location below the bottom of the well"
@@ -519,6 +520,7 @@ class GeometryView:
519
520
  well_location=well_location,
520
521
  well_depth=well_depth,
521
522
  operation_volume=operation_volume,
523
+ pipette_id=pipette_id,
522
524
  )
523
525
  if not isinstance(offset_adjustment, SimulatedProbeResult):
524
526
  offset = offset.model_copy(update={"z": offset.z + offset_adjustment})
@@ -1843,6 +1845,7 @@ class GeometryView:
1843
1845
  self,
1844
1846
  labware_id: str,
1845
1847
  well_name: str,
1848
+ pipette_id: str,
1846
1849
  operation_volume: float,
1847
1850
  ) -> float:
1848
1851
  """Get the change in height from a liquid handling operation."""
@@ -1852,6 +1855,7 @@ class GeometryView:
1852
1855
  final_height = self.get_well_height_after_liquid_handling(
1853
1856
  labware_id=labware_id,
1854
1857
  well_name=well_name,
1858
+ pipette_id=pipette_id,
1855
1859
  initial_height=initial_handling_height,
1856
1860
  volume=operation_volume,
1857
1861
  )
@@ -1872,6 +1876,7 @@ class GeometryView:
1872
1876
  well_name: str,
1873
1877
  well_location: WellLocationType,
1874
1878
  well_depth: float,
1879
+ pipette_id: Optional[str] = None,
1875
1880
  operation_volume: Optional[float] = None,
1876
1881
  ) -> LiquidTrackingType:
1877
1882
  """Return a z-axis distance that accounts for well handling height and operation volume.
@@ -1905,9 +1910,14 @@ class GeometryView:
1905
1910
  volume = well_location.volumeOffset
1906
1911
 
1907
1912
  if volume:
1913
+ if pipette_id is None:
1914
+ raise ValueError(
1915
+ "cannot get liquid handling offset without pipette id."
1916
+ )
1908
1917
  liquid_height_after = self.get_well_height_after_liquid_handling(
1909
1918
  labware_id=labware_id,
1910
1919
  well_name=well_name,
1920
+ pipette_id=pipette_id,
1911
1921
  initial_height=initial_handling_height,
1912
1922
  volume=volume,
1913
1923
  )
@@ -2029,6 +2039,7 @@ class GeometryView:
2029
2039
  self,
2030
2040
  labware_id: str,
2031
2041
  well_name: str,
2042
+ pipette_id: str,
2032
2043
  initial_height: LiquidTrackingType,
2033
2044
  volume: float,
2034
2045
  ) -> LiquidTrackingType:
@@ -2043,7 +2054,14 @@ class GeometryView:
2043
2054
  initial_volume = find_volume_at_well_height(
2044
2055
  target_height=initial_height, well_geometry=well_geometry
2045
2056
  )
2046
- final_volume = initial_volume + volume
2057
+ final_volume = initial_volume + (
2058
+ volume
2059
+ * self.get_nozzles_per_well(
2060
+ labware_id=labware_id,
2061
+ target_well_name=well_name,
2062
+ pipette_id=pipette_id,
2063
+ )
2064
+ )
2047
2065
  return find_height_at_well_volume(
2048
2066
  target_volume=final_volume, well_geometry=well_geometry
2049
2067
  )
@@ -2057,6 +2075,7 @@ class GeometryView:
2057
2075
  self,
2058
2076
  labware_id: str,
2059
2077
  well_name: str,
2078
+ pipette_id: str,
2060
2079
  initial_height: LiquidTrackingType,
2061
2080
  volume: float,
2062
2081
  ) -> LiquidTrackingType:
@@ -2072,7 +2091,14 @@ class GeometryView:
2072
2091
  initial_volume = find_volume_at_well_height(
2073
2092
  target_height=initial_height, well_geometry=well_geometry
2074
2093
  )
2075
- final_volume = initial_volume + volume
2094
+ final_volume = initial_volume + (
2095
+ volume
2096
+ * self.get_nozzles_per_well(
2097
+ labware_id=labware_id,
2098
+ target_well_name=well_name,
2099
+ pipette_id=pipette_id,
2100
+ )
2101
+ )
2076
2102
  well_volume = find_height_at_well_volume(
2077
2103
  target_volume=final_volume,
2078
2104
  well_geometry=well_geometry,
@@ -135,6 +135,7 @@ from .liquid_level_detection import (
135
135
  WellInfoSummary,
136
136
  WellLiquidInfo,
137
137
  LiquidTrackingType,
138
+ SimulatedProbeResult,
138
139
  )
139
140
  from .liquid_handling import FlowRates
140
141
  from .labware_movement import LabwareMovementStrategy, LabwareMovementOffsetData
@@ -280,6 +281,7 @@ __all__ = [
280
281
  "WellInfoSummary",
281
282
  "WellLiquidInfo",
282
283
  "LiquidTrackingType",
284
+ "SimulatedProbeResult",
283
285
  # Liquid handling
284
286
  "FlowRates",
285
287
  # Labware movement
@@ -1,9 +1,10 @@
1
1
  """Protocol Engine types to do with liquid level detection."""
2
+
2
3
  from __future__ import annotations
3
4
  from dataclasses import dataclass
4
5
  from datetime import datetime
5
- from typing import Optional, List
6
- from pydantic import BaseModel, model_serializer, field_validator
6
+ from typing import Optional, List, Any
7
+ from pydantic import BaseModel, model_serializer, model_validator
7
8
 
8
9
 
9
10
  class SimulatedProbeResult(BaseModel):
@@ -17,6 +18,14 @@ class SimulatedProbeResult(BaseModel):
17
18
  """Serialize instances of this class as a string."""
18
19
  return "SimulatedProbeResult"
19
20
 
21
+ @model_validator(mode="before")
22
+ @classmethod
23
+ def validate_model(cls, data: object) -> Any:
24
+ """Handle deserializing from a simulated probe result."""
25
+ if isinstance(data, str) and data == "SimulatedProbeResult":
26
+ return {}
27
+ return data
28
+
20
29
  def __add__(
21
30
  self, other: float | SimulatedProbeResult
22
31
  ) -> float | SimulatedProbeResult:
@@ -75,7 +84,9 @@ class SimulatedProbeResult(BaseModel):
75
84
  self.operations_after_probe.append(volume)
76
85
 
77
86
 
78
- LiquidTrackingType = SimulatedProbeResult | float
87
+ # Work around https://github.com/pydantic/pydantic/issues/6830 - do not change the order of
88
+ # this union
89
+ LiquidTrackingType = float | SimulatedProbeResult
79
90
 
80
91
 
81
92
  class LoadedVolumeInfo(BaseModel):
@@ -104,23 +115,6 @@ class ProbedVolumeInfo(BaseModel):
104
115
  class WellInfoSummary(BaseModel):
105
116
  """Payload for a well's liquid info in StateSummary."""
106
117
 
107
- # TODO(cm): 3/21/25: refactor SimulatedLiquidProbe in a way that
108
- # doesn't require models like this one that are just using it to
109
- # need a custom validator
110
- @field_validator("probed_height", "probed_volume", mode="before")
111
- @classmethod
112
- def validate_simulated_probe_result(
113
- cls, input_val: object
114
- ) -> LiquidTrackingType | None:
115
- """Return the appropriate input to WellInfoSummary from json data."""
116
- if input_val is None:
117
- return None
118
- if isinstance(input_val, LiquidTrackingType):
119
- return input_val
120
- if isinstance(input_val, str) and input_val == "SimulatedProbeResult":
121
- return SimulatedProbeResult()
122
- raise ValueError(f"Invalid input value {input_val} to WellInfoSummary")
123
-
124
118
  labware_id: str
125
119
  well_name: str
126
120
  loaded_volume: Optional[float] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opentrons
3
- Version: 8.4.0a5
3
+ Version: 8.4.0a7
4
4
  Summary: The Opentrons API is a simple framework designed to make writing automated biology lab protocols easy.
5
5
  Author: Opentrons
6
6
  Author-email: engineering@opentrons.com
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Topic :: Scientific/Engineering
22
22
  Requires-Python: >=3.10
23
23
  License-File: ../LICENSE
24
- Requires-Dist: opentrons-shared-data (==8.4.0a5)
24
+ Requires-Dist: opentrons-shared-data (==8.4.0a7)
25
25
  Requires-Dist: aionotify (==0.3.1)
26
26
  Requires-Dist: anyio (<4.0.0,>=3.6.1)
27
27
  Requires-Dist: jsonschema (<4.18.0,>=3.0.1)
@@ -35,9 +35,9 @@ Requires-Dist: pyusb (==1.2.1)
35
35
  Requires-Dist: packaging (>=21.0)
36
36
  Requires-Dist: importlib-metadata (>=1.0) ; python_version < "3.8"
37
37
  Provides-Extra: flex-hardware
38
- Requires-Dist: opentrons-hardware[flex] (==8.4.0a5) ; extra == 'flex-hardware'
38
+ Requires-Dist: opentrons-hardware[flex] (==8.4.0a7) ; extra == 'flex-hardware'
39
39
  Provides-Extra: ot2-hardware
40
- Requires-Dist: opentrons-hardware (==8.4.0a5) ; extra == 'ot2-hardware'
40
+ Requires-Dist: opentrons-hardware (==8.4.0a7) ; extra == 'ot2-hardware'
41
41
 
42
42
  .. _Full API Documentation: http://docs.opentrons.com
43
43
 
@@ -228,11 +228,11 @@ opentrons/protocol_api/config.py,sha256=r9lyvXjagTX_g3q5FGURPpcz2IA9sSF7Oa_1mKx-
228
228
  opentrons/protocol_api/create_protocol_context.py,sha256=wwsZje0L__oDnu1Yrihau320_f-ASloR9eL1QCtkOh8,7612
229
229
  opentrons/protocol_api/deck.py,sha256=94vFceg1SC1bAGd7TvC1ZpYwnJR-VlzurEZ6jkacYeg,8910
230
230
  opentrons/protocol_api/disposal_locations.py,sha256=NRiSGmDR0LnbyEkWSOM-o64uR2fUoB1NWJG7Y7SsJSs,7920
231
- opentrons/protocol_api/instrument_context.py,sha256=XUVIxnylpkhviFy5jxYJv-o7Lvmb2FJIbdoEjQ_9Vro,117886
232
- opentrons/protocol_api/labware.py,sha256=1m1y7h70bBBqW60LjiCPQWwFfVXzY_goJrB773yUN0A,60407
231
+ opentrons/protocol_api/instrument_context.py,sha256=-X5RM0jAiplHNt5xr1Giarch1airL7nAhqMZWm_XATg,120139
232
+ opentrons/protocol_api/labware.py,sha256=KRt91dOzmCX6l8LQ5Wny7kIk_L0Y69Uzpolp-geE0MY,60458
233
233
  opentrons/protocol_api/module_contexts.py,sha256=3tVXj6Q7n-WuTJPU_dvIQLzzGv1P-jsMuDtMVpuhAf8,48291
234
234
  opentrons/protocol_api/module_validation_and_errors.py,sha256=XL_m72P8rcvGO2fynY7UzXLcpGuI6X4s0V6Xf735Iyc,1464
235
- opentrons/protocol_api/protocol_context.py,sha256=CHMG5xbx_wxHDQYcobOWo2E9vsRw7FPSNy9J3YV99fM,66126
235
+ opentrons/protocol_api/protocol_context.py,sha256=RBdvcgWwyD6yoIocCtQiqVRszrGiz1SXheZlX8FJAkA,66227
236
236
  opentrons/protocol_api/robot_context.py,sha256=hlxFOYx0SkqK7TihT25leYt-RJ_VvhDDzzclf1L8DKw,10405
237
237
  opentrons/protocol_api/validation.py,sha256=uiVTHyJF3wSh5LLfaIDBTELoMNCAT17E767ursB_s1g,28684
238
238
  opentrons/protocol_api/core/__init__.py,sha256=-g74o8OtBB0LmmOvwkRvPgrHt7fF7T8FRHDj-x_-Onk,736
@@ -243,7 +243,7 @@ opentrons/protocol_api/core/labware.py,sha256=-ZOjkalikXCV3ptehKCNaWGAdKxIdwne8L
243
243
  opentrons/protocol_api/core/module.py,sha256=z2STDyqqxZX3y6UyJVDnajeFXMEn1ie2NRBYHhry_XE,13838
244
244
  opentrons/protocol_api/core/protocol.py,sha256=v7v28jfeHSfOf-tqFDW2chGtrEatPiZ1y6YNwHfmtAs,9058
245
245
  opentrons/protocol_api/core/robot.py,sha256=QMAqj5Oqq3_IhTDyUF4jpWI4j2LRPP9crUiaYD_RUv4,1385
246
- opentrons/protocol_api/core/well.py,sha256=PpnbiBxLa9ov5dF5QcV6H2pNRt30VwKS3h4Rsi9ogEc,3100
246
+ opentrons/protocol_api/core/well.py,sha256=LUPOoSouNdRkTknryNjArtzDLz2Bl8-y9bscP3peVjM,3135
247
247
  opentrons/protocol_api/core/well_grid.py,sha256=BU28DKaBgEU_JdZ6pEzrwNxmuh6TkO4zlg7Pq1Rf5Xk,1516
248
248
  opentrons/protocol_api/core/engine/__init__.py,sha256=B_5T7zgkWDb1mXPg4NbT-wBkQaK-WVokMMnJRNu7xiM,582
249
249
  opentrons/protocol_api/core/engine/deck_conflict.py,sha256=q3JViIAHDthIqq6ce7h2gxw3CHRfYsm5kkwzuXB-Gnc,12334
@@ -259,7 +259,7 @@ opentrons/protocol_api/core/engine/protocol.py,sha256=_1gdg4lq2B21LWV9Tqb924E39H
259
259
  opentrons/protocol_api/core/engine/robot.py,sha256=o252HrC11tmZ5LRKT6NwXCoTeqcQFXHeNjszfxbJHjo,5366
260
260
  opentrons/protocol_api/core/engine/stringify.py,sha256=GwFgEhFMk-uPfFQhQG_2mkaf4cxaItiY8RW7rZwiooQ,2794
261
261
  opentrons/protocol_api/core/engine/transfer_components_executor.py,sha256=96SsftBRPB5WCeChLokXkdPJcmjP8FqlXEZXhNAlZKA,37582
262
- opentrons/protocol_api/core/engine/well.py,sha256=PEtDwdC8NkHjqasJaDaVDtzc_WFw-qv5Lf7IU1DkrLY,7570
262
+ opentrons/protocol_api/core/engine/well.py,sha256=o4Qm25AvZwd4ytR3IpK4wH-BwAmzt_7fVIKQsbHMsgg,8122
263
263
  opentrons/protocol_api/core/legacy/__init__.py,sha256=_9jCJNKG3SlS_vljVu8HHkZmtLf4F-f-JHALLF5d5go,401
264
264
  opentrons/protocol_api/core/legacy/deck.py,sha256=qHqcGo-Kdkl9L1aOE0pwrm9tsAnwkXbt4rIOr_VEP-s,13955
265
265
  opentrons/protocol_api/core/legacy/labware_offset_provider.py,sha256=2DLIby9xmUrwLb2ht8hZbvNTxqPhNzWijd7yCb2cqP8,3783
@@ -267,7 +267,7 @@ opentrons/protocol_api/core/legacy/legacy_instrument_core.py,sha256=k-aM8Eu4qHqf
267
267
  opentrons/protocol_api/core/legacy/legacy_labware_core.py,sha256=WQOgtMlq--zv0Ch7mmraYr9rQBT4ie2zHqwgamBq9J8,8606
268
268
  opentrons/protocol_api/core/legacy/legacy_module_core.py,sha256=tUhj88NKBMjCmCg6wjh1e2HX4d5hxjh8ZeJiYXaTaGY,23111
269
269
  opentrons/protocol_api/core/legacy/legacy_protocol_core.py,sha256=ZIFC7W6YA61oWWkq5xYGTcI_2S2pmALz16uB1R8HVyQ,23670
270
- opentrons/protocol_api/core/legacy/legacy_well_core.py,sha256=bh4VVrhQ2beBTotUpVF_zL8c1WW7jIo5oPphfsFmekE,5175
270
+ opentrons/protocol_api/core/legacy/legacy_well_core.py,sha256=LNMF0q3A6tUJQb7zM5eBg0IGpb85OLPzDdsex8y3hNU,5210
271
271
  opentrons/protocol_api/core/legacy/load_info.py,sha256=r-WaH5ZJb3TRCp_zvbMMh0P4BhbZM8HsBs1K_pU98dk,1857
272
272
  opentrons/protocol_api/core/legacy/module_geometry.py,sha256=lvWFHZ81-JFw-1VZUW1R3yUIb59xpXT6H3jwlRintRo,21082
273
273
  opentrons/protocol_api/core/legacy/well_geometry.py,sha256=n5bEsvYZXXTAqYSAqlXd5t40bUPPrJ2Oj2frBZafQHA,4719
@@ -418,14 +418,14 @@ opentrons/protocol_engine/execution/hardware_stopper.py,sha256=wlIl7U3gvnOiCvwri
418
418
  opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py,sha256=BSFLzSSeELAYZCrCUfJZx5DdlrwU06Ur92TYd0T-hzM,9084
419
419
  opentrons/protocol_engine/execution/labware_movement.py,sha256=XYVcxZOQ6_udpxcpwkIJpVD8-lgWLhizJAcRD9BclIo,12247
420
420
  opentrons/protocol_engine/execution/movement.py,sha256=AWcj7xlrOh3QrvoaH0sZO63yrPCEc7VE8hKfJNxxtP0,12527
421
- opentrons/protocol_engine/execution/pipetting.py,sha256=0_OAxIDCQBAtdNOvdYwV0kEYfJvUrJWr-63rocqf75E,22094
421
+ opentrons/protocol_engine/execution/pipetting.py,sha256=96ubBr_lCkfYieD8NheSKeiVoxXepVDcAZH1qCajDmc,22164
422
422
  opentrons/protocol_engine/execution/queue_worker.py,sha256=riVVywKIOQ3Lx-woFuuSqqBtfeKFt23nCUnsk7gSVoI,2860
423
423
  opentrons/protocol_engine/execution/rail_lights.py,sha256=eiJT6oI_kFk7rFuFkZzISZiLNnpf7Kkh86Kyk9wQ_Jo,590
424
424
  opentrons/protocol_engine/execution/run_control.py,sha256=ksvI2zkguC4G3lR3HJgAF8uY1PNcaRfi7UOYu-oIZgo,1144
425
425
  opentrons/protocol_engine/execution/status_bar.py,sha256=tR7CHS_y1ARQxcSKDO4YFU2cqVQhePzalmzsyH8b23A,970
426
426
  opentrons/protocol_engine/execution/thermocycler_movement_flagger.py,sha256=Ouljgjtm7-sCXwDcpfbir84dAZh5y89DNiuKedYimyg,6790
427
427
  opentrons/protocol_engine/execution/thermocycler_plate_lifter.py,sha256=j33nYV8rkeAYUOau8wFIyJVWjWkjyildleYHCysez-o,3375
428
- opentrons/protocol_engine/execution/tip_handler.py,sha256=DDlI-AwdasbwLjZpN6r76pxqr1WI6R21m3tElBswg5o,18398
428
+ opentrons/protocol_engine/execution/tip_handler.py,sha256=Ouunj3KVqz-UMbkjFIbJJr2zpfgcUht_r4_60uHEx3M,19731
429
429
  opentrons/protocol_engine/notes/__init__.py,sha256=G0bIQswsov7MrJU0ArrOaWcOTxJU9BCUmNR3LRoNg-Q,311
430
430
  opentrons/protocol_engine/notes/notes.py,sha256=A5C9xHExlS9GAK7o_mYiKJgibBm6EEgHQ4PJor0IET0,1993
431
431
  opentrons/protocol_engine/resources/__init__.py,sha256=yvGFYpmLoxHYQff_IwiaEH9viZUfal5D5K91UjYLwwY,805
@@ -449,8 +449,8 @@ opentrons/protocol_engine/state/commands.py,sha256=aoON_C5DbiIEPxOGATvwCsSG9eHsF
449
449
  opentrons/protocol_engine/state/config.py,sha256=7jSGxC6Vqj1eA8fqZ2I3zjlxVXg8pxvcBYMztRIx9Mg,1515
450
450
  opentrons/protocol_engine/state/files.py,sha256=w8xxxg8HY0RqKKEGSfHWfrjV54Gb02O3dwtisJ-9j8E,1753
451
451
  opentrons/protocol_engine/state/fluid_stack.py,sha256=uwkf0qYk1UX5iU52xmk-e3yLPK8OG-TtMCcBqrkVFpM,5932
452
- opentrons/protocol_engine/state/frustum_helpers.py,sha256=I_tVCqy-CgYBqBS7FVyDpxdz0AdDeKYyeTPmaZuUIqU,17342
453
- opentrons/protocol_engine/state/geometry.py,sha256=_4buTI8kNjCogc4zKJwhCrvlB71yXrdcYqiwen-qkcA,97628
452
+ opentrons/protocol_engine/state/frustum_helpers.py,sha256=uRBLLR75Z_PnfVd-U7gPF3NeOALR3TgLNCojgxB4z0o,17343
453
+ opentrons/protocol_engine/state/geometry.py,sha256=oTMORp6Zc96HQTsEA8kWAtnVJCfMLdqBMcwP5_wEsQQ,98552
454
454
  opentrons/protocol_engine/state/labware.py,sha256=rehy7R1HIhxx3DTtTHIKxqHoBQJ_1tDhhiculMJeIy8,57556
455
455
  opentrons/protocol_engine/state/liquid_classes.py,sha256=u_z75UYdiFAKG0yB3mr1il4T3qaS0Sotq8sL7KLODP8,2990
456
456
  opentrons/protocol_engine/state/liquids.py,sha256=NoesktcQdJUjIVmet1uqqJPf-rzbo4SGemXwQC295W0,2338
@@ -470,7 +470,7 @@ opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py,sha2
470
470
  opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py,sha256=IJ5zpufz5WSRbJqHOAi-WroDxpsRZz-GvwznIL4v7VQ,2468
471
471
  opentrons/protocol_engine/state/module_substates/temperature_module_substate.py,sha256=w9h6EBM1YY8SeUOlUz5-nW1Zoyce8-zua8Z6mX4sDNg,2310
472
472
  opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py,sha256=fLt2jMsbnfe8Q25vAjloxLBGdx8sotqM34VxbwfegpE,5167
473
- opentrons/protocol_engine/types/__init__.py,sha256=KUKrF0jBWlY2BUsCNnu_9OdMYu51YDpfKaUWBAJZtzE,7994
473
+ opentrons/protocol_engine/types/__init__.py,sha256=IQ7XVYNQqP_1ZWyqpYvCBDgNphWTe8CgK_G32jB34UY,8048
474
474
  opentrons/protocol_engine/types/automatic_tip_selection.py,sha256=I_B3iWei1Sl7F7IrMKqOn4S12heZXRnfKvtCTUXIMyM,1118
475
475
  opentrons/protocol_engine/types/command_annotations.py,sha256=5A4k_R_4A2_nGl0K85SKwNlnKA09fUhEIe_mdU55yro,1843
476
476
  opentrons/protocol_engine/types/deck_configuration.py,sha256=O5zO6sX0iGsrWvBxuR3JnxjtAH-gSMImcy1krP4B360,2047
@@ -485,7 +485,7 @@ opentrons/protocol_engine/types/labware_offset_vector.py,sha256=5Hhyv60I8KpZdUDn
485
485
  opentrons/protocol_engine/types/liquid.py,sha256=6Ec0fC0SEN3jKHYeFSwbQxdEAj5hxDPHlDcL1wXlx6k,810
486
486
  opentrons/protocol_engine/types/liquid_class.py,sha256=SF5WS3s38S87efUqawRGSIYqjhwa4pNx7fB1xdiGHl0,2384
487
487
  opentrons/protocol_engine/types/liquid_handling.py,sha256=Xx1GihrNRJJdJJA5zIwWvIYNydbSXAHjSUAliF18Iu0,319
488
- opentrons/protocol_engine/types/liquid_level_detection.py,sha256=NmEasE7HR3GqtCj8tNBSuMufBLVMPYNbTXSvXVJu_JM,4783
488
+ opentrons/protocol_engine/types/liquid_level_detection.py,sha256=0Pf9B_w6tTZ110pGbjgZVoigmOIEF0DVuXVHvhRKeNQ,4395
489
489
  opentrons/protocol_engine/types/location.py,sha256=qIYBs86RO1ws2aXStmdx0CqEVNF9enlj-ACknf75nSs,5707
490
490
  opentrons/protocol_engine/types/module.py,sha256=su7Qw5Z0o-qAGfz0XC6nOqJpgpe84Kx96E9oHUt7Q9I,8921
491
491
  opentrons/protocol_engine/types/partial_tip_configuration.py,sha256=4RMtHOAX-dgpXWA737tthj_izTBnhKphBcA24LAKmhI,2760
@@ -583,9 +583,9 @@ opentrons/util/linal.py,sha256=IlKAP9HkNBBgULeSf4YVwSKHdx9jnCjSr7nvDvlRALg,5753
583
583
  opentrons/util/logging_config.py,sha256=7et4YYuQdWdq_e50U-8vFS_QyNBRgdnqPGAQJm8qrIo,9954
584
584
  opentrons/util/logging_queue_handler.py,sha256=ZsSJwy-oV8DXwpYiZisQ1PbYwmK2cOslD46AcyJ1E4I,2484
585
585
  opentrons/util/performance_helpers.py,sha256=ew7H8XD20iS6-2TJAzbQeyzStZkkE6PzHt_Adx3wbZQ,5172
586
- opentrons-8.4.0a5.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
587
- opentrons-8.4.0a5.dist-info/METADATA,sha256=dVv_Zex7NX_Yf6tBwmhxcaTUWKOKDvYAmuqkq99qwEU,5084
588
- opentrons-8.4.0a5.dist-info/WHEEL,sha256=qUzzGenXXuJTzyjFah76kDVqDvnk-YDzY00svnrl84w,109
589
- opentrons-8.4.0a5.dist-info/entry_points.txt,sha256=fTa6eGCYkvOtv0ov-KVE8LLGetgb35LQLF9x85OWPVw,106
590
- opentrons-8.4.0a5.dist-info/top_level.txt,sha256=wk6whpbMZdBQpcK0Fg0YVfUGrAgVOFON7oQAhOMGMW8,10
591
- opentrons-8.4.0a5.dist-info/RECORD,,
586
+ opentrons-8.4.0a7.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
587
+ opentrons-8.4.0a7.dist-info/METADATA,sha256=IcoTsLV3w0JPwCTAuaSs0jj8PUFnhR8M1RTdrygSaEw,5084
588
+ opentrons-8.4.0a7.dist-info/WHEEL,sha256=qUzzGenXXuJTzyjFah76kDVqDvnk-YDzY00svnrl84w,109
589
+ opentrons-8.4.0a7.dist-info/entry_points.txt,sha256=fTa6eGCYkvOtv0ov-KVE8LLGetgb35LQLF9x85OWPVw,106
590
+ opentrons-8.4.0a7.dist-info/top_level.txt,sha256=wk6whpbMZdBQpcK0Fg0YVfUGrAgVOFON7oQAhOMGMW8,10
591
+ opentrons-8.4.0a7.dist-info/RECORD,,