opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0__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.
Files changed (238) hide show
  1. opentrons/cli/analyze.py +71 -7
  2. opentrons/config/__init__.py +9 -0
  3. opentrons/config/advanced_settings.py +22 -0
  4. opentrons/config/defaults_ot3.py +14 -36
  5. opentrons/config/feature_flags.py +4 -0
  6. opentrons/config/types.py +6 -17
  7. opentrons/drivers/absorbance_reader/abstract.py +27 -3
  8. opentrons/drivers/absorbance_reader/async_byonoy.py +208 -154
  9. opentrons/drivers/absorbance_reader/driver.py +24 -15
  10. opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
  11. opentrons/drivers/absorbance_reader/simulator.py +32 -6
  12. opentrons/drivers/types.py +23 -1
  13. opentrons/execute.py +2 -2
  14. opentrons/hardware_control/api.py +18 -10
  15. opentrons/hardware_control/backends/controller.py +3 -2
  16. opentrons/hardware_control/backends/flex_protocol.py +11 -5
  17. opentrons/hardware_control/backends/ot3controller.py +18 -50
  18. opentrons/hardware_control/backends/ot3simulator.py +7 -6
  19. opentrons/hardware_control/backends/ot3utils.py +1 -0
  20. opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
  21. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
  22. opentrons/hardware_control/module_control.py +43 -2
  23. opentrons/hardware_control/modules/__init__.py +7 -1
  24. opentrons/hardware_control/modules/absorbance_reader.py +232 -83
  25. opentrons/hardware_control/modules/errors.py +7 -0
  26. opentrons/hardware_control/modules/heater_shaker.py +8 -3
  27. opentrons/hardware_control/modules/magdeck.py +12 -3
  28. opentrons/hardware_control/modules/mod_abc.py +27 -2
  29. opentrons/hardware_control/modules/tempdeck.py +15 -7
  30. opentrons/hardware_control/modules/thermocycler.py +69 -3
  31. opentrons/hardware_control/modules/types.py +11 -5
  32. opentrons/hardware_control/modules/update.py +11 -5
  33. opentrons/hardware_control/modules/utils.py +3 -1
  34. opentrons/hardware_control/ot3_calibration.py +6 -6
  35. opentrons/hardware_control/ot3api.py +131 -94
  36. opentrons/hardware_control/poller.py +15 -11
  37. opentrons/hardware_control/protocols/__init__.py +1 -7
  38. opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
  39. opentrons/hardware_control/protocols/liquid_handler.py +5 -0
  40. opentrons/hardware_control/protocols/position_estimator.py +3 -1
  41. opentrons/hardware_control/types.py +2 -0
  42. opentrons/legacy_commands/helpers.py +8 -2
  43. opentrons/motion_planning/__init__.py +2 -0
  44. opentrons/motion_planning/waypoints.py +32 -0
  45. opentrons/protocol_api/__init__.py +2 -1
  46. opentrons/protocol_api/_liquid.py +87 -1
  47. opentrons/protocol_api/_parameter_context.py +10 -1
  48. opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
  49. opentrons/protocol_api/core/engine/instrument.py +29 -25
  50. opentrons/protocol_api/core/engine/labware.py +20 -4
  51. opentrons/protocol_api/core/engine/module_core.py +166 -17
  52. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +362 -0
  53. opentrons/protocol_api/core/engine/protocol.py +30 -2
  54. opentrons/protocol_api/core/instrument.py +2 -0
  55. opentrons/protocol_api/core/labware.py +4 -0
  56. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
  57. opentrons/protocol_api/core/legacy/legacy_labware_core.py +5 -0
  58. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +6 -2
  59. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
  60. opentrons/protocol_api/core/module.py +22 -4
  61. opentrons/protocol_api/core/protocol.py +6 -2
  62. opentrons/protocol_api/instrument_context.py +52 -20
  63. opentrons/protocol_api/labware.py +13 -1
  64. opentrons/protocol_api/module_contexts.py +115 -17
  65. opentrons/protocol_api/protocol_context.py +49 -5
  66. opentrons/protocol_api/validation.py +5 -3
  67. opentrons/protocol_engine/__init__.py +10 -9
  68. opentrons/protocol_engine/actions/__init__.py +3 -0
  69. opentrons/protocol_engine/actions/actions.py +30 -25
  70. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  71. opentrons/protocol_engine/clients/sync_client.py +1 -1
  72. opentrons/protocol_engine/clients/transports.py +1 -1
  73. opentrons/protocol_engine/commands/__init__.py +0 -4
  74. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
  75. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +148 -0
  76. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +65 -9
  77. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +148 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/read.py +200 -0
  79. opentrons/protocol_engine/commands/aspirate.py +29 -16
  80. opentrons/protocol_engine/commands/aspirate_in_place.py +33 -16
  81. opentrons/protocol_engine/commands/blow_out.py +63 -14
  82. opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
  83. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
  84. opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
  85. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
  86. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
  87. opentrons/protocol_engine/commands/command.py +31 -18
  88. opentrons/protocol_engine/commands/command_unions.py +37 -24
  89. opentrons/protocol_engine/commands/comment.py +5 -3
  90. opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
  91. opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
  92. opentrons/protocol_engine/commands/custom.py +5 -3
  93. opentrons/protocol_engine/commands/dispense.py +42 -20
  94. opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
  95. opentrons/protocol_engine/commands/drop_tip.py +70 -16
  96. opentrons/protocol_engine/commands/drop_tip_in_place.py +59 -13
  97. opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
  98. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
  99. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
  100. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
  101. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
  102. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
  103. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
  104. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
  105. opentrons/protocol_engine/commands/home.py +11 -5
  106. opentrons/protocol_engine/commands/liquid_probe.py +146 -88
  107. opentrons/protocol_engine/commands/load_labware.py +28 -5
  108. opentrons/protocol_engine/commands/load_liquid.py +18 -7
  109. opentrons/protocol_engine/commands/load_module.py +4 -6
  110. opentrons/protocol_engine/commands/load_pipette.py +18 -17
  111. opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
  112. opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
  113. opentrons/protocol_engine/commands/move_labware.py +155 -23
  114. opentrons/protocol_engine/commands/move_relative.py +15 -3
  115. opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
  116. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
  117. opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
  118. opentrons/protocol_engine/commands/move_to_well.py +37 -10
  119. opentrons/protocol_engine/commands/pick_up_tip.py +51 -30
  120. opentrons/protocol_engine/commands/pipetting_common.py +47 -16
  121. opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
  122. opentrons/protocol_engine/commands/reload_labware.py +13 -4
  123. opentrons/protocol_engine/commands/retract_axis.py +6 -3
  124. opentrons/protocol_engine/commands/save_position.py +2 -3
  125. opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
  126. opentrons/protocol_engine/commands/set_status_bar.py +5 -3
  127. opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
  128. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
  129. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
  130. opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
  131. opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
  132. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
  133. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
  134. opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
  135. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
  136. opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
  137. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
  138. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
  139. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
  140. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
  141. opentrons/protocol_engine/commands/touch_tip.py +19 -7
  142. opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
  143. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
  144. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
  145. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
  146. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  147. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  148. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +10 -4
  149. opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
  150. opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
  151. opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
  152. opentrons/protocol_engine/create_protocol_engine.py +60 -10
  153. opentrons/protocol_engine/engine_support.py +2 -1
  154. opentrons/protocol_engine/error_recovery_policy.py +14 -3
  155. opentrons/protocol_engine/errors/__init__.py +20 -0
  156. opentrons/protocol_engine/errors/error_occurrence.py +8 -3
  157. opentrons/protocol_engine/errors/exceptions.py +127 -2
  158. opentrons/protocol_engine/execution/__init__.py +2 -0
  159. opentrons/protocol_engine/execution/command_executor.py +22 -13
  160. opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
  161. opentrons/protocol_engine/execution/door_watcher.py +1 -1
  162. opentrons/protocol_engine/execution/equipment.py +2 -1
  163. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  164. opentrons/protocol_engine/execution/gantry_mover.py +4 -2
  165. opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
  166. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
  167. opentrons/protocol_engine/execution/labware_movement.py +73 -22
  168. opentrons/protocol_engine/execution/movement.py +17 -7
  169. opentrons/protocol_engine/execution/pipetting.py +7 -4
  170. opentrons/protocol_engine/execution/queue_worker.py +6 -2
  171. opentrons/protocol_engine/execution/run_control.py +1 -1
  172. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
  173. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +77 -43
  175. opentrons/protocol_engine/notes/__init__.py +14 -2
  176. opentrons/protocol_engine/notes/notes.py +18 -1
  177. opentrons/protocol_engine/plugins.py +1 -1
  178. opentrons/protocol_engine/protocol_engine.py +47 -31
  179. opentrons/protocol_engine/resources/__init__.py +2 -0
  180. opentrons/protocol_engine/resources/deck_data_provider.py +19 -5
  181. opentrons/protocol_engine/resources/file_provider.py +161 -0
  182. opentrons/protocol_engine/resources/fixture_validation.py +11 -1
  183. opentrons/protocol_engine/resources/labware_validation.py +10 -0
  184. opentrons/protocol_engine/state/__init__.py +0 -70
  185. opentrons/protocol_engine/state/addressable_areas.py +1 -1
  186. opentrons/protocol_engine/state/command_history.py +21 -2
  187. opentrons/protocol_engine/state/commands.py +110 -31
  188. opentrons/protocol_engine/state/files.py +59 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +440 -0
  190. opentrons/protocol_engine/state/geometry.py +445 -59
  191. opentrons/protocol_engine/state/labware.py +264 -84
  192. opentrons/protocol_engine/state/liquids.py +1 -1
  193. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +21 -3
  194. opentrons/protocol_engine/state/modules.py +145 -90
  195. opentrons/protocol_engine/state/motion.py +33 -14
  196. opentrons/protocol_engine/state/pipettes.py +157 -317
  197. opentrons/protocol_engine/state/state.py +30 -1
  198. opentrons/protocol_engine/state/state_summary.py +3 -0
  199. opentrons/protocol_engine/state/tips.py +69 -114
  200. opentrons/protocol_engine/state/update_types.py +424 -0
  201. opentrons/protocol_engine/state/wells.py +236 -0
  202. opentrons/protocol_engine/types.py +90 -0
  203. opentrons/protocol_reader/file_format_validator.py +83 -15
  204. opentrons/protocol_runner/json_translator.py +21 -5
  205. opentrons/protocol_runner/legacy_command_mapper.py +27 -6
  206. opentrons/protocol_runner/legacy_context_plugin.py +27 -71
  207. opentrons/protocol_runner/protocol_runner.py +6 -3
  208. opentrons/protocol_runner/run_orchestrator.py +41 -6
  209. opentrons/protocols/advanced_control/mix.py +3 -5
  210. opentrons/protocols/advanced_control/transfers.py +125 -56
  211. opentrons/protocols/api_support/constants.py +1 -1
  212. opentrons/protocols/api_support/definitions.py +1 -1
  213. opentrons/protocols/api_support/labware_like.py +4 -4
  214. opentrons/protocols/api_support/tip_tracker.py +2 -2
  215. opentrons/protocols/api_support/types.py +15 -2
  216. opentrons/protocols/api_support/util.py +30 -42
  217. opentrons/protocols/duration/errors.py +1 -1
  218. opentrons/protocols/duration/estimator.py +50 -29
  219. opentrons/protocols/execution/dev_types.py +2 -2
  220. opentrons/protocols/execution/execute_json_v4.py +15 -10
  221. opentrons/protocols/execution/execute_python.py +8 -3
  222. opentrons/protocols/geometry/planning.py +12 -12
  223. opentrons/protocols/labware.py +17 -33
  224. opentrons/protocols/parameters/csv_parameter_interface.py +3 -1
  225. opentrons/simulate.py +3 -3
  226. opentrons/types.py +30 -3
  227. opentrons/util/logging_config.py +34 -0
  228. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/METADATA +5 -4
  229. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/RECORD +235 -223
  230. opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
  231. opentrons/protocol_engine/commands/configuring_common.py +0 -26
  232. opentrons/protocol_runner/thread_async_queue.py +0 -174
  233. /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
  234. /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
  235. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/LICENSE +0 -0
  236. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/WHEEL +0 -0
  237. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.1.0a0.dist-info → opentrons-8.2.0.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,11 @@ from typing import (
9
9
  Callable,
10
10
  Generator,
11
11
  Iterator,
12
+ Iterable,
13
+ Sequence,
12
14
  Tuple,
15
+ TypedDict,
16
+ TypeAlias,
13
17
  TYPE_CHECKING,
14
18
  TypeVar,
15
19
  )
@@ -19,9 +23,22 @@ from opentrons.protocols.api_support.types import APIVersion
19
23
  from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
20
24
 
21
25
 
26
+ AdvancedLiquidHandling = Union[
27
+ Well,
28
+ types.Location,
29
+ Sequence[Union[Well, types.Location]],
30
+ Sequence[Sequence[Well]],
31
+ ]
32
+
33
+
34
+ class TransferStep(TypedDict):
35
+ method: str
36
+ args: Optional[List[Any]]
37
+ kwargs: Optional[Dict[Any, Any]]
38
+
39
+
22
40
  if TYPE_CHECKING:
23
41
  from opentrons.protocol_api import InstrumentContext
24
- from opentrons.protocols.execution.dev_types import Dictable
25
42
 
26
43
  _PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18)
27
44
  """The version after which partial tip support and nozzle maps were made available."""
@@ -335,6 +352,11 @@ class TransferOptions(NamedTuple):
335
352
  dispense: DispenseOpts = DispenseOpts()
336
353
 
337
354
 
355
+ FormatDictArgs: TypeAlias = Union[
356
+ PickUpTipOpts, MixOpts, BlowOutOpts, TouchTipOpts, AspirateOpts, DispenseOpts
357
+ ]
358
+
359
+
338
360
  TransferOptions.transfer.__doc__ = """
339
361
  Options pertaining to behavior of the transfer.
340
362
 
@@ -386,9 +408,9 @@ class TransferPlan:
386
408
 
387
409
  def __init__(
388
410
  self,
389
- volume,
390
- sources,
391
- dests,
411
+ volume: Union[float, Sequence[float]],
412
+ srcs: AdvancedLiquidHandling,
413
+ dsts: AdvancedLiquidHandling,
392
414
  # todo(mm, 2021-03-10):
393
415
  # Refactor to not need an InstrumentContext, so we can more
394
416
  # easily test this class's logic on its own.
@@ -414,6 +436,8 @@ class TransferPlan:
414
436
  # ii. if using single channel pipettes, flatten a multi-dimensional
415
437
  # list of Wells into a 1 dimensional list of Wells
416
438
  pipette_configuration_type = NozzleConfigurationType.FULL
439
+ normalized_sources: List[Union[Well, types.Location]]
440
+ normalized_dests: List[Union[Well, types.Location]]
417
441
  if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED:
418
442
  pipette_configuration_type = (
419
443
  self._instr._core.get_nozzle_map().configuration
@@ -422,24 +446,36 @@ class TransferPlan:
422
446
  self._instr.channels > 1
423
447
  and pipette_configuration_type == NozzleConfigurationType.FULL
424
448
  ):
425
- sources, dests = self._multichannel_transfer(sources, dests)
449
+ normalized_sources, normalized_dests = self._multichannel_transfer(
450
+ srcs, dsts
451
+ )
426
452
  else:
427
- if isinstance(sources, List) and isinstance(sources[0], List):
428
- # Source is a List[List[Well]]
429
- sources = [well for well_list in sources for well in well_list]
430
- elif isinstance(sources, Well) or isinstance(sources, types.Location):
431
- sources = [sources]
432
- if isinstance(dests, List) and isinstance(dests[0], List):
433
- # Dest is a List[List[Well]]
434
- dests = [well for well_list in dests for well in well_list]
435
- elif isinstance(dests, Well) or isinstance(dests, types.Location):
436
- dests = [dests]
437
-
438
- total_xfers = max(len(sources), len(dests))
453
+ if isinstance(srcs, List):
454
+ if isinstance(srcs[0], List):
455
+ # Source is a List[List[Well]]
456
+ normalized_sources = [
457
+ well for well_list in srcs for well in well_list
458
+ ]
459
+ else:
460
+ normalized_sources = srcs
461
+ elif isinstance(srcs, Well) or isinstance(srcs, types.Location):
462
+ normalized_sources = [srcs]
463
+ if isinstance(dsts, List):
464
+ if isinstance(dsts[0], List):
465
+ # Dest is a List[List[Well]]
466
+ normalized_dests = [
467
+ well for well_list in dsts for well in well_list
468
+ ]
469
+ else:
470
+ normalized_dests = dsts
471
+ elif isinstance(dsts, Well) or isinstance(dsts, types.Location):
472
+ normalized_dests = [dsts]
473
+
474
+ total_xfers = max(len(normalized_sources), len(normalized_dests))
439
475
 
440
476
  self._volumes = self._create_volume_list(volume, total_xfers)
441
- self._sources = sources
442
- self._dests = dests
477
+ self._sources = normalized_sources
478
+ self._dests = normalized_dests
443
479
  self._options = options or TransferOptions()
444
480
  self._strategy = self._options.transfer
445
481
  self._tip_opts = self._options.pick_up_tip
@@ -451,7 +487,7 @@ class TransferPlan:
451
487
 
452
488
  self._mode = TransferMode[mode.upper()]
453
489
 
454
- def __iter__(self):
490
+ def __iter__(self) -> Iterator[TransferStep]:
455
491
  if self._strategy.new_tip == types.TransferTipPolicy.ONCE:
456
492
  yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
457
493
  yield from {
@@ -465,7 +501,7 @@ class TransferPlan:
465
501
  else:
466
502
  yield self._format_dict("drop_tip")
467
503
 
468
- def _plan_transfer(self):
504
+ def _plan_transfer(self) -> Generator[TransferStep, None, None]:
469
505
  """
470
506
  * **Source/ Dest:** Multiple sources to multiple destinations.
471
507
  Src & dest should be equal length
@@ -532,7 +568,7 @@ class TransferPlan:
532
568
  def _extend_source_target_lists(
533
569
  sources: List[Union[Well, types.Location]],
534
570
  targets: List[Union[Well, types.Location]],
535
- ):
571
+ ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]:
536
572
  """Extend source or target list to match the length of the other"""
537
573
  if len(sources) < len(targets):
538
574
  if len(targets) % len(sources) != 0:
@@ -552,7 +588,7 @@ class TransferPlan:
552
588
  ]
553
589
  return sources, targets
554
590
 
555
- def _plan_distribute(self):
591
+ def _plan_distribute(self) -> Generator[TransferStep, None, None]:
556
592
  """
557
593
  * **Source/ Dest:** One source to many destinations
558
594
  * **Volume:** Single volume or List of volumes is acceptable. This list
@@ -617,7 +653,7 @@ class TransferPlan:
617
653
  if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
618
654
  yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
619
655
  while not done:
620
- asp_grouped: List[Tuple[float, Well]] = []
656
+ asp_grouped: List[Tuple[float, Well | types.Location]] = []
621
657
  try:
622
658
  while (
623
659
  sum(a[0] for a in asp_grouped)
@@ -641,6 +677,7 @@ class TransferPlan:
641
677
  self._sources[0],
642
678
  )
643
679
  for step in asp_grouped:
680
+
644
681
  yield from self._dispense_actions(
645
682
  vol=step[0],
646
683
  src=self._sources[0],
@@ -653,7 +690,7 @@ class TransferPlan:
653
690
 
654
691
  @staticmethod
655
692
  def _expand_for_volume_constraints(
656
- volumes: Iterator[float], targets: Iterator[Target], max_volume: float
693
+ volumes: Iterable[float], targets: Iterable[Target], max_volume: float
657
694
  ) -> Generator[Tuple[float, "Target"], None, None]:
658
695
  """Split a sequence of proposed transfers if necessary to keep each
659
696
  transfer under the given max volume.
@@ -672,7 +709,7 @@ class TransferPlan:
672
709
  yield volume, target
673
710
  yield volume, target
674
711
 
675
- def _plan_consolidate(self):
712
+ def _plan_consolidate(self) -> Generator[TransferStep, None, None]:
676
713
  """
677
714
  * **Source/ Dest:** Many sources to one destination
678
715
  * **Volume:** Single volume or List of volumes is acceptable. This list
@@ -728,7 +765,7 @@ class TransferPlan:
728
765
  yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
729
766
  done = False
730
767
  while not done:
731
- asp_grouped: List[Tuple[float, Well]] = []
768
+ asp_grouped: List[Tuple[float, Union[Well, types.Location]]] = []
732
769
  try:
733
770
  while (
734
771
  sum([a[0] for a in asp_grouped])
@@ -759,18 +796,28 @@ class TransferPlan:
759
796
  )
760
797
  yield from self._new_tip_action()
761
798
 
762
- def _aspirate_actions(self, vol, loc):
799
+ def _aspirate_actions(
800
+ self, vol: float, loc: Union[Well, types.Location]
801
+ ) -> Generator[TransferStep, None, None]:
763
802
  yield from self._before_aspirate(loc)
764
803
  yield self._format_dict("aspirate", [vol, loc, self._options.aspirate.rate])
765
804
  yield from self._after_aspirate()
766
805
 
767
- def _dispense_actions(self, vol, dest, src=None, is_disp_next=False):
806
+ def _dispense_actions(
807
+ self,
808
+ vol: float,
809
+ dest: Union[Well, types.Location],
810
+ src: Optional[Union[Well, types.Location]] = None,
811
+ is_disp_next: bool = False,
812
+ ) -> Generator[TransferStep, None, None]:
768
813
  if self._strategy.air_gap:
769
814
  vol += self._strategy.air_gap
770
815
  yield self._format_dict("dispense", [vol, dest, self._options.dispense.rate])
771
816
  yield from self._after_dispense(dest=dest, src=src, is_disp_next=is_disp_next)
772
817
 
773
- def _before_aspirate(self, loc):
818
+ def _before_aspirate(
819
+ self, loc: Union[Well, types.Location]
820
+ ) -> Generator[TransferStep, None, None]:
774
821
  if (
775
822
  self._strategy.mix_strategy == MixStrategy.BEFORE
776
823
  or self._strategy.mix_strategy == MixStrategy.BOTH
@@ -780,13 +827,33 @@ class TransferPlan:
780
827
  mix_before_opts["location"] = loc
781
828
  yield self._format_dict("mix", kwargs=mix_before_opts)
782
829
 
783
- def _after_aspirate(self):
830
+ def _after_aspirate(self) -> Generator[TransferStep, None, None]:
784
831
  if self._strategy.air_gap:
785
832
  yield self._format_dict("air_gap", [self._strategy.air_gap])
786
833
  if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
787
834
  yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
788
835
 
789
- def _after_dispense(self, dest, src, is_disp_next=False): # noqa: C901
836
+ def _after_dispense_trash(self) -> Generator[TransferStep, None, None]:
837
+ if isinstance(self._instr.trash_container, Labware):
838
+ yield self._format_dict(
839
+ "blow_out", [self._instr.trash_container.wells()[0]]
840
+ )
841
+ else:
842
+ yield self._format_dict("blow_out", [self._instr.trash_container])
843
+
844
+ def _after_dispense_helper(self) -> Generator[TransferStep, None, None]:
845
+ # Used by distribute
846
+ if self._strategy.air_gap:
847
+ yield self._format_dict("air_gap", [self._strategy.air_gap])
848
+ if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
849
+ yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
850
+
851
+ def _after_dispense(
852
+ self,
853
+ dest: Union[Well, types.Location],
854
+ src: Optional[Union[types.Location, Well]],
855
+ is_disp_next: bool = False,
856
+ ) -> Generator[TransferStep, None, None]:
790
857
  # This sequence of actions is subject to change
791
858
  if not is_disp_next:
792
859
  # If the next command is an aspirate, we are switching
@@ -813,21 +880,11 @@ class TransferPlan:
813
880
  self._strategy.blow_out_strategy == BlowOutStrategy.TRASH
814
881
  or self._strategy.disposal_volume
815
882
  ):
816
- if isinstance(self._instr.trash_container, Labware):
817
- yield self._format_dict(
818
- "blow_out", [self._instr.trash_container.wells()[0]]
819
- )
820
- else:
821
- yield self._format_dict("blow_out", [self._instr.trash_container])
822
-
883
+ yield from self._after_dispense_trash()
823
884
  else:
824
- # Used by distribute
825
- if self._strategy.air_gap:
826
- yield self._format_dict("air_gap", [self._strategy.air_gap])
827
- if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
828
- yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
885
+ yield from self._after_dispense_helper()
829
886
 
830
- def _new_tip_action(self):
887
+ def _new_tip_action(self) -> Generator[TransferStep, None, None]:
831
888
  if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
832
889
  if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN:
833
890
  yield self._format_dict("return_tip")
@@ -837,9 +894,9 @@ class TransferPlan:
837
894
  def _format_dict(
838
895
  self,
839
896
  method: str,
840
- args: List = None,
841
- kwargs: Union["Dictable", Dict[str, Any]] = None,
842
- ):
897
+ args: Optional[List[Any]] = None,
898
+ kwargs: Optional[Union[Dict[Any, Any], FormatDictArgs]] = None,
899
+ ) -> TransferStep:
843
900
  if kwargs:
844
901
  if isinstance(kwargs, Dict):
845
902
  params = {key: val for key, val in kwargs.items() if val}
@@ -851,9 +908,11 @@ class TransferPlan:
851
908
  args = []
852
909
  return {"method": method, "args": args, "kwargs": params}
853
910
 
854
- def _create_volume_list(self, volume, total_xfers):
911
+ def _create_volume_list(
912
+ self, volume: Union[Union[float, int], Sequence[float]], total_xfers: int
913
+ ) -> List[float]:
855
914
  if isinstance(volume, (float, int)):
856
- return [volume] * total_xfers
915
+ return [float(volume)] * total_xfers
857
916
  elif isinstance(volume, tuple):
858
917
  return self._create_volume_gradient(
859
918
  volume[0], volume[-1], total_xfers, self._strategy.gradient_function
@@ -870,11 +929,17 @@ class TransferPlan:
870
929
  )
871
930
  return volume
872
931
 
873
- def _create_volume_gradient(self, min_v, max_v, total, gradient=None):
932
+ def _create_volume_gradient(
933
+ self,
934
+ min_v: float,
935
+ max_v: float,
936
+ total: int,
937
+ gradient: Optional[Callable[[float], float]] = None,
938
+ ) -> List[float]:
874
939
 
875
940
  diff_vol = max_v - min_v
876
941
 
877
- def _map_volume(i):
942
+ def _map_volume(i: int) -> float:
878
943
  nonlocal diff_vol, total
879
944
  rel_x = i / (total - 1)
880
945
  rel_y = gradient(rel_x) if gradient else rel_x
@@ -884,7 +949,7 @@ class TransferPlan:
884
949
 
885
950
  def _check_valid_volume_parameters(
886
951
  self, disposal_volume: float, air_gap: float, max_volume: float
887
- ):
952
+ ) -> None:
888
953
  if air_gap >= max_volume:
889
954
  raise ValueError(
890
955
  "The air gap must be less than the maximum volume of the pipette"
@@ -898,7 +963,9 @@ class TransferPlan:
898
963
  "The sum of the air gap and disposal volume must be less than the maximum volume of the pipette"
899
964
  )
900
965
 
901
- def _check_valid_well_list(self, well_list, id, old_well_list):
966
+ def _check_valid_well_list(
967
+ self, well_list: List[Any], id: str, old_well_list: List[Any]
968
+ ) -> None:
902
969
  if self._api_version >= APIVersion(2, 2) and len(well_list) < 1:
903
970
  raise RuntimeError(
904
971
  f"Invalid {id} for multichannel transfer: {old_well_list}"
@@ -914,7 +981,9 @@ class TransferPlan:
914
981
  return True
915
982
  return False
916
983
 
917
- def _multichannel_transfer(self, s, d):
984
+ def _multichannel_transfer(
985
+ self, s: AdvancedLiquidHandling, d: AdvancedLiquidHandling
986
+ ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]:
918
987
  # TODO: add a check for container being multi-channel compatible?
919
988
  # Helper function for multi-channel use-case
920
989
  assert (
@@ -958,7 +1027,7 @@ class TransferPlan:
958
1027
  self._check_valid_well_list(new_dst, "target", d)
959
1028
  return new_src, new_dst
960
1029
 
961
- def _is_valid_row(self, well: Union[Well, types.Location]):
1030
+ def _is_valid_row(self, well: Union[Well, types.Location]) -> bool:
962
1031
  if isinstance(well, types.Location):
963
1032
  test_well = well.labware.as_well()
964
1033
  else:
@@ -4,5 +4,5 @@ from opentrons.config import get_opentrons_path
4
4
 
5
5
  OPENTRONS_NAMESPACE = "opentrons"
6
6
  CUSTOM_NAMESPACE = "custom_beta"
7
- STANDARD_DEFS_PATH = Path("labware/definitions/2")
7
+ STANDARD_DEFS_PATH = Path("labware/definitions")
8
8
  USER_DEFS_PATH = get_opentrons_path("labware_user_definitions_dir_v2")
@@ -1,6 +1,6 @@
1
1
  from .types import APIVersion
2
2
 
3
- MAX_SUPPORTED_VERSION = APIVersion(2, 20)
3
+ MAX_SUPPORTED_VERSION = APIVersion(2, 21)
4
4
  """The maximum supported protocol API version in this release."""
5
5
 
6
6
  MIN_SUPPORTED_VERSION = APIVersion(2, 0)
@@ -1,5 +1,5 @@
1
1
  from enum import Enum, auto
2
- from typing import TYPE_CHECKING, Optional, Union, cast, Tuple, List, Set
2
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast, Tuple, List, Set
3
3
 
4
4
  if TYPE_CHECKING:
5
5
  from opentrons.protocol_api.labware import Labware, Well
@@ -161,7 +161,7 @@ class LabwareLike:
161
161
  seen: Set[LabwareLike] = set()
162
162
 
163
163
  # internal function to have the cycle detector different per call
164
- def _fp_recurse(location: LabwareLike):
164
+ def _fp_recurse(location: LabwareLike) -> Optional[str]:
165
165
  if location in seen:
166
166
  raise RuntimeError("Cycle in labware parent")
167
167
  seen.add(location)
@@ -222,12 +222,12 @@ class LabwareLike:
222
222
  def __repr__(self) -> str:
223
223
  return str(self)
224
224
 
225
- def __eq__(self, other):
225
+ def __eq__(self, other: Any) -> bool:
226
226
  return (
227
227
  other is not None
228
228
  and isinstance(other, LabwareLike)
229
229
  and self.object == other.object
230
230
  )
231
231
 
232
- def __hash__(self):
232
+ def __hash__(self) -> int:
233
233
  return id(self)
@@ -65,7 +65,7 @@ class TipTracker:
65
65
  start_well: AbstractWellCore,
66
66
  num_channels: int = 1,
67
67
  fail_if_full: bool = False,
68
- ):
68
+ ) -> None:
69
69
  """
70
70
  Removes tips from the tip tracker.
71
71
 
@@ -142,7 +142,7 @@ class TipTracker:
142
142
  except IndexError:
143
143
  return None
144
144
 
145
- def return_tips(self, start_well: AbstractWellCore, num_channels: int = 1):
145
+ def return_tips(self, start_well: AbstractWellCore, num_channels: int = 1) -> None:
146
146
  """
147
147
  Re-adds tips to the tip tracker
148
148
 
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import NamedTuple
2
+ from typing import NamedTuple, TypedDict
3
3
 
4
4
 
5
5
  class APIVersion(NamedTuple):
@@ -15,5 +15,18 @@ class APIVersion(NamedTuple):
15
15
 
16
16
  return cls(major=intparts[0], minor=intparts[1])
17
17
 
18
- def __str__(self):
18
+ def __str__(self) -> str:
19
19
  return f"{self.major}.{self.minor}"
20
+
21
+
22
+ class ThermocyclerStepBase(TypedDict):
23
+ """Required elements of a thermocycler step: the temperature."""
24
+
25
+ temperature: float
26
+
27
+
28
+ class ThermocyclerStep(ThermocyclerStepBase, total=False):
29
+ """Optional elements of a thermocycler step: the hold time. One of these must be present."""
30
+
31
+ hold_time_seconds: float
32
+ hold_time_minutes: float
@@ -11,10 +11,13 @@ from typing import (
11
11
  Callable,
12
12
  Dict,
13
13
  List,
14
+ Iterator,
14
15
  Optional,
15
16
  TypeVar,
16
17
  Union,
17
18
  cast,
19
+ KeysView,
20
+ ItemsView,
18
21
  )
19
22
 
20
23
  from opentrons import types as top_types
@@ -180,7 +183,7 @@ class FlowRates:
180
183
  return self._instr.get_aspirate_flow_rate()
181
184
 
182
185
  @aspirate.setter
183
- def aspirate(self, new_val: float):
186
+ def aspirate(self, new_val: float) -> None:
184
187
  self._instr.set_flow_rate(
185
188
  aspirate=_assert_gzero(
186
189
  new_val, "flow rate should be a numerical value in ul/s"
@@ -192,7 +195,7 @@ class FlowRates:
192
195
  return self._instr.get_dispense_flow_rate()
193
196
 
194
197
  @dispense.setter
195
- def dispense(self, new_val: float):
198
+ def dispense(self, new_val: float) -> None:
196
199
  self._instr.set_flow_rate(
197
200
  dispense=_assert_gzero(
198
201
  new_val, "flow rate should be a numerical value in ul/s"
@@ -204,7 +207,7 @@ class FlowRates:
204
207
  return self._instr.get_blow_out_flow_rate()
205
208
 
206
209
  @blow_out.setter
207
- def blow_out(self, new_val: float):
210
+ def blow_out(self, new_val: float) -> None:
208
211
  self._instr.set_flow_rate(
209
212
  blow_out=_assert_gzero(
210
213
  new_val, "flow rate should be a numerical value in ul/s"
@@ -247,7 +250,7 @@ class PlungerSpeeds:
247
250
  return self._instr.get_hardware_state()["aspirate_speed"]
248
251
 
249
252
  @aspirate.setter
250
- def aspirate(self, new_val: float):
253
+ def aspirate(self, new_val: float) -> None:
251
254
  self._instr.set_pipette_speed(
252
255
  aspirate=_assert_gzero(new_val, "speed should be a numerical value in mm/s")
253
256
  )
@@ -257,7 +260,7 @@ class PlungerSpeeds:
257
260
  return self._instr.get_hardware_state()["dispense_speed"]
258
261
 
259
262
  @dispense.setter
260
- def dispense(self, new_val: float):
263
+ def dispense(self, new_val: float) -> None:
261
264
  self._instr.set_pipette_speed(
262
265
  dispense=_assert_gzero(new_val, "speed should be a numerical value in mm/s")
263
266
  )
@@ -267,13 +270,13 @@ class PlungerSpeeds:
267
270
  return self._instr.get_hardware_state()["blow_out_speed"]
268
271
 
269
272
  @blow_out.setter
270
- def blow_out(self, new_val: float):
273
+ def blow_out(self, new_val: float) -> None:
271
274
  self._instr.set_pipette_speed(
272
275
  blow_out=_assert_gzero(new_val, "speed should be a numerical value in mm/s")
273
276
  )
274
277
 
275
278
 
276
- class AxisMaxSpeeds(UserDict):
279
+ class AxisMaxSpeeds(UserDict[Union[str, Axis], float]):
277
280
  """Special mapping allowing internal storage by Mount enums and
278
281
  user access by string
279
282
  """
@@ -283,12 +286,12 @@ class AxisMaxSpeeds(UserDict):
283
286
  super().__init__()
284
287
  self._robot_type = robot_type
285
288
 
286
- def __getitem__(self, key: Union[str, Axis]):
289
+ def __getitem__(self, key: Union[str, Axis]) -> float:
287
290
  checked_key = AxisMaxSpeeds._verify_key(key)
288
291
  return self.data[checked_key]
289
292
 
290
293
  @staticmethod
291
- def _verify_key(key: Any) -> Axis:
294
+ def _verify_key(key: object) -> Axis:
292
295
  if isinstance(key, Axis):
293
296
  checked_key: Optional[Axis] = key
294
297
  elif isinstance(key, str):
@@ -299,47 +302,40 @@ class AxisMaxSpeeds(UserDict):
299
302
  raise KeyError(key)
300
303
  return checked_key
301
304
 
302
- def __setitem__(self, key: Any, value: Any):
305
+ def __setitem__(self, key: object, value: object) -> None:
306
+
307
+ checked_key = AxisMaxSpeeds._verify_key(key)
303
308
  if value is None:
304
- del self[key]
309
+ del self[checked_key]
305
310
  return
306
311
 
307
- checked_key = AxisMaxSpeeds._verify_key(key)
308
312
  checked_val = _assert_gzero(
309
313
  value, "max speeds should be numerical values in mm/s"
310
314
  )
311
315
 
312
316
  self.data[checked_key] = checked_val
313
317
 
314
- def __delitem__(self, key: Union[str, Axis]):
318
+ def _axis_to_string(self, axis: Union[str, Axis]) -> str:
319
+ if isinstance(axis, str):
320
+ return axis
321
+ if self._robot_type == "OT-3 Standard":
322
+ return axis.name
323
+ return ot2_axis_to_string(axis)
324
+
325
+ def __delitem__(self, key: Union[str, Axis]) -> None:
315
326
  checked_key = AxisMaxSpeeds._verify_key(key)
316
327
  del self.data[checked_key]
317
328
 
318
- def __iter__(self):
329
+ def __iter__(self) -> Iterator[str]:
319
330
  """keys() and dict iteration return string keys"""
320
- string_keys = (
321
- k.name if self._robot_type == "OT-3 Standard" else ot2_axis_to_string(k)
322
- for k in self.data.keys()
323
- )
331
+ string_keys = (self._axis_to_string(k) for k in self.data.keys())
324
332
  return string_keys
325
333
 
326
- def keys(self):
327
- string_keys = (
328
- k.name if self._robot_type == "OT-3 Standard" else ot2_axis_to_string(k)
329
- for k in self.data.keys()
330
- )
331
- return string_keys
334
+ def keys(self) -> KeysView[str]:
335
+ return ({self._axis_to_string(k): v for k, v in self.data.items()}).keys()
332
336
 
333
- def items(self):
334
- return (
335
- (
336
- k.name
337
- if self._robot_type == "OT-3 Standard"
338
- else ot2_axis_to_string(k),
339
- v,
340
- )
341
- for k, v in self.data.items()
342
- )
337
+ def items(self) -> ItemsView[str, float]:
338
+ return ({self._axis_to_string(k): v for k, v in self.data.items()}).items()
343
339
 
344
340
 
345
341
  def clamp_value(
@@ -395,11 +391,3 @@ def requires_version(major: int, minor: int) -> Callable[[FuncT], FuncT]:
395
391
  return cast(FuncT, _check_version_wrapper)
396
392
 
397
393
  return _set_version
398
-
399
-
400
- class ModifiedList(list):
401
- def __contains__(self, item):
402
- for name in self:
403
- if name == item.replace("-", "_").lower():
404
- return True
405
- return False
@@ -1,5 +1,5 @@
1
1
  class DurationEstimatorException(Exception):
2
- def __init__(self, message):
2
+ def __init__(self, message: str) -> None:
3
3
  super().__init__(
4
4
  f"Error encountered while estimating protocol duration: '{message}'"
5
5
  )