opentrons 8.3.0a1__py2.py3-none-any.whl → 8.3.0a4__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.

Files changed (58) hide show
  1. opentrons/hardware_control/api.py +5 -1
  2. opentrons/hardware_control/ot3api.py +18 -8
  3. opentrons/hardware_control/protocols/liquid_handler.py +4 -1
  4. opentrons/hardware_control/protocols/motion_controller.py +1 -0
  5. opentrons/legacy_commands/commands.py +37 -0
  6. opentrons/legacy_commands/types.py +39 -0
  7. opentrons/protocol_api/core/engine/instrument.py +109 -0
  8. opentrons/protocol_api/core/instrument.py +27 -0
  9. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +50 -0
  10. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +24 -0
  11. opentrons/protocol_api/instrument_context.py +141 -0
  12. opentrons/protocol_engine/commands/__init__.py +40 -0
  13. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -1
  14. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +1 -1
  15. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -1
  16. opentrons/protocol_engine/commands/absorbance_reader/read.py +4 -1
  17. opentrons/protocol_engine/commands/air_gap_in_place.py +1 -1
  18. opentrons/protocol_engine/commands/command_unions.py +39 -0
  19. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  20. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  21. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  22. opentrons/protocol_engine/commands/get_next_tip.py +1 -1
  23. opentrons/protocol_engine/commands/liquid_probe.py +63 -12
  24. opentrons/protocol_engine/commands/load_labware.py +5 -0
  25. opentrons/protocol_engine/commands/load_lid.py +1 -1
  26. opentrons/protocol_engine/commands/load_lid_stack.py +1 -1
  27. opentrons/protocol_engine/commands/load_liquid_class.py +1 -1
  28. opentrons/protocol_engine/commands/move_labware.py +9 -0
  29. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +1 -1
  30. opentrons/protocol_engine/commands/robot/move_axes_relative.py +1 -1
  31. opentrons/protocol_engine/commands/robot/move_axes_to.py +1 -1
  32. opentrons/protocol_engine/commands/robot/move_to.py +1 -1
  33. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +1 -1
  34. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +1 -1
  35. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +1 -1
  36. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +1 -1
  37. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -1
  38. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  39. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +1 -1
  40. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -1
  41. opentrons/protocol_engine/errors/__init__.py +4 -0
  42. opentrons/protocol_engine/errors/exceptions.py +26 -0
  43. opentrons/protocol_engine/execution/gantry_mover.py +5 -0
  44. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  45. opentrons/protocol_engine/execution/tip_handler.py +30 -9
  46. opentrons/protocol_engine/resources/labware_validation.py +13 -0
  47. opentrons/protocol_engine/state/commands.py +6 -2
  48. opentrons/protocol_engine/state/frustum_helpers.py +13 -44
  49. opentrons/protocol_engine/state/labware.py +13 -1
  50. opentrons/protocol_engine/state/pipettes.py +5 -0
  51. opentrons/protocols/labware.py +37 -8
  52. opentrons/util/entrypoint_util.py +2 -5
  53. {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/METADATA +4 -4
  54. {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/RECORD +58 -55
  55. {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/LICENSE +0 -0
  56. {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/WHEEL +0 -0
  57. {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/entry_points.txt +0 -0
  58. {opentrons-8.3.0a1.dist-info → opentrons-8.3.0a4.dist-info}/top_level.txt +0 -0
@@ -65,7 +65,7 @@ class HardwareStopper:
65
65
  axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z]
66
66
  )
67
67
 
68
- async def _drop_tip(self) -> None:
68
+ async def _try_to_drop_tips(self) -> None:
69
69
  """Drop currently attached tip, if any, into trash after a run cancel."""
70
70
  attached_tips = self._state_store.pipettes.get_all_attached_tips()
71
71
 
@@ -134,9 +134,9 @@ class HardwareStopper:
134
134
  PostRunHardwareState.HOME_THEN_DISENGAGE,
135
135
  )
136
136
  if drop_tips_after_run:
137
- await self._drop_tip()
138
- await self._hardware_api.stop(home_after=home_after_stop)
139
- else:
140
- await self._hardware_api.stop(home_after=False)
141
- if home_after_stop:
142
- await self._home_everything_except_plungers()
137
+ await self._try_to_drop_tips()
138
+
139
+ await self._hardware_api.stop(home_after=False)
140
+
141
+ if home_after_stop:
142
+ await self._home_everything_except_plungers()
@@ -62,6 +62,7 @@ class TipHandler(TypingProtocol):
62
62
  pipette_id: str,
63
63
  labware_id: str,
64
64
  well_name: str,
65
+ do_not_ignore_tip_presence: bool = True,
65
66
  ) -> TipGeometry:
66
67
  """Pick up the named tip.
67
68
 
@@ -75,7 +76,13 @@ class TipHandler(TypingProtocol):
75
76
  """
76
77
  ...
77
78
 
78
- async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
79
+ async def drop_tip(
80
+ self,
81
+ pipette_id: str,
82
+ home_after: Optional[bool],
83
+ do_not_ignore_tip_presence: bool = True,
84
+ ignore_plunger: bool = False,
85
+ ) -> None:
79
86
  """Drop the attached tip into the current location.
80
87
 
81
88
  Pipette should be in place over the destination prior to calling this method.
@@ -230,6 +237,7 @@ class HardwareTipHandler(TipHandler):
230
237
  pipette_id: str,
231
238
  labware_id: str,
232
239
  well_name: str,
240
+ do_not_ignore_tip_presence: bool = True,
233
241
  ) -> TipGeometry:
234
242
  """See documentation on abstract base class."""
235
243
  hw_mount = self._get_hw_mount(pipette_id)
@@ -253,10 +261,11 @@ class HardwareTipHandler(TipHandler):
253
261
  await self._hardware_api.tip_pickup_moves(
254
262
  mount=hw_mount, presses=None, increment=None
255
263
  )
256
- try:
257
- await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
258
- except TipNotAttachedError as e:
259
- raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
264
+ if do_not_ignore_tip_presence:
265
+ try:
266
+ await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT)
267
+ except TipNotAttachedError as e:
268
+ raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e
260
269
 
261
270
  self.cache_tip(pipette_id, tip_geometry)
262
271
 
@@ -264,7 +273,13 @@ class HardwareTipHandler(TipHandler):
264
273
 
265
274
  return tip_geometry
266
275
 
267
- async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
276
+ async def drop_tip(
277
+ self,
278
+ pipette_id: str,
279
+ home_after: Optional[bool],
280
+ do_not_ignore_tip_presence: bool = True,
281
+ ignore_plunger: bool = False,
282
+ ) -> None:
268
283
  """See documentation on abstract base class."""
269
284
  hw_mount = self._get_hw_mount(pipette_id)
270
285
 
@@ -275,10 +290,13 @@ class HardwareTipHandler(TipHandler):
275
290
  else:
276
291
  kwargs = {}
277
292
 
278
- await self._hardware_api.tip_drop_moves(mount=hw_mount, **kwargs)
293
+ await self._hardware_api.tip_drop_moves(
294
+ mount=hw_mount, ignore_plunger=ignore_plunger, **kwargs
295
+ )
279
296
 
280
- # Allow TipNotAttachedError to propagate.
281
- await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)
297
+ if do_not_ignore_tip_presence:
298
+ # Allow TipNotAttachedError to propagate.
299
+ await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)
282
300
 
283
301
  self.remove_tip(pipette_id)
284
302
 
@@ -383,6 +401,7 @@ class VirtualTipHandler(TipHandler):
383
401
  pipette_id: str,
384
402
  labware_id: str,
385
403
  well_name: str,
404
+ do_not_ignore_tip_presence: bool = True,
386
405
  ) -> TipGeometry:
387
406
  """Pick up a tip at the current location using a virtual pipette.
388
407
 
@@ -424,6 +443,8 @@ class VirtualTipHandler(TipHandler):
424
443
  self,
425
444
  pipette_id: str,
426
445
  home_after: Optional[bool],
446
+ do_not_ignore_tip_presence: bool = True,
447
+ ignore_plunger: bool = False,
427
448
  ) -> None:
428
449
  """Pick up a tip at the current location using a virtual pipette.
429
450
 
@@ -14,6 +14,11 @@ def is_absorbance_reader_lid(load_name: str) -> bool:
14
14
  return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
15
15
 
16
16
 
17
+ def is_evotips(load_name: str) -> bool:
18
+ """Check if a labware is an evotips tiprack."""
19
+ return load_name == "evotips_opentrons_96_labware"
20
+
21
+
17
22
  def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
18
23
  """Validate that one of the definition's allowed roles is `labware`.
19
24
 
@@ -44,6 +49,14 @@ def validate_labware_can_be_stacked(
44
49
  return below_labware_load_name in top_labware_definition.stackingOffsetWithLabware
45
50
 
46
51
 
52
+ def validate_labware_can_be_ondeck(definition: LabwareDefinition) -> bool:
53
+ """Validate that the labware being loaded onto the deck can sit in a slot."""
54
+ return (
55
+ definition.parameters.quirks is None
56
+ or "stackingOnly" not in definition.parameters.quirks
57
+ )
58
+
59
+
47
60
  def validate_gripper_compatible(definition: LabwareDefinition) -> bool:
48
61
  """Validate that the labware definition does not have a quirk disallowing movement with gripper."""
49
62
  return (
@@ -1,4 +1,5 @@
1
1
  """Protocol engine commands sub-state."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import enum
@@ -304,7 +305,7 @@ class CommandStore(HasState[CommandState], HandlesActions):
304
305
  # TODO(mc, 2021-06-22): mypy has trouble with this automatic
305
306
  # request > command mapping, figure out how to type precisely
306
307
  # (or wait for a future mypy version that can figure it out).
307
- queued_command = action.request._CommandCls.model_construct( # type: ignore[call-arg]
308
+ queued_command = action.request._CommandCls.model_construct(
308
309
  id=action.command_id,
309
310
  key=(
310
311
  action.request.key
@@ -506,7 +507,10 @@ class CommandStore(HasState[CommandState], HandlesActions):
506
507
  pass
507
508
  case QueueStatus.RUNNING | QueueStatus.PAUSED:
508
509
  self._state.queue_status = QueueStatus.PAUSED
509
- case QueueStatus.AWAITING_RECOVERY | QueueStatus.AWAITING_RECOVERY_PAUSED:
510
+ case (
511
+ QueueStatus.AWAITING_RECOVERY
512
+ | QueueStatus.AWAITING_RECOVERY_PAUSED
513
+ ):
510
514
  self._state.queue_status = QueueStatus.AWAITING_RECOVERY_PAUSED
511
515
  elif action.door_state == DoorState.CLOSED:
512
516
  self._state.is_door_blocking = False
@@ -82,19 +82,12 @@ def _circular_frustum_polynomial_roots(
82
82
 
83
83
 
84
84
  def _volume_from_height_circular(
85
- target_height: float,
86
- total_frustum_height: float,
87
- bottom_radius: float,
88
- top_radius: float,
85
+ target_height: float, segment: ConicalFrustum
89
86
  ) -> float:
90
87
  """Find the volume given a height within a circular frustum."""
91
- a, b, c = _circular_frustum_polynomial_roots(
92
- bottom_radius=bottom_radius,
93
- top_radius=top_radius,
94
- total_frustum_height=total_frustum_height,
95
- )
96
- volume = a * (target_height**3) + b * (target_height**2) + c * target_height
97
- return volume
88
+ heights = segment.height_to_volume_table.keys()
89
+ best_fit_height = min(heights, key=lambda x: abs(x - target_height))
90
+ return segment.height_to_volume_table[best_fit_height]
98
91
 
99
92
 
100
93
  def _volume_from_height_rectangular(
@@ -138,26 +131,12 @@ def _volume_from_height_squared_cone(
138
131
 
139
132
 
140
133
  def _height_from_volume_circular(
141
- volume: float,
142
- total_frustum_height: float,
143
- bottom_radius: float,
144
- top_radius: float,
134
+ target_volume: float, segment: ConicalFrustum
145
135
  ) -> float:
146
- """Find the height given a volume within a circular frustum."""
147
- a, b, c = _circular_frustum_polynomial_roots(
148
- bottom_radius=bottom_radius,
149
- top_radius=top_radius,
150
- total_frustum_height=total_frustum_height,
151
- )
152
- d = volume * -1
153
- x_intercept_roots = (a, b, c, d)
154
-
155
- height_from_volume_roots = roots(x_intercept_roots)
156
- height = _reject_unacceptable_heights(
157
- potential_heights=list(height_from_volume_roots),
158
- max_height=total_frustum_height,
159
- )
160
- return height
136
+ """Find the height given a volume within a squared cone segment."""
137
+ volumes = segment.volume_to_height_table.keys()
138
+ best_fit_volume = min(volumes, key=lambda x: abs(x - target_volume))
139
+ return segment.volume_to_height_table[best_fit_volume]
161
140
 
162
141
 
163
142
  def _height_from_volume_rectangular(
@@ -243,9 +222,7 @@ def _get_segment_capacity(segment: WellSegment) -> float:
243
222
  return (
244
223
  _volume_from_height_circular(
245
224
  target_height=section_height,
246
- total_frustum_height=section_height,
247
- bottom_radius=(segment.bottomDiameter / 2),
248
- top_radius=(segment.topDiameter / 2),
225
+ segment=segment,
249
226
  )
250
227
  * segment.count
251
228
  )
@@ -293,12 +270,7 @@ def height_at_volume_within_section(
293
270
  radius_of_curvature=section.radiusOfCurvature,
294
271
  )
295
272
  case ConicalFrustum():
296
- return _height_from_volume_circular(
297
- volume=target_volume_relative,
298
- top_radius=(section.bottomDiameter / 2),
299
- bottom_radius=(section.topDiameter / 2),
300
- total_frustum_height=section_height,
301
- )
273
+ return _height_from_volume_circular(target_volume_relative, section)
302
274
  case CuboidalFrustum():
303
275
  return _height_from_volume_rectangular(
304
276
  volume=target_volume_relative,
@@ -334,10 +306,7 @@ def volume_at_height_within_section(
334
306
  case ConicalFrustum():
335
307
  return (
336
308
  _volume_from_height_circular(
337
- target_height=target_height_relative,
338
- total_frustum_height=section_height,
339
- bottom_radius=(section.bottomDiameter / 2),
340
- top_radius=(section.topDiameter / 2),
309
+ target_height=target_height_relative, segment=section
341
310
  )
342
311
  * section.count
343
312
  )
@@ -427,7 +396,7 @@ def _find_height_in_partial_frustum(
427
396
  if (
428
397
  bottom_section_volume
429
398
  < target_volume
430
- < (bottom_section_volume + section_volume)
399
+ <= (bottom_section_volume + section_volume)
431
400
  ):
432
401
  relative_target_volume = target_volume - bottom_section_volume
433
402
  section_height = section.topHeight - section.bottomHeight
@@ -524,7 +524,6 @@ class LabwareView:
524
524
  will be used.
525
525
  """
526
526
  definition = self.get_definition(labware_id)
527
-
528
527
  if well_name is None:
529
528
  well_name = definition.ordering[0][0]
530
529
 
@@ -887,6 +886,19 @@ class LabwareView:
887
886
  f"Labware {labware.loadName} is already present at {location}."
888
887
  )
889
888
 
889
+ def raise_if_labware_cannot_be_ondeck(
890
+ self,
891
+ location: LabwareLocation,
892
+ labware_definition: LabwareDefinition,
893
+ ) -> None:
894
+ """Raise an error if the labware cannot be in the specified location."""
895
+ if isinstance(
896
+ location, (DeckSlotLocation, AddressableAreaLocation)
897
+ ) and not labware_validation.validate_labware_can_be_ondeck(labware_definition):
898
+ raise errors.LabwareCannotSitOnDeckError(
899
+ f"{labware_definition.parameters.loadName} cannot sit in a slot by itself."
900
+ )
901
+
890
902
  def raise_if_labware_incompatible_with_plate_reader(
891
903
  self,
892
904
  labware_definition: LabwareDefinition,
@@ -331,6 +331,11 @@ class PipetteStore(HasState[PipetteState], HandlesActions):
331
331
  def _update_aspirated(
332
332
  self, update: update_types.PipetteAspiratedFluidUpdate
333
333
  ) -> None:
334
+ if self._state.pipette_contents_by_id[update.pipette_id] is None:
335
+ self._state.pipette_contents_by_id[
336
+ update.pipette_id
337
+ ] = fluid_stack.FluidStack()
338
+
334
339
  self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid)
335
340
 
336
341
  def _update_ejected(self, update: update_types.PipetteEjectedFluidUpdate) -> None:
@@ -4,7 +4,7 @@ import logging
4
4
  import json
5
5
  import os
6
6
  from pathlib import Path
7
- from typing import Any, AnyStr, Dict, Optional, Union, List
7
+ from typing import Any, AnyStr, Dict, Optional, Union, List, Sequence, Literal
8
8
 
9
9
  import jsonschema # type: ignore
10
10
 
@@ -17,10 +17,30 @@ from opentrons.protocols.api_support.constants import (
17
17
  USER_DEFS_PATH,
18
18
  )
19
19
  from opentrons_shared_data.labware.types import LabwareDefinition
20
+ from opentrons_shared_data.errors.exceptions import InvalidProtocolData
20
21
 
21
22
 
22
23
  MODULE_LOG = logging.getLogger(__name__)
23
24
 
25
+ LabwareProblem = Literal[
26
+ "no-schema-id", "bad-schema-id", "schema-mismatch", "invalid-json"
27
+ ]
28
+
29
+
30
+ class NotALabwareError(InvalidProtocolData):
31
+ def __init__(
32
+ self, problem: LabwareProblem, wrapping: Sequence[BaseException]
33
+ ) -> None:
34
+ messages: dict[LabwareProblem, str] = {
35
+ "no-schema-id": "No schema ID present in file",
36
+ "bad-schema-id": "Bad schema ID in file",
37
+ "invalid-json": "File does not contain valid JSON",
38
+ "schema-mismatch": "File does not match labware schema",
39
+ }
40
+ super().__init__(
41
+ message=messages[problem], detail={"kind": problem}, wrapping=wrapping
42
+ )
43
+
24
44
 
25
45
  def get_labware_definition(
26
46
  load_name: str,
@@ -126,7 +146,7 @@ def save_definition(
126
146
  json.dump(labware_def, f)
127
147
 
128
148
 
129
- def verify_definition(
149
+ def verify_definition( # noqa: C901
130
150
  contents: Union[AnyStr, LabwareDefinition, Dict[str, Any]]
131
151
  ) -> LabwareDefinition:
132
152
  """Verify that an input string is a labware definition and return it.
@@ -146,15 +166,24 @@ def verify_definition(
146
166
  if isinstance(contents, dict):
147
167
  to_return = contents
148
168
  else:
149
- to_return = json.loads(contents)
169
+ try:
170
+ to_return = json.loads(contents)
171
+ except json.JSONDecodeError as e:
172
+ raise NotALabwareError("invalid-json", [e]) from e
150
173
  try:
151
174
  schema_version = to_return["schemaVersion"]
175
+ except KeyError as e:
176
+ raise NotALabwareError("no-schema-id", [e]) from e
177
+
178
+ try:
152
179
  schema = schemata_by_version[schema_version]
153
- except KeyError:
154
- raise RuntimeError(
155
- f'Invalid or unknown labware schema version {to_return.get("schemaVersion", None)}'
156
- )
157
- jsonschema.validate(to_return, schema)
180
+ except KeyError as e:
181
+ raise NotALabwareError("bad-schema-id", [e]) from e
182
+
183
+ try:
184
+ jsonschema.validate(to_return, schema)
185
+ except jsonschema.ValidationError as e:
186
+ raise NotALabwareError("schema-mismatch", [e]) from e
158
187
 
159
188
  # we can type ignore this because if it passes the jsonschema it has
160
189
  # the correct structure
@@ -6,7 +6,6 @@ import contextlib
6
6
  from dataclasses import dataclass
7
7
  import json
8
8
  import logging
9
- from json import JSONDecodeError
10
9
  import pathlib
11
10
  import subprocess
12
11
  import sys
@@ -21,8 +20,6 @@ from typing import (
21
20
  TYPE_CHECKING,
22
21
  )
23
22
 
24
- from jsonschema import ValidationError # type: ignore
25
-
26
23
  from opentrons.calibration_storage.deck_configuration import (
27
24
  deserialize_deck_configuration,
28
25
  )
@@ -32,7 +29,7 @@ from opentrons.config import (
32
29
  JUPYTER_NOTEBOOK_LABWARE_DIR,
33
30
  SystemArchitecture,
34
31
  )
35
- from opentrons.protocol_api import labware
32
+ from opentrons.protocols import labware
36
33
  from opentrons.calibration_storage import helpers
37
34
  from opentrons.protocol_engine.errors.error_occurrence import (
38
35
  ErrorOccurrence as ProtocolEngineErrorOccurrence,
@@ -79,7 +76,7 @@ def labware_from_paths(
79
76
  if child.is_file() and child.suffix.endswith("json"):
80
77
  try:
81
78
  defn = labware.verify_definition(child.read_bytes())
82
- except (ValidationError, JSONDecodeError):
79
+ except labware.NotALabwareError:
83
80
  log.info(f"{child}: invalid labware, ignoring")
84
81
  log.debug(
85
82
  f"{child}: labware invalid because of this exception.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opentrons
3
- Version: 8.3.0a1
3
+ Version: 8.3.0a4
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.3.0a1)
24
+ Requires-Dist: opentrons-shared-data (==8.3.0a4)
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.3.0a1) ; extra == 'flex-hardware'
38
+ Requires-Dist: opentrons-hardware[flex] (==8.3.0a4) ; extra == 'flex-hardware'
39
39
  Provides-Extra: ot2-hardware
40
- Requires-Dist: opentrons-hardware (==8.3.0a1) ; extra == 'ot2-hardware'
40
+ Requires-Dist: opentrons-hardware (==8.3.0a4) ; extra == 'ot2-hardware'
41
41
 
42
42
  .. _Full API Documentation: http://docs.opentrons.com
43
43