opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.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.

Potentially problematic release.


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

Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,56 @@
1
+ """Common functions between v1 transfer and liquid-class-based transfer."""
2
+ import enum
3
+ from typing import Iterable, Generator, Tuple, TypeVar, Literal
4
+
5
+
6
+ class TransferTipPolicyV2(enum.Enum):
7
+ ONCE = "once"
8
+ NEVER = "never"
9
+ ALWAYS = "always"
10
+ PER_SOURCE = "per source"
11
+
12
+
13
+ TransferTipPolicyV2Type = Literal["once", "always", "per source", "never"]
14
+
15
+ Target = TypeVar("Target")
16
+
17
+
18
+ def check_valid_volume_parameters(
19
+ disposal_volume: float, air_gap: float, max_volume: float
20
+ ) -> None:
21
+ if air_gap >= max_volume:
22
+ raise ValueError(
23
+ "The air gap must be less than the maximum volume of the pipette"
24
+ )
25
+ elif disposal_volume >= max_volume:
26
+ raise ValueError(
27
+ "The disposal volume must be less than the maximum volume of the pipette"
28
+ )
29
+ elif disposal_volume + air_gap >= max_volume:
30
+ raise ValueError(
31
+ "The sum of the air gap and disposal volume must be less than"
32
+ " the maximum volume of the pipette"
33
+ )
34
+
35
+
36
+ def expand_for_volume_constraints(
37
+ volumes: Iterable[float],
38
+ targets: Iterable[Target],
39
+ max_volume: float,
40
+ ) -> Generator[Tuple[float, "Target"], None, None]:
41
+ """Split a sequence of proposed transfers if necessary to keep each
42
+ transfer under the given max volume.
43
+ """
44
+ # A final defense against an infinite loop.
45
+ # Raising a proper exception with a helpful message is left to calling code,
46
+ # because it has more context about what the user is trying to do.
47
+ assert max_volume > 0
48
+ for volume, target in zip(volumes, targets):
49
+ while volume > max_volume * 2:
50
+ yield max_volume, target
51
+ volume -= max_volume
52
+
53
+ if volume > max_volume:
54
+ volume /= 2
55
+ yield volume, target
56
+ yield volume, target
@@ -9,19 +9,18 @@ from typing import (
9
9
  Callable,
10
10
  Generator,
11
11
  Iterator,
12
- Iterable,
13
12
  Sequence,
14
13
  Tuple,
15
14
  TypedDict,
16
15
  TypeAlias,
17
16
  TYPE_CHECKING,
18
- TypeVar,
19
17
  )
20
18
  from opentrons.protocol_api.labware import Labware, Well
21
19
  from opentrons import types
22
20
  from opentrons.protocols.api_support.types import APIVersion
23
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
24
21
 
22
+ from . import common as tx_commons
23
+ from ..common import Mix, MixOpts, MixStrategy
25
24
 
26
25
  AdvancedLiquidHandling = Union[
27
26
  Well,
@@ -44,13 +43,6 @@ _PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18)
44
43
  """The version after which partial tip support and nozzle maps were made available."""
45
44
 
46
45
 
47
- class MixStrategy(enum.Enum):
48
- BOTH = enum.auto()
49
- BEFORE = enum.auto()
50
- AFTER = enum.auto()
51
- NEVER = enum.auto()
52
-
53
-
54
46
  class DropTipStrategy(enum.Enum):
55
47
  TRASH = enum.auto()
56
48
  RETURN = enum.auto()
@@ -239,34 +231,6 @@ PickUpTipOpts.presses.__doc__ = ":py:class:`int`"
239
231
  PickUpTipOpts.increment.__doc__ = ":py:class:`int`"
240
232
 
241
233
 
242
- class MixOpts(NamedTuple):
243
- """
244
- Options to customize behavior of mix.
245
-
246
- These options will be passed to
247
- :py:meth:`InstrumentContext.mix` when it is called during the
248
- transfer.
249
- """
250
-
251
- repetitions: Optional[int] = None
252
- volume: Optional[float] = None
253
- rate: Optional[float] = None
254
-
255
-
256
- MixOpts.repetitions.__doc__ = ":py:class:`int`"
257
- MixOpts.volume.__doc__ = ":py:class:`float`"
258
- MixOpts.rate.__doc__ = ":py:class:`float`"
259
-
260
-
261
- class Mix(NamedTuple):
262
- """
263
- Options to control mix behavior before aspirate and after dispense.
264
- """
265
-
266
- mix_before: MixOpts = MixOpts()
267
- mix_after: MixOpts = MixOpts()
268
-
269
-
270
234
  Mix.mix_before.__doc__ = """
271
235
  Options applied to mix before aspirate.
272
236
  See :py:class:`.Mix.MixOpts`.
@@ -435,7 +399,7 @@ class TransferPlan:
435
399
  # then avoid iterating through its Wells.
436
400
  # ii. if using single channel pipettes, flatten a multi-dimensional
437
401
  # list of Wells into a 1 dimensional list of Wells
438
- pipette_configuration_type = NozzleConfigurationType.FULL
402
+ pipette_configuration_type = types.NozzleConfigurationType.FULL
439
403
  normalized_sources: List[Union[Well, types.Location]]
440
404
  normalized_dests: List[Union[Well, types.Location]]
441
405
  if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED:
@@ -444,7 +408,7 @@ class TransferPlan:
444
408
  )
445
409
  if (
446
410
  self._instr.channels > 1
447
- and pipette_configuration_type == NozzleConfigurationType.FULL
411
+ and pipette_configuration_type == types.NozzleConfigurationType.FULL
448
412
  ):
449
413
  normalized_sources, normalized_dests = self._multichannel_transfer(
450
414
  srcs, dsts
@@ -534,12 +498,12 @@ class TransferPlan:
534
498
  """
535
499
  # reform source target lists
536
500
  sources, dests = self._extend_source_target_lists(self._sources, self._dests)
537
- self._check_valid_volume_parameters(
501
+ tx_commons.check_valid_volume_parameters(
538
502
  disposal_volume=self._strategy.disposal_volume,
539
503
  air_gap=self._strategy.air_gap,
540
504
  max_volume=self._instr.max_volume,
541
505
  )
542
- plan_iter = self._expand_for_volume_constraints(
506
+ plan_iter = tx_commons.expand_for_volume_constraints(
543
507
  self._volumes,
544
508
  zip(sources, dests),
545
509
  self._instr.max_volume
@@ -626,7 +590,7 @@ class TransferPlan:
626
590
 
627
591
  """
628
592
 
629
- self._check_valid_volume_parameters(
593
+ tx_commons.check_valid_volume_parameters(
630
594
  disposal_volume=self._strategy.disposal_volume,
631
595
  air_gap=self._strategy.air_gap,
632
596
  max_volume=self._instr.max_volume,
@@ -637,7 +601,7 @@ class TransferPlan:
637
601
  # recommend users to specify a disposal vol when using distribute.
638
602
  # First method keeps distribute consistent with current behavior while
639
603
  # the other maintains consistency in default behaviors of all functions
640
- plan_iter = self._expand_for_volume_constraints(
604
+ plan_iter = tx_commons.expand_for_volume_constraints(
641
605
  self._volumes,
642
606
  self._dests,
643
607
  # todo(mm, 2021-03-09): Is it right for this to be
@@ -686,29 +650,6 @@ class TransferPlan:
686
650
  )
687
651
  yield from self._new_tip_action()
688
652
 
689
- Target = TypeVar("Target")
690
-
691
- @staticmethod
692
- def _expand_for_volume_constraints(
693
- volumes: Iterable[float], targets: Iterable[Target], max_volume: float
694
- ) -> Generator[Tuple[float, "Target"], None, None]:
695
- """Split a sequence of proposed transfers if necessary to keep each
696
- transfer under the given max volume.
697
- """
698
- # A final defense against an infinite loop.
699
- # Raising a proper exception with a helpful message is left to calling code,
700
- # because it has more context about what the user is trying to do.
701
- assert max_volume > 0
702
- for volume, target in zip(volumes, targets):
703
- while volume > max_volume * 2:
704
- yield max_volume, target
705
- volume -= max_volume
706
-
707
- if volume > max_volume:
708
- volume /= 2
709
- yield volume, target
710
- yield volume, target
711
-
712
653
  def _plan_consolidate(self) -> Generator[TransferStep, None, None]:
713
654
  """
714
655
  * **Source/ Dest:** Many sources to one destination
@@ -752,7 +693,7 @@ class TransferPlan:
752
693
  # air_gap=self._strategy.air_gap,
753
694
  # max_volume=self._instr.max_volume,
754
695
  # )
755
- plan_iter = self._expand_for_volume_constraints(
696
+ plan_iter = tx_commons.expand_for_volume_constraints(
756
697
  # todo(mm, 2021-03-09): Is it right to use _instr.max_volume here?
757
698
  # Why don't we account for tip max volume, disposal volume, or air
758
699
  # gap?
@@ -929,8 +870,8 @@ class TransferPlan:
929
870
  )
930
871
  return volume
931
872
 
873
+ @staticmethod
932
874
  def _create_volume_gradient(
933
- self,
934
875
  min_v: float,
935
876
  max_v: float,
936
877
  total: int,
@@ -947,22 +888,6 @@ class TransferPlan:
947
888
 
948
889
  return [_map_volume(i) for i in range(total)]
949
890
 
950
- def _check_valid_volume_parameters(
951
- self, disposal_volume: float, air_gap: float, max_volume: float
952
- ) -> None:
953
- if air_gap >= max_volume:
954
- raise ValueError(
955
- "The air gap must be less than the maximum volume of the pipette"
956
- )
957
- elif disposal_volume >= max_volume:
958
- raise ValueError(
959
- "The disposal volume must be less than the maximum volume of the pipette"
960
- )
961
- elif disposal_volume + air_gap >= max_volume:
962
- raise ValueError(
963
- "The sum of the air gap and disposal volume must be less than the maximum volume of the pipette"
964
- )
965
-
966
891
  def _check_valid_well_list(
967
892
  self, well_list: List[Any], id: str, old_well_list: List[Any]
968
893
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  from .types import APIVersion
2
2
 
3
- MAX_SUPPORTED_VERSION = APIVersion(2, 21)
3
+ MAX_SUPPORTED_VERSION = APIVersion(2, 22)
4
4
  """The maximum supported protocol API version in this release."""
5
5
 
6
6
  MIN_SUPPORTED_VERSION = APIVersion(2, 0)
@@ -73,7 +73,7 @@ def tip_length_for(
73
73
 
74
74
 
75
75
  VALID_PIP_TIPRACK_VOL = {
76
- "FLEX": {"p50": [50], "p1000": [50, 200, 1000]},
76
+ "FLEX": {"p50": [50], "p200": [50, 200], "p1000": [50, 200, 1000]},
77
77
  "OT2": {
78
78
  "p10": [10, 20],
79
79
  "p20": [10, 20],
@@ -391,3 +391,13 @@ def requires_version(major: int, minor: int) -> Callable[[FuncT], FuncT]:
391
391
  return cast(FuncT, _check_version_wrapper)
392
392
 
393
393
  return _set_version
394
+
395
+
396
+ class ModifiedList(list[str]):
397
+ def __contains__(self, item: object) -> bool:
398
+ if not isinstance(item, str):
399
+ return False
400
+ for name in self:
401
+ if name == item.replace("-", "_").lower():
402
+ return True
403
+ return False
@@ -2,13 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  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
7
+ from typing import Any, AnyStr, Dict, Optional, Union, List, Sequence, Literal
8
8
 
9
9
  import jsonschema # type: ignore
10
10
 
11
11
  from opentrons_shared_data import load_shared_data, get_shared_data_root
12
+ from opentrons.protocols.api_support.util import ModifiedList
12
13
  from opentrons.protocols.api_support.constants import (
13
14
  OPENTRONS_NAMESPACE,
14
15
  CUSTOM_NAMESPACE,
@@ -16,10 +17,30 @@ from opentrons.protocols.api_support.constants import (
16
17
  USER_DEFS_PATH,
17
18
  )
18
19
  from opentrons_shared_data.labware.types import LabwareDefinition
20
+ from opentrons_shared_data.errors.exceptions import InvalidProtocolData
19
21
 
20
22
 
21
23
  MODULE_LOG = logging.getLogger(__name__)
22
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
+
23
44
 
24
45
  def get_labware_definition(
25
46
  load_name: str,
@@ -61,6 +82,29 @@ def get_labware_definition(
61
82
  return _get_standard_labware_definition(load_name, namespace, version)
62
83
 
63
84
 
85
+ def get_all_labware_definitions(schema_version: str = "2") -> List[str]:
86
+ """
87
+ Return a list of standard and custom labware definitions with load_name +
88
+ name_space + version existing on the robot
89
+ """
90
+ labware_list = ModifiedList()
91
+
92
+ def _check_for_subdirectories(path: Union[str, Path, os.DirEntry[str]]) -> None:
93
+ with os.scandir(path) as top_path:
94
+ for sub_dir in top_path:
95
+ if sub_dir.is_dir():
96
+ labware_list.append(sub_dir.name)
97
+
98
+ # check for standard labware
99
+ _check_for_subdirectories(
100
+ get_shared_data_root() / STANDARD_DEFS_PATH / schema_version
101
+ )
102
+ # check for custom labware
103
+ for namespace in os.scandir(USER_DEFS_PATH):
104
+ _check_for_subdirectories(namespace)
105
+ return labware_list
106
+
107
+
64
108
  def save_definition(
65
109
  labware_def: LabwareDefinition, force: bool = False, location: Optional[Path] = None
66
110
  ) -> None:
@@ -102,7 +146,7 @@ def save_definition(
102
146
  json.dump(labware_def, f)
103
147
 
104
148
 
105
- def verify_definition(
149
+ def verify_definition( # noqa: C901
106
150
  contents: Union[AnyStr, LabwareDefinition, Dict[str, Any]]
107
151
  ) -> LabwareDefinition:
108
152
  """Verify that an input string is a labware definition and return it.
@@ -114,14 +158,33 @@ def verify_definition(
114
158
  :raises jsonschema.ValidationError: If the definition is not valid.
115
159
  :returns: The parsed definition
116
160
  """
117
- schema_body = load_shared_data("labware/schemas/2.json").decode("utf-8")
118
- labware_schema_v2 = json.loads(schema_body)
161
+ schemata_by_version = {
162
+ 2: json.loads(load_shared_data("labware/schemas/2.json").decode("utf-8")),
163
+ 3: json.loads(load_shared_data("labware/schemas/3.json").decode("utf-8")),
164
+ }
119
165
 
120
166
  if isinstance(contents, dict):
121
167
  to_return = contents
122
168
  else:
123
- to_return = json.loads(contents)
124
- jsonschema.validate(to_return, labware_schema_v2)
169
+ try:
170
+ to_return = json.loads(contents)
171
+ except json.JSONDecodeError as e:
172
+ raise NotALabwareError("invalid-json", [e]) from e
173
+ try:
174
+ schema_version = to_return["schemaVersion"]
175
+ except KeyError as e:
176
+ raise NotALabwareError("no-schema-id", [e]) from e
177
+
178
+ try:
179
+ schema = schemata_by_version[schema_version]
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
187
+
125
188
  # we can type ignore this because if it passes the jsonschema it has
126
189
  # the correct structure
127
190
  return to_return # type: ignore[return-value]
@@ -176,7 +239,6 @@ def _get_labware_definition_from_bundle(
176
239
  def _get_standard_labware_definition(
177
240
  load_name: str, namespace: Optional[str] = None, version: Optional[int] = None
178
241
  ) -> LabwareDefinition:
179
-
180
242
  if version is None:
181
243
  checked_version = 1
182
244
  else:
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  from typing import Any, Dict, List, Optional, Union
11
11
 
12
- from pydantic import BaseModel, Extra, Field
12
+ from pydantic import ConfigDict, BaseModel, Field
13
13
  from typing_extensions import Literal
14
14
 
15
15
  from opentrons_shared_data.labware.labware_definition import LabwareDefinition
@@ -75,8 +75,7 @@ class Metadata(BaseModel):
75
75
  Optional metadata about the protocol
76
76
  """
77
77
 
78
- class Config:
79
- extra = Extra.allow
78
+ model_config = ConfigDict(extra="allow")
80
79
 
81
80
  protocolName: Optional[str] = Field(
82
81
  None, description="A short, human-readable name for the protocol"
@@ -574,8 +573,7 @@ class Pipettes(BaseModel):
574
573
  Fields describing an individual pipette
575
574
  """
576
575
 
577
- class Config:
578
- extra = Extra.allow
576
+ model_config = ConfigDict(extra="allow")
579
577
 
580
578
  mount: Literal["left", "right"] = Field(
581
579
  ..., description="Where the pipette is mounted"
@@ -592,8 +590,7 @@ class Labware(BaseModel):
592
590
  Fields describing a single labware on the deck
593
591
  """
594
592
 
595
- class Config:
596
- extra = Extra.allow
593
+ model_config = ConfigDict(extra="allow")
597
594
 
598
595
  slot: str = Field(
599
596
  ...,
@@ -616,8 +613,7 @@ class Modules(BaseModel):
616
613
  Fields describing a single module on the deck
617
614
  """
618
615
 
619
- class Config:
620
- extra = Extra.allow
616
+ model_config = ConfigDict(extra="allow")
621
617
 
622
618
  slot: str = Field(
623
619
  ...,
opentrons/simulate.py CHANGED
@@ -829,7 +829,9 @@ def _create_live_context_pe(
829
829
  # Non-async would use call_soon_threadsafe(), which makes the waiting harder.
830
830
  async def add_all_extra_labware() -> None:
831
831
  for labware_definition_dict in extra_labware.values():
832
- labware_definition = LabwareDefinition.parse_obj(labware_definition_dict)
832
+ labware_definition = LabwareDefinition.model_validate(
833
+ labware_definition_dict
834
+ )
833
835
  pe.add_labware_definition(labware_definition)
834
836
 
835
837
  # Add extra_labware to ProtocolEngine, being careful not to modify ProtocolEngine from this
opentrons/types.py CHANGED
@@ -1,7 +1,17 @@
1
1
  from __future__ import annotations
2
2
  import enum
3
3
  from math import sqrt, isclose
4
- from typing import TYPE_CHECKING, Any, NamedTuple, Iterator, Union, List, Optional
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Any,
7
+ NamedTuple,
8
+ Iterator,
9
+ Union,
10
+ List,
11
+ Optional,
12
+ Protocol,
13
+ Dict,
14
+ )
5
15
 
6
16
  from opentrons_shared_data.robot.types import RobotType
7
17
 
@@ -150,7 +160,7 @@ class Location:
150
160
  point, labware = location
151
161
  some_function_taking_both(*location)
152
162
  """
153
- return iter((self._point, self._labware)) # type: ignore [arg-type]
163
+ return iter((self._point, self._labware))
154
164
 
155
165
  def __eq__(self, other: object) -> bool:
156
166
  return (
@@ -253,6 +263,75 @@ class OT3MountType(str, enum.Enum):
253
263
  GRIPPER = "gripper"
254
264
 
255
265
 
266
+ class AxisType(enum.Enum):
267
+ X = "X" # gantry
268
+ Y = "Y"
269
+ Z_L = "Z_L" # left pipette mount Z
270
+ Z_R = "Z_R" # right pipette mount Z
271
+ Z_G = "Z_G" # gripper mount Z
272
+ P_L = "P_L" # left pipette plunger
273
+ P_R = "P_R" # right pipette plunger
274
+ Q = "Q" # hi-throughput pipette tiprack grab
275
+ G = "G" # gripper grab
276
+
277
+ @classmethod
278
+ def axis_for_mount(cls, mount: Mount) -> "AxisType":
279
+ map_axis_to_mount = {
280
+ Mount.LEFT: cls.Z_L,
281
+ Mount.RIGHT: cls.Z_R,
282
+ Mount.EXTENSION: cls.Z_G,
283
+ }
284
+ return map_axis_to_mount[mount]
285
+
286
+ @classmethod
287
+ def mount_for_axis(cls, axis: "AxisType") -> Mount:
288
+ map_mount_to_axis = {
289
+ cls.Z_L: Mount.LEFT,
290
+ cls.Z_R: Mount.RIGHT,
291
+ cls.Z_G: Mount.EXTENSION,
292
+ }
293
+ return map_mount_to_axis[axis]
294
+
295
+ @classmethod
296
+ def plunger_axis_for_mount(cls, mount: Mount) -> "AxisType":
297
+ map_plunger_axis_mount = {Mount.LEFT: cls.P_L, Mount.RIGHT: cls.P_R}
298
+ return map_plunger_axis_mount[mount]
299
+
300
+ @classmethod
301
+ def ot2_axes(cls) -> List["AxisType"]:
302
+ return [
303
+ AxisType.X,
304
+ AxisType.Y,
305
+ AxisType.Z_L,
306
+ AxisType.Z_R,
307
+ AxisType.P_L,
308
+ AxisType.P_R,
309
+ ]
310
+
311
+ @classmethod
312
+ def flex_gantry_axes(cls) -> List["AxisType"]:
313
+ return [
314
+ AxisType.X,
315
+ AxisType.Y,
316
+ AxisType.Z_L,
317
+ AxisType.Z_R,
318
+ AxisType.Z_G,
319
+ ]
320
+
321
+ @classmethod
322
+ def ot2_gantry_axes(cls) -> List["AxisType"]:
323
+ return [
324
+ AxisType.X,
325
+ AxisType.Y,
326
+ AxisType.Z_L,
327
+ AxisType.Z_R,
328
+ ]
329
+
330
+
331
+ AxisMapType = Dict[AxisType, float]
332
+ StringAxisMap = Dict[str, float]
333
+
334
+
256
335
  # TODO(mc, 2020-11-09): this makes sense in shared-data or other common
257
336
  # model library
258
337
  # https://github.com/Opentrons/opentrons/pull/6943#discussion_r519029833
@@ -426,3 +505,84 @@ class TransferTipPolicy(enum.Enum):
426
505
 
427
506
  DeckLocation = Union[int, str]
428
507
  ALLOWED_PRIMARY_NOZZLES = ["A1", "H1", "A12", "H12"]
508
+
509
+
510
+ class NozzleConfigurationType(enum.Enum):
511
+ """Short names for types of nozzle configurations.
512
+
513
+ Represents the current nozzle configuration stored in a NozzleMap.
514
+ """
515
+
516
+ COLUMN = "COLUMN"
517
+ ROW = "ROW"
518
+ SINGLE = "SINGLE"
519
+ FULL = "FULL"
520
+ SUBRECT = "SUBRECT"
521
+
522
+
523
+ class NozzleMapInterface(Protocol):
524
+ """
525
+ A NozzleMap instance represents a specific configuration of active nozzles on a pipette.
526
+
527
+ It exposes properties of the configuration like the configuration's front-right, front-left,
528
+ back-left and starting nozzles as well as a map of all the nozzles active in the configuration.
529
+
530
+ Because NozzleMaps represent configurations directly, the properties of the NozzleMap may not
531
+ match the properties of the physical pipette. For instance, a NozzleMap for a single channel
532
+ configuration of an 8-channel pipette - say, A1 only - will have its front left, front right,
533
+ and active channels all be A1, while the physical configuration would have the front right
534
+ channel be H1.
535
+ """
536
+
537
+ @property
538
+ def starting_nozzle(self) -> str:
539
+ """The nozzle that automated operations that count nozzles should start at."""
540
+ ...
541
+
542
+ @property
543
+ def rows(self) -> dict[str, list[str]]:
544
+ """A map of all the rows active in this configuration."""
545
+ ...
546
+
547
+ @property
548
+ def columns(self) -> dict[str, list[str]]:
549
+ """A map of all the columns active in this configuration."""
550
+ ...
551
+
552
+ @property
553
+ def back_left(self) -> str:
554
+ """The backest, leftest (i.e. back if it's a column, left if it's a row) nozzle of the configuration.
555
+
556
+ Note: This is the value relevant for this particular configuration, and it may not represent the back left nozzle
557
+ of the underlying physical pipette. For instance, the back-left nozzle of a configuration representing nozzles
558
+ D7 to H12 of a 96-channel pipette is D7, which is not the back-left nozzle of the physical pipette (A1).
559
+ """
560
+ ...
561
+
562
+ @property
563
+ def configuration(self) -> NozzleConfigurationType:
564
+ """The kind of configuration represented by this nozzle map."""
565
+ ...
566
+
567
+ @property
568
+ def front_right(self) -> str:
569
+ """The frontest, rightest (i.e. front if it's a column, right if it's a row) nozzle of the configuration.
570
+
571
+ Note: This is the value relevant for this configuration, not the physical pipette. See the note on back_left.
572
+ """
573
+ ...
574
+
575
+ @property
576
+ def tip_count(self) -> int:
577
+ """The total number of active nozzles in the configuration, and thus the number of tips that will be picked up."""
578
+ ...
579
+
580
+ @property
581
+ def physical_nozzle_count(self) -> int:
582
+ """The number of actual physical nozzles on the pipette, regardless of configuration."""
583
+ ...
584
+
585
+ @property
586
+ def active_nozzles(self) -> list[str]:
587
+ """An unstructured list of all nozzles active in the configuration."""
588
+ ...
@@ -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.",