opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.0a1__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 (229) 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 +28 -20
  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 +60 -23
  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 +18 -0
  47. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/protocol_api/__init__.py +20 -1
  51. opentrons/protocol_api/_liquid.py +24 -49
  52. opentrons/protocol_api/_liquid_properties.py +754 -0
  53. opentrons/protocol_api/_types.py +24 -0
  54. opentrons/protocol_api/core/common.py +2 -0
  55. opentrons/protocol_api/core/engine/instrument.py +82 -10
  56. opentrons/protocol_api/core/engine/labware.py +29 -7
  57. opentrons/protocol_api/core/engine/protocol.py +130 -5
  58. opentrons/protocol_api/core/engine/robot.py +139 -0
  59. opentrons/protocol_api/core/engine/well.py +4 -1
  60. opentrons/protocol_api/core/instrument.py +46 -4
  61. opentrons/protocol_api/core/labware.py +13 -4
  62. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
  63. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  65. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  66. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
  67. opentrons/protocol_api/core/protocol.py +34 -1
  68. opentrons/protocol_api/core/robot.py +51 -0
  69. opentrons/protocol_api/instrument_context.py +158 -44
  70. opentrons/protocol_api/labware.py +231 -7
  71. opentrons/protocol_api/module_contexts.py +21 -17
  72. opentrons/protocol_api/protocol_context.py +125 -4
  73. opentrons/protocol_api/robot_context.py +204 -32
  74. opentrons/protocol_api/validation.py +262 -3
  75. opentrons/protocol_engine/__init__.py +4 -0
  76. opentrons/protocol_engine/actions/actions.py +2 -3
  77. opentrons/protocol_engine/clients/sync_client.py +18 -0
  78. opentrons/protocol_engine/commands/__init__.py +81 -0
  79. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  80. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  81. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  82. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  83. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  84. opentrons/protocol_engine/commands/aspirate.py +103 -53
  85. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  86. opentrons/protocol_engine/commands/blow_out.py +44 -39
  87. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  88. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  89. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  90. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  91. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  92. opentrons/protocol_engine/commands/command.py +73 -66
  93. opentrons/protocol_engine/commands/command_unions.py +101 -1
  94. opentrons/protocol_engine/commands/comment.py +1 -1
  95. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  96. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  97. opentrons/protocol_engine/commands/custom.py +6 -12
  98. opentrons/protocol_engine/commands/dispense.py +82 -48
  99. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  100. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  101. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  102. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  103. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  104. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  112. opentrons/protocol_engine/commands/home.py +13 -4
  113. opentrons/protocol_engine/commands/liquid_probe.py +67 -24
  114. opentrons/protocol_engine/commands/load_labware.py +29 -7
  115. opentrons/protocol_engine/commands/load_lid.py +146 -0
  116. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  117. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  118. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  119. opentrons/protocol_engine/commands/load_module.py +31 -10
  120. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  121. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  122. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  123. opentrons/protocol_engine/commands/move_labware.py +19 -6
  124. opentrons/protocol_engine/commands/move_relative.py +35 -25
  125. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  126. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  127. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  128. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  129. opentrons/protocol_engine/commands/movement_common.py +338 -0
  130. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  131. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  132. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  133. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  134. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  135. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  136. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  137. opentrons/protocol_engine/commands/robot/common.py +18 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  139. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  140. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  141. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  142. opentrons/protocol_engine/commands/save_position.py +14 -5
  143. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  144. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  147. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  148. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  152. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  153. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  154. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  155. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  158. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  159. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  160. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  161. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  162. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  163. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  164. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  165. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  166. opentrons/protocol_engine/errors/__init__.py +8 -0
  167. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  168. opentrons/protocol_engine/errors/exceptions.py +50 -0
  169. opentrons/protocol_engine/execution/command_executor.py +1 -1
  170. opentrons/protocol_engine/execution/equipment.py +73 -5
  171. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  172. opentrons/protocol_engine/execution/movement.py +27 -0
  173. opentrons/protocol_engine/execution/pipetting.py +5 -1
  174. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  175. opentrons/protocol_engine/notes/notes.py +1 -1
  176. opentrons/protocol_engine/protocol_engine.py +7 -6
  177. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  178. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  179. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  180. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  181. opentrons/protocol_engine/slot_standardization.py +9 -9
  182. opentrons/protocol_engine/state/_move_types.py +9 -5
  183. opentrons/protocol_engine/state/_well_math.py +193 -0
  184. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  185. opentrons/protocol_engine/state/command_history.py +12 -0
  186. opentrons/protocol_engine/state/commands.py +17 -13
  187. opentrons/protocol_engine/state/files.py +10 -12
  188. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  189. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  190. opentrons/protocol_engine/state/geometry.py +47 -1
  191. opentrons/protocol_engine/state/labware.py +79 -25
  192. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  193. opentrons/protocol_engine/state/liquids.py +16 -4
  194. opentrons/protocol_engine/state/modules.py +52 -70
  195. opentrons/protocol_engine/state/motion.py +6 -1
  196. opentrons/protocol_engine/state/pipettes.py +144 -58
  197. opentrons/protocol_engine/state/state.py +21 -2
  198. opentrons/protocol_engine/state/state_summary.py +4 -2
  199. opentrons/protocol_engine/state/tips.py +11 -44
  200. opentrons/protocol_engine/state/update_types.py +343 -48
  201. opentrons/protocol_engine/state/wells.py +19 -11
  202. opentrons/protocol_engine/types.py +176 -28
  203. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  204. opentrons/protocol_reader/file_format_validator.py +5 -5
  205. opentrons/protocol_runner/json_file_reader.py +9 -3
  206. opentrons/protocol_runner/json_translator.py +51 -25
  207. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  208. opentrons/protocol_runner/protocol_runner.py +35 -4
  209. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  210. opentrons/protocol_runner/run_orchestrator.py +13 -3
  211. opentrons/protocols/advanced_control/common.py +38 -0
  212. opentrons/protocols/advanced_control/mix.py +1 -1
  213. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  214. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  215. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  216. opentrons/protocols/api_support/definitions.py +1 -1
  217. opentrons/protocols/api_support/instrument.py +1 -1
  218. opentrons/protocols/api_support/util.py +10 -0
  219. opentrons/protocols/labware.py +39 -6
  220. opentrons/protocols/models/json_protocol.py +5 -9
  221. opentrons/simulate.py +3 -1
  222. opentrons/types.py +162 -2
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
  225. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
  226. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
  227. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
  228. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
@@ -1,29 +1,30 @@
1
1
  """Public protocol engine value types and models."""
2
2
  from __future__ import annotations
3
- import re
4
3
  from datetime import datetime
5
4
  from enum import Enum
6
5
  from dataclasses import dataclass
7
6
  from pathlib import Path
7
+ from typing import (
8
+ Any,
9
+ Dict,
10
+ FrozenSet,
11
+ List,
12
+ Mapping,
13
+ NamedTuple,
14
+ Optional,
15
+ Tuple,
16
+ Union,
17
+ )
18
+
8
19
  from pydantic import (
20
+ ConfigDict,
9
21
  BaseModel,
10
22
  Field,
23
+ RootModel,
11
24
  StrictBool,
12
25
  StrictFloat,
13
26
  StrictInt,
14
27
  StrictStr,
15
- validator,
16
- )
17
- from typing import (
18
- Optional,
19
- Union,
20
- List,
21
- Dict,
22
- Any,
23
- NamedTuple,
24
- Tuple,
25
- FrozenSet,
26
- Mapping,
27
28
  )
28
29
  from typing_extensions import Literal, TypeGuard
29
30
 
@@ -36,7 +37,9 @@ from opentrons.hardware_control.types import (
36
37
  from opentrons.hardware_control.modules import (
37
38
  ModuleType as ModuleType,
38
39
  )
39
-
40
+ from opentrons_shared_data.liquid_classes.liquid_class_definition import (
41
+ ByTipTypeSetting,
42
+ )
40
43
  from opentrons_shared_data.pipette.types import ( # noqa: F401
41
44
  # convenience re-export of LabwareUri type
42
45
  LabwareUri as LabwareUri,
@@ -423,6 +426,21 @@ class TipGeometry:
423
426
  volume: float
424
427
 
425
428
 
429
+ class FluidKind(str, Enum):
430
+ """A kind of fluid that can be inside a pipette."""
431
+
432
+ LIQUID = "LIQUID"
433
+ AIR = "AIR"
434
+
435
+
436
+ @dataclass(frozen=True)
437
+ class AspiratedFluid:
438
+ """Fluid inside a pipette."""
439
+
440
+ kind: FluidKind
441
+ volume: float
442
+
443
+
426
444
  class MovementAxis(str, Enum):
427
445
  """Axis on which to issue a relative movement."""
428
446
 
@@ -442,6 +460,7 @@ class MotorAxis(str, Enum):
442
460
  RIGHT_PLUNGER = "rightPlunger"
443
461
  EXTENSION_Z = "extensionZ"
444
462
  EXTENSION_JAW = "extensionJaw"
463
+ AXIS_96_CHANNEL_CAM = "axis96ChannelCam"
445
464
 
446
465
 
447
466
  # TODO(mc, 2022-01-18): use opentrons_shared_data.module.types.ModuleModel
@@ -535,7 +554,7 @@ class ModuleDimensions(BaseModel):
535
554
 
536
555
  bareOverallHeight: float
537
556
  overLabwareHeight: float
538
- lidHeight: Optional[float]
557
+ lidHeight: Optional[float] = None
539
558
 
540
559
 
541
560
  class Vec3f(BaseModel):
@@ -690,8 +709,8 @@ class LoadedModule(BaseModel):
690
709
 
691
710
  id: str
692
711
  model: ModuleModel
693
- location: Optional[DeckSlotLocation]
694
- serialNumber: Optional[str]
712
+ location: Optional[DeckSlotLocation] = None
713
+ serialNumber: Optional[str] = None
695
714
 
696
715
 
697
716
  class LabwareOffsetLocation(BaseModel):
@@ -785,6 +804,10 @@ class LoadedLabware(BaseModel):
785
804
  location: LabwareLocation = Field(
786
805
  ..., description="The labware's current location."
787
806
  )
807
+ lid_id: Optional[str] = Field(
808
+ None,
809
+ description=("Labware ID of a Lid currently loaded on top of the labware."),
810
+ )
788
811
  offsetId: Optional[str] = Field(
789
812
  None,
790
813
  description=(
@@ -800,17 +823,14 @@ class LoadedLabware(BaseModel):
800
823
  )
801
824
 
802
825
 
803
- class HexColor(BaseModel):
826
+ class HexColor(RootModel[str]):
804
827
  """Hex color representation."""
805
828
 
806
- __root__: str
829
+ root: str = Field(pattern=r"^#(?:[0-9a-fA-F]{3,4}){1,2}$")
830
+
807
831
 
808
- @validator("__root__")
809
- def _color_is_a_valid_hex(cls, v: str) -> str:
810
- match = re.search(r"^#(?:[0-9a-fA-F]{3,4}){1,2}$", v)
811
- if not match:
812
- raise ValueError("Color is not a valid hex color.")
813
- return v
832
+ EmptyLiquidId = Literal["EMPTY"]
833
+ LiquidId = str | EmptyLiquidId
814
834
 
815
835
 
816
836
  class Liquid(BaseModel):
@@ -819,7 +839,59 @@ class Liquid(BaseModel):
819
839
  id: str
820
840
  displayName: str
821
841
  description: str
822
- displayColor: Optional[HexColor]
842
+ displayColor: Optional[HexColor] = None
843
+
844
+
845
+ class LiquidClassRecord(ByTipTypeSetting, frozen=True):
846
+ """LiquidClassRecord is our internal representation of an (immutable) liquid class.
847
+
848
+ Conceptually, a liquid class record is the tuple (name, pipette, tip, transfer properties).
849
+ We consider two liquid classes to be the same if every entry in that tuple is the same; and liquid
850
+ classes are different if any entry in the tuple is different.
851
+
852
+ This class defines the tuple via inheritance so that we can reuse the definitions from shared_data.
853
+ """
854
+
855
+ liquidClassName: str = Field(
856
+ ...,
857
+ description="Identifier for the liquid of this liquid class, e.g. glycerol50.",
858
+ )
859
+ pipetteModel: str = Field(
860
+ ...,
861
+ description="Identifier for the pipette of this liquid class.",
862
+ )
863
+ # The other fields like tiprack ID, aspirate properties, etc. are pulled in from ByTipTypeSetting.
864
+
865
+ def __hash__(self) -> int:
866
+ """Hash function for LiquidClassRecord."""
867
+ # Within the Protocol Engine, LiquidClassRecords are immutable, and we'd like to be able to
868
+ # look up LiquidClassRecords by value, which involves hashing. However, Pydantic does not
869
+ # generate a usable hash function if any of the subfields (like Coordinate) are not frozen.
870
+ # So we have to implement the hash function ourselves.
871
+ # Our strategy is to recursively convert this object into a list of (key, value) tuples.
872
+ def dict_to_tuple(d: dict[str, Any]) -> tuple[tuple[str, Any], ...]:
873
+ return tuple(
874
+ (
875
+ field_name,
876
+ dict_to_tuple(value)
877
+ if isinstance(value, dict)
878
+ else tuple(value)
879
+ if isinstance(value, list)
880
+ else value,
881
+ )
882
+ for field_name, value in d.items()
883
+ )
884
+
885
+ return hash(dict_to_tuple(self.model_dump()))
886
+
887
+
888
+ class LiquidClassRecordWithId(LiquidClassRecord, frozen=True):
889
+ """A LiquidClassRecord with its ID, for use in summary lists."""
890
+
891
+ liquidClassId: str = Field(
892
+ ...,
893
+ description="Unique identifier for this liquid class.",
894
+ )
823
895
 
824
896
 
825
897
  class SpeedRange(NamedTuple):
@@ -976,12 +1048,12 @@ class QuadrantNozzleLayoutConfiguration(BaseModel):
976
1048
  )
977
1049
  frontRightNozzle: str = Field(
978
1050
  ...,
979
- regex=NOZZLE_NAME_REGEX,
1051
+ pattern=NOZZLE_NAME_REGEX,
980
1052
  description="The front right nozzle in your configuration.",
981
1053
  )
982
1054
  backLeftNozzle: str = Field(
983
1055
  ...,
984
- regex=NOZZLE_NAME_REGEX,
1056
+ pattern=NOZZLE_NAME_REGEX,
985
1057
  description="The back left nozzle in your configuration.",
986
1058
  )
987
1059
 
@@ -1041,6 +1113,82 @@ class TipPresenceStatus(str, Enum):
1041
1113
  }[state]
1042
1114
 
1043
1115
 
1116
+ class NextTipInfo(BaseModel):
1117
+ """Next available tip labware and well name data."""
1118
+
1119
+ labwareId: str = Field(
1120
+ ...,
1121
+ description="The labware ID of the tip rack where the next available tip(s) are located.",
1122
+ )
1123
+ tipStartingWell: str = Field(
1124
+ ..., description="The (starting) well name of the next available tip(s)."
1125
+ )
1126
+
1127
+
1128
+ class NoTipReason(Enum):
1129
+ """The cause of no tip being available for a pipette and tip rack(s)."""
1130
+
1131
+ NO_AVAILABLE_TIPS = "noAvailableTips"
1132
+ STARTING_TIP_WITH_PARTIAL = "startingTipWithPartial"
1133
+ INCOMPATIBLE_CONFIGURATION = "incompatibleConfiguration"
1134
+
1135
+
1136
+ class NoTipAvailable(BaseModel):
1137
+ """No available next tip data."""
1138
+
1139
+ noTipReason: NoTipReason = Field(
1140
+ ..., description="The reason why no next available tip could be provided."
1141
+ )
1142
+ message: Optional[str] = Field(
1143
+ None, description="Optional message explaining why a tip wasn't available."
1144
+ )
1145
+
1146
+
1147
+ class BaseCommandAnnotation(BaseModel):
1148
+ """Optional annotations for protocol engine commands."""
1149
+
1150
+ commandKeys: List[str] = Field(
1151
+ ..., description="Command keys to which this annotation applies"
1152
+ )
1153
+ annotationType: str = Field(
1154
+ ..., description="The type of annotation (for machine parsing)"
1155
+ )
1156
+
1157
+
1158
+ class SecondOrderCommandAnnotation(BaseCommandAnnotation):
1159
+ """Annotates a group of atomic commands which were the direct result of a second order command.
1160
+
1161
+ Examples of second order commands would be transfer, consolidate, mix, etc.
1162
+ """
1163
+
1164
+ annotationType: Literal["secondOrderCommand"] = "secondOrderCommand"
1165
+ params: Dict[str, Any] = Field(
1166
+ ...,
1167
+ description="Key value pairs of the parameters passed to the second order command that this annotates.",
1168
+ )
1169
+ machineReadableName: str = Field(
1170
+ ...,
1171
+ description="The name of the second order command in the form that the generating software refers to it",
1172
+ )
1173
+ userSpecifiedName: Optional[str] = Field(
1174
+ None, description="The optional user-specified name of the second order command"
1175
+ )
1176
+ userSpecifiedDescription: Optional[str] = Field(
1177
+ None,
1178
+ description="The optional user-specified description of the second order command",
1179
+ )
1180
+
1181
+
1182
+ class CustomCommandAnnotation(BaseCommandAnnotation):
1183
+ """Annotates a group of atomic commands in some manner that Opentrons software does not anticipate or originate."""
1184
+
1185
+ annotationType: Literal["custom"] = "custom"
1186
+ model_config = ConfigDict(extra="allow")
1187
+
1188
+
1189
+ CommandAnnotation = Union[SecondOrderCommandAnnotation, CustomCommandAnnotation]
1190
+
1191
+
1044
1192
  # TODO (spp, 2024-04-02): move all RTP types to runner
1045
1193
  class RTPBase(BaseModel):
1046
1194
  """Parameters defined in a protocol."""
@@ -41,7 +41,10 @@ async def extract_labware_definitions(
41
41
 
42
42
 
43
43
  async def _extract_from_labware_file(path: Path) -> LabwareDefinition:
44
- return await anyio.to_thread.run_sync(LabwareDefinition.parse_file, path)
44
+ def _do_parse() -> LabwareDefinition:
45
+ return LabwareDefinition.model_validate_json(path.read_bytes())
46
+
47
+ return await anyio.to_thread.run_sync(_do_parse)
45
48
 
46
49
 
47
50
  async def _extract_from_json_protocol_file(path: Path) -> List[LabwareDefinition]:
@@ -52,7 +55,7 @@ async def _extract_from_json_protocol_file(path: Path) -> List[LabwareDefinition
52
55
  # which require this labwareDefinitions key.
53
56
  unvalidated_definitions = json_contents["labwareDefinitions"].values()
54
57
  validated_definitions = [
55
- LabwareDefinition.parse_obj(u) for u in unvalidated_definitions
58
+ LabwareDefinition.model_validate(u) for u in unvalidated_definitions
56
59
  ]
57
60
  return validated_definitions
58
61
 
@@ -60,7 +60,7 @@ class FileFormatValidator:
60
60
  async def _validate_labware_definition(info: IdentifiedLabwareDefinition) -> None:
61
61
  def validate_sync() -> None:
62
62
  try:
63
- LabwareDefinition.parse_obj(info.unvalidated_json)
63
+ LabwareDefinition.model_validate(info.unvalidated_json)
64
64
  except PydanticValidationError as e:
65
65
  raise FileFormatValidationError(
66
66
  message=f"{info.original_file.name} could not be read as a labware definition.",
@@ -133,17 +133,17 @@ async def _validate_json_protocol(info: IdentifiedJsonMain) -> None:
133
133
  def validate_sync() -> None:
134
134
  if info.schema_version == 8:
135
135
  try:
136
- JsonProtocolV8.parse_obj(info.unvalidated_json)
136
+ JsonProtocolV8.model_validate(info.unvalidated_json)
137
137
  except PydanticValidationError as pve:
138
138
  _handle_v8_json_protocol_validation_error(info, pve)
139
139
  else:
140
140
  try:
141
141
  if info.schema_version == 7:
142
- JsonProtocolV7.parse_obj(info.unvalidated_json)
142
+ JsonProtocolV7.model_validate(info.unvalidated_json)
143
143
  elif info.schema_version == 6:
144
- JsonProtocolV6.parse_obj(info.unvalidated_json)
144
+ JsonProtocolV6.model_validate(info.unvalidated_json)
145
145
  else:
146
- JsonProtocolUpToV5.parse_obj(info.unvalidated_json)
146
+ JsonProtocolUpToV5.model_validate(info.unvalidated_json)
147
147
  except PydanticValidationError as e:
148
148
  raise FileFormatValidationError._generic_json_failure(info, e) from e
149
149
 
@@ -30,11 +30,17 @@ class JsonFileReader:
30
30
  },
31
31
  )
32
32
  if protocol_source.config.schema_version == 6:
33
- return ProtocolSchemaV6.parse_file(protocol_source.main_file)
33
+ return ProtocolSchemaV6.model_validate_json(
34
+ protocol_source.main_file.read_bytes()
35
+ )
34
36
  elif protocol_source.config.schema_version == 7:
35
- return ProtocolSchemaV7.parse_file(protocol_source.main_file)
37
+ return ProtocolSchemaV7.model_validate_json(
38
+ protocol_source.main_file.read_bytes()
39
+ )
36
40
  elif protocol_source.config.schema_version == 8:
37
- return ProtocolSchemaV8.parse_file(protocol_source.main_file)
41
+ return ProtocolSchemaV8.model_validate_json(
42
+ protocol_source.main_file.read_bytes()
43
+ )
38
44
  else:
39
45
  raise ProtocolFilesInvalidError(
40
46
  message=f"{name} is a JSON protocol v{protocol_source.config.schema_version} which this robot cannot execute",
@@ -1,6 +1,7 @@
1
1
  """Translation of JSON protocol commands into ProtocolEngine commands."""
2
- from typing import cast, List, Union, Iterator
3
- from pydantic import parse_obj_as, ValidationError as PydanticValidationError
2
+
3
+ from typing import List, Union, Iterator
4
+ from pydantic import ValidationError as PydanticValidationError, TypeAdapter
4
5
 
5
6
  from opentrons_shared_data.pipette.types import PipetteNameType
6
7
  from opentrons_shared_data.protocol.models import (
@@ -10,6 +11,8 @@ from opentrons_shared_data.protocol.models import (
10
11
  protocol_schema_v7,
11
12
  ProtocolSchemaV8,
12
13
  protocol_schema_v8,
14
+ Location,
15
+ # CommandSchemaId,
13
16
  )
14
17
  from opentrons_shared_data import command as command_schema
15
18
  from opentrons_shared_data.errors.exceptions import InvalidProtocolData, PythonException
@@ -22,7 +25,7 @@ from opentrons.protocol_engine import (
22
25
  DeckSlotLocation,
23
26
  Liquid,
24
27
  )
25
- from opentrons.protocol_engine.types import HexColor
28
+ from opentrons.protocol_engine.types import HexColor, CommandAnnotation
26
29
 
27
30
 
28
31
  class CommandTranslatorError(Exception):
@@ -31,6 +34,15 @@ class CommandTranslatorError(Exception):
31
34
  pass
32
35
 
33
36
 
37
+ # Each time a TypeAdapter is instantiated, it will construct a new validator and
38
+ # serializer. To improve performance, TypeAdapters are instantiated once.
39
+ # See https://docs.pydantic.dev/latest/concepts/performance/#typeadapter-instantiated-once
40
+ LabwareLocationAdapter: TypeAdapter[LabwareLocation] = TypeAdapter(LabwareLocation)
41
+ CommandAnnotationAdapter: TypeAdapter[CommandAnnotation] = TypeAdapter(
42
+ CommandAnnotation
43
+ )
44
+
45
+
34
46
  def _translate_labware_command(
35
47
  protocol: ProtocolSchemaV6,
36
48
  command: protocol_schema_v6.Command,
@@ -41,6 +53,8 @@ def _translate_labware_command(
41
53
  assert labware_id is not None
42
54
  definition_id = protocol.labware[labware_id].definitionId
43
55
  assert definition_id is not None
56
+
57
+ location = command.params.location
44
58
  labware_command = pe_commands.LoadLabwareCreate(
45
59
  params=pe_commands.LoadLabwareParams(
46
60
  labwareId=command.params.labwareId,
@@ -48,10 +62,8 @@ def _translate_labware_command(
48
62
  version=protocol.labwareDefinitions[definition_id].version,
49
63
  namespace=protocol.labwareDefinitions[definition_id].namespace,
50
64
  loadName=protocol.labwareDefinitions[definition_id].parameters.loadName,
51
- location=parse_obj_as(
52
- # https://github.com/samuelcolvin/pydantic/issues/1847
53
- LabwareLocation, # type: ignore[arg-type]
54
- command.params.location,
65
+ location=LabwareLocationAdapter.validate_python(
66
+ location.model_dump() if isinstance(location, Location) else location
55
67
  ),
56
68
  ),
57
69
  key=command.key,
@@ -70,6 +82,7 @@ def _translate_v7_labware_command(
70
82
  assert command.params.namespace is not None
71
83
  assert command.params.loadName is not None
72
84
 
85
+ location = command.params.location
73
86
  labware_command = pe_commands.LoadLabwareCreate(
74
87
  params=pe_commands.LoadLabwareParams(
75
88
  labwareId=command.params.labwareId,
@@ -77,10 +90,8 @@ def _translate_v7_labware_command(
77
90
  version=command.params.version,
78
91
  namespace=command.params.namespace,
79
92
  loadName=command.params.loadName,
80
- location=parse_obj_as(
81
- # https://github.com/samuelcolvin/pydantic/issues/1847
82
- LabwareLocation, # type: ignore[arg-type]
83
- command.params.location,
93
+ location=LabwareLocationAdapter.validate_python(
94
+ location.model_dump() if isinstance(location, Location) else location
84
95
  ),
85
96
  ),
86
97
  key=command.key,
@@ -98,10 +109,14 @@ def _translate_module_command(
98
109
  # load module command must contain module_id. modules cannot be None.
99
110
  assert module_id is not None
100
111
  assert modules is not None
112
+
113
+ location = command.params.location
101
114
  translated_obj = pe_commands.LoadModuleCreate(
102
115
  params=pe_commands.LoadModuleParams(
103
116
  model=ModuleModel(modules[module_id].model),
104
- location=DeckSlotLocation.parse_obj(command.params.location),
117
+ location=DeckSlotLocation.model_validate(
118
+ location.model_dump() if isinstance(location, Location) else location
119
+ ),
105
120
  moduleId=command.params.moduleId,
106
121
  ),
107
122
  key=command.key,
@@ -117,10 +132,13 @@ def _translate_v7_module_command(
117
132
  # load module command must contain module_id. modules cannot be None.
118
133
  assert module_id is not None
119
134
  assert command.params.model is not None
135
+ location = command.params.location
120
136
  translated_obj = pe_commands.LoadModuleCreate(
121
137
  params=pe_commands.LoadModuleParams(
122
138
  model=ModuleModel(command.params.model),
123
- location=DeckSlotLocation.parse_obj(command.params.location),
139
+ location=DeckSlotLocation.model_validate(
140
+ location.model_dump() if isinstance(location, Location) else location
141
+ ),
124
142
  moduleId=command.params.moduleId,
125
143
  ),
126
144
  key=command.key,
@@ -171,9 +189,9 @@ def _translate_simple_command(
171
189
  protocol_schema_v6.Command,
172
190
  protocol_schema_v7.Command,
173
191
  protocol_schema_v8.Command,
174
- ]
192
+ ],
175
193
  ) -> pe_commands.CommandCreate:
176
- dict_command = command.dict(exclude_none=True)
194
+ dict_command = command.model_dump(exclude_none=True)
177
195
 
178
196
  # map deprecated `delay` commands to `waitForResume` / `waitForDuration`
179
197
  if dict_command["commandType"] == "delay":
@@ -182,15 +200,7 @@ def _translate_simple_command(
182
200
  else:
183
201
  dict_command["commandType"] = "waitForDuration"
184
202
 
185
- translated_obj = cast(
186
- pe_commands.CommandCreate,
187
- parse_obj_as(
188
- # https://github.com/samuelcolvin/pydantic/issues/1847
189
- pe_commands.CommandCreate, # type: ignore[arg-type]
190
- dict_command,
191
- ),
192
- )
193
- return translated_obj
203
+ return pe_commands.CommandCreateAdapter.validate_python(dict_command)
194
204
 
195
205
 
196
206
  class JsonTranslator:
@@ -207,7 +217,7 @@ class JsonTranslator:
207
217
  id=liquid_id,
208
218
  displayName=liquid.displayName,
209
219
  description=liquid.description,
210
- displayColor=HexColor(__root__=liquid.displayColor)
220
+ displayColor=HexColor(liquid.displayColor)
211
221
  if liquid.displayColor is not None
212
222
  else None,
213
223
  )
@@ -284,3 +294,19 @@ class JsonTranslator:
284
294
  )
285
295
 
286
296
  return list(translate_all_commands())
297
+
298
+ def translate_command_annotations(
299
+ self,
300
+ protocol: Union[ProtocolSchemaV8, ProtocolSchemaV7, ProtocolSchemaV6],
301
+ ) -> List[CommandAnnotation]:
302
+ """Translate command annotations in json protocol schema v8."""
303
+ if isinstance(protocol, (ProtocolSchemaV6, ProtocolSchemaV7)):
304
+ return []
305
+ else:
306
+ command_annotations: List[CommandAnnotation] = [
307
+ CommandAnnotationAdapter.validate_python(
308
+ command_annotation.model_dump(),
309
+ )
310
+ for command_annotation in protocol.commandAnnotations
311
+ ]
312
+ return command_annotations