opentrons 8.2.0a4__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 +36 -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 +1 -1
  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 +52 -70
  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.0a4.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,10 @@
1
1
  """Command models to start heating a Thermocycler's block."""
2
2
  from __future__ import annotations
3
- from typing import Optional, TYPE_CHECKING
3
+ from typing import Optional, TYPE_CHECKING, Any
4
4
  from typing_extensions import Literal, Type
5
5
 
6
6
  from pydantic import BaseModel, Field
7
+ from pydantic.json_schema import SkipJsonSchema
7
8
 
8
9
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
9
10
  from ...errors.error_occurrence import ErrorOccurrence
@@ -16,21 +17,27 @@ if TYPE_CHECKING:
16
17
  SetTargetBlockTemperatureCommandType = Literal["thermocycler/setTargetBlockTemperature"]
17
18
 
18
19
 
20
+ def _remove_default(s: dict[str, Any]) -> None:
21
+ s.pop("default", None)
22
+
23
+
19
24
  class SetTargetBlockTemperatureParams(BaseModel):
20
25
  """Input parameters to set a Thermocycler's target block temperature."""
21
26
 
22
27
  moduleId: str = Field(..., description="Unique ID of the Thermocycler Module.")
23
28
  celsius: float = Field(..., description="Target temperature in °C.")
24
- blockMaxVolumeUl: Optional[float] = Field(
29
+ blockMaxVolumeUl: float | SkipJsonSchema[None] = Field(
25
30
  None,
26
31
  description="Amount of liquid in uL of the most-full well"
27
32
  " in labware loaded onto the thermocycler.",
33
+ json_schema_extra=_remove_default,
28
34
  )
29
- holdTimeSeconds: Optional[float] = Field(
35
+ holdTimeSeconds: float | SkipJsonSchema[None] = Field(
30
36
  None,
31
37
  description="Amount of time, in seconds, to hold the temperature for."
32
38
  " If specified, a waitForBlockTemperature command will block until"
33
39
  " the given hold time has elapsed.",
40
+ json_schema_extra=_remove_default,
34
41
  )
35
42
 
36
43
 
@@ -113,7 +120,7 @@ class SetTargetBlockTemperature(
113
120
  "thermocycler/setTargetBlockTemperature"
114
121
  )
115
122
  params: SetTargetBlockTemperatureParams
116
- result: Optional[SetTargetBlockTemperatureResult]
123
+ result: Optional[SetTargetBlockTemperatureResult] = None
117
124
 
118
125
  _ImplementationCls: Type[
119
126
  SetTargetBlockTemperatureImpl
@@ -84,7 +84,7 @@ class SetTargetLidTemperature(
84
84
  "thermocycler/setTargetLidTemperature"
85
85
  )
86
86
  params: SetTargetLidTemperatureParams
87
- result: Optional[SetTargetLidTemperatureResult]
87
+ result: Optional[SetTargetLidTemperatureResult] = None
88
88
 
89
89
  _ImplementationCls: Type[SetTargetLidTemperatureImpl] = SetTargetLidTemperatureImpl
90
90
 
@@ -77,7 +77,7 @@ class WaitForBlockTemperature(
77
77
  "thermocycler/waitForBlockTemperature"
78
78
  )
79
79
  params: WaitForBlockTemperatureParams
80
- result: Optional[WaitForBlockTemperatureResult]
80
+ result: Optional[WaitForBlockTemperatureResult] = None
81
81
 
82
82
  _ImplementationCls: Type[WaitForBlockTemperatureImpl] = WaitForBlockTemperatureImpl
83
83
 
@@ -75,7 +75,7 @@ class WaitForLidTemperature(
75
75
 
76
76
  commandType: WaitForLidTemperatureCommandType = "thermocycler/waitForLidTemperature"
77
77
  params: WaitForLidTemperatureParams
78
- result: Optional[WaitForLidTemperatureResult]
78
+ result: Optional[WaitForLidTemperatureResult] = None
79
79
 
80
80
  _ImplementationCls: Type[WaitForLidTemperatureImpl] = WaitForLidTemperatureImpl
81
81
 
@@ -1,29 +1,50 @@
1
1
  """Touch tip command request, result, and implementation models."""
2
+
2
3
  from __future__ import annotations
3
- from pydantic import Field
4
- from typing import TYPE_CHECKING, Optional, Type
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
5
+
5
6
  from typing_extensions import Literal
7
+ from pydantic import Field
8
+ from pydantic.json_schema import SkipJsonSchema
6
9
 
7
- from opentrons.protocol_engine.state import update_types
10
+ from opentrons.types import Point
8
11
 
9
- from ..errors import TouchTipDisabledError, LabwareIsTipRackError
12
+ from ..errors import (
13
+ TouchTipDisabledError,
14
+ TouchTipIncompatibleArgumentsError,
15
+ LabwareIsTipRackError,
16
+ )
10
17
  from ..types import DeckPoint
11
- from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
12
- from ..errors.error_occurrence import ErrorOccurrence
18
+ from .command import (
19
+ AbstractCommandImpl,
20
+ BaseCommand,
21
+ BaseCommandCreate,
22
+ SuccessData,
23
+ DefinedErrorData,
24
+ )
13
25
  from .pipetting_common import (
14
26
  PipetteIdMixin,
27
+ )
28
+ from .movement_common import (
15
29
  WellLocationMixin,
16
30
  DestinationPositionResult,
31
+ StallOrCollisionError,
32
+ move_to_well,
17
33
  )
18
34
 
19
35
  if TYPE_CHECKING:
20
36
  from ..execution import MovementHandler, GantryMover
21
37
  from ..state.state import StateView
38
+ from ..resources.model_utils import ModelUtils
22
39
 
23
40
 
24
41
  TouchTipCommandType = Literal["touchTip"]
25
42
 
26
43
 
44
+ def _remove_default(s: dict[str, Any]) -> None:
45
+ s.pop("default", None)
46
+
47
+
27
48
  class TouchTipParams(PipetteIdMixin, WellLocationMixin):
28
49
  """Payload needed to touch a pipette tip the sides of a specific well."""
29
50
 
@@ -34,12 +55,20 @@ class TouchTipParams(PipetteIdMixin, WellLocationMixin):
34
55
  ),
35
56
  )
36
57
 
37
- speed: Optional[float] = Field(
58
+ mmFromEdge: float | SkipJsonSchema[None] = Field(
59
+ None,
60
+ description="Offset away from the the well edge, in millimeters."
61
+ "Incompatible when a radius is included as a non 1.0 value.",
62
+ json_schema_extra=_remove_default,
63
+ )
64
+
65
+ speed: float | SkipJsonSchema[None] = Field(
38
66
  None,
39
67
  description=(
40
68
  "Override the travel speed in mm/s."
41
69
  " This controls the straight linear speed of motion."
42
70
  ),
71
+ json_schema_extra=_remove_default,
43
72
  )
44
73
 
45
74
 
@@ -50,7 +79,10 @@ class TouchTipResult(DestinationPositionResult):
50
79
 
51
80
 
52
81
  class TouchTipImplementation(
53
- AbstractCommandImpl[TouchTipParams, SuccessData[TouchTipResult]]
82
+ AbstractCommandImpl[
83
+ TouchTipParams,
84
+ SuccessData[TouchTipResult] | DefinedErrorData[StallOrCollisionError],
85
+ ]
54
86
  ):
55
87
  """Touch tip command implementation."""
56
88
 
@@ -59,19 +91,26 @@ class TouchTipImplementation(
59
91
  state_view: StateView,
60
92
  movement: MovementHandler,
61
93
  gantry_mover: GantryMover,
94
+ model_utils: ModelUtils,
62
95
  **kwargs: object,
63
96
  ) -> None:
64
97
  self._state_view = state_view
65
98
  self._movement = movement
66
99
  self._gantry_mover = gantry_mover
100
+ self._model_utils = model_utils
67
101
 
68
- async def execute(self, params: TouchTipParams) -> SuccessData[TouchTipResult]:
102
+ async def execute(
103
+ self, params: TouchTipParams
104
+ ) -> SuccessData[TouchTipResult] | DefinedErrorData[StallOrCollisionError]:
69
105
  """Touch tip to sides of a well using the requested pipette."""
70
106
  pipette_id = params.pipetteId
71
107
  labware_id = params.labwareId
72
108
  well_name = params.wellName
73
109
 
74
- state_update = update_types.StateUpdate()
110
+ if params.radius != 1.0 and params.mmFromEdge is not None:
111
+ raise TouchTipIncompatibleArgumentsError(
112
+ "Cannot use mmFromEdge with a radius that is not 1.0"
113
+ )
75
114
 
76
115
  if self._state_view.labware.get_has_quirk(labware_id, "touchTipDisabled"):
77
116
  raise TouchTipDisabledError(
@@ -81,23 +120,33 @@ class TouchTipImplementation(
81
120
  if self._state_view.labware.is_tiprack(labware_id):
82
121
  raise LabwareIsTipRackError("Cannot touch tip on tip rack")
83
122
 
84
- center_point = await self._movement.move_to_well(
123
+ center_result = await move_to_well(
124
+ movement=self._movement,
125
+ model_utils=self._model_utils,
85
126
  pipette_id=pipette_id,
86
127
  labware_id=labware_id,
87
128
  well_name=well_name,
88
129
  well_location=params.wellLocation,
89
130
  )
131
+ if isinstance(center_result, DefinedErrorData):
132
+ return center_result
90
133
 
91
134
  touch_speed = self._state_view.pipettes.get_movement_speed(
92
135
  pipette_id, params.speed
93
136
  )
94
137
 
138
+ mm_from_edge = params.mmFromEdge if params.mmFromEdge is not None else 0
95
139
  touch_waypoints = self._state_view.motion.get_touch_tip_waypoints(
96
140
  pipette_id=pipette_id,
97
141
  labware_id=labware_id,
98
142
  well_name=well_name,
99
143
  radius=params.radius,
100
- center_point=center_point,
144
+ mm_from_edge=mm_from_edge,
145
+ center_point=Point(
146
+ center_result.public.position.x,
147
+ center_result.public.position.y,
148
+ center_result.public.position.z,
149
+ ),
101
150
  )
102
151
 
103
152
  final_point = await self._gantry_mover.move_to(
@@ -105,10 +154,10 @@ class TouchTipImplementation(
105
154
  waypoints=touch_waypoints,
106
155
  speed=touch_speed,
107
156
  )
108
- final_deck_point = DeckPoint.construct(
157
+ final_deck_point = DeckPoint.model_construct(
109
158
  x=final_point.x, y=final_point.y, z=final_point.z
110
159
  )
111
- state_update.set_pipette_location(
160
+ state_update = center_result.state_update.set_pipette_location(
112
161
  pipette_id=pipette_id,
113
162
  new_labware_id=labware_id,
114
163
  new_well_name=well_name,
@@ -121,12 +170,12 @@ class TouchTipImplementation(
121
170
  )
122
171
 
123
172
 
124
- class TouchTip(BaseCommand[TouchTipParams, TouchTipResult, ErrorOccurrence]):
173
+ class TouchTip(BaseCommand[TouchTipParams, TouchTipResult, StallOrCollisionError]):
125
174
  """Touch up tip command model."""
126
175
 
127
176
  commandType: TouchTipCommandType = "touchTip"
128
177
  params: TouchTipParams
129
- result: Optional[TouchTipResult]
178
+ result: Optional[TouchTipResult] = None
130
179
 
131
180
  _ImplementationCls: Type[TouchTipImplementation] = TouchTipImplementation
132
181
 
@@ -10,6 +10,7 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, Succe
10
10
  from ..pipetting_common import PipetteIdMixin, FlowRateMixin
11
11
  from ...resources import ensure_ot3_hardware
12
12
  from ...errors.error_occurrence import ErrorOccurrence
13
+ from ...state import update_types
13
14
 
14
15
  from opentrons.hardware_control import HardwareControlAPI
15
16
  from opentrons.hardware_control.types import Axis
@@ -66,9 +67,11 @@ class UnsafeBlowOutInPlaceImplementation(
66
67
  await self._pipetting.blow_out_in_place(
67
68
  pipette_id=params.pipetteId, flow_rate=params.flowRate
68
69
  )
70
+ state_update = update_types.StateUpdate()
71
+ state_update.set_fluid_empty(pipette_id=params.pipetteId)
69
72
 
70
73
  return SuccessData(
71
- public=UnsafeBlowOutInPlaceResult(),
74
+ public=UnsafeBlowOutInPlaceResult(), state_update=state_update
72
75
  )
73
76
 
74
77
 
@@ -79,7 +82,7 @@ class UnsafeBlowOutInPlace(
79
82
 
80
83
  commandType: UnsafeBlowOutInPlaceCommandType = "unsafe/blowOutInPlace"
81
84
  params: UnsafeBlowOutInPlaceParams
82
- result: Optional[UnsafeBlowOutInPlaceResult]
85
+ result: Optional[UnsafeBlowOutInPlaceResult] = None
83
86
 
84
87
  _ImplementationCls: Type[
85
88
  UnsafeBlowOutInPlaceImplementation
@@ -1,13 +1,16 @@
1
1
  """Command models to drop tip in place while plunger positions are unknown."""
2
+
2
3
  from __future__ import annotations
3
- from opentrons.protocol_engine.state.update_types import StateUpdate
4
+ from typing import TYPE_CHECKING, Optional, Type, Any
5
+
4
6
  from pydantic import Field, BaseModel
5
- from typing import TYPE_CHECKING, Optional, Type
7
+ from pydantic.json_schema import SkipJsonSchema
6
8
  from typing_extensions import Literal
7
9
 
8
10
  from opentrons.hardware_control import HardwareControlAPI
9
11
  from opentrons.hardware_control.types import Axis
10
12
 
13
+ from opentrons.protocol_engine.state.update_types import StateUpdate
11
14
  from ..pipetting_common import PipetteIdMixin
12
15
  from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
13
16
  from ...errors.error_occurrence import ErrorOccurrence
@@ -21,16 +24,21 @@ if TYPE_CHECKING:
21
24
  UnsafeDropTipInPlaceCommandType = Literal["unsafe/dropTipInPlace"]
22
25
 
23
26
 
27
+ def _remove_default(s: dict[str, Any]) -> None:
28
+ s.pop("default", None)
29
+
30
+
24
31
  class UnsafeDropTipInPlaceParams(PipetteIdMixin):
25
32
  """Payload required to drop a tip in place even if the plunger position is not known."""
26
33
 
27
- homeAfter: Optional[bool] = Field(
34
+ homeAfter: bool | SkipJsonSchema[None] = Field(
28
35
  None,
29
36
  description=(
30
37
  "Whether to home this pipette's plunger after dropping the tip."
31
38
  " You should normally leave this unspecified to let the robot choose"
32
39
  " a safe default depending on its hardware."
33
40
  ),
41
+ json_schema_extra=_remove_default,
34
42
  )
35
43
 
36
44
 
@@ -77,6 +85,7 @@ class UnsafeDropTipInPlaceImplementation(
77
85
  state_update.update_pipette_tip_state(
78
86
  pipette_id=params.pipetteId, tip_geometry=None
79
87
  )
88
+ state_update.set_fluid_unknown(pipette_id=params.pipetteId)
80
89
 
81
90
  return SuccessData(
82
91
  public=UnsafeDropTipInPlaceResult(), state_update=state_update
@@ -90,7 +99,7 @@ class UnsafeDropTipInPlace(
90
99
 
91
100
  commandType: UnsafeDropTipInPlaceCommandType = "unsafe/dropTipInPlace"
92
101
  params: UnsafeDropTipInPlaceParams
93
- result: Optional[UnsafeDropTipInPlaceResult]
102
+ result: Optional[UnsafeDropTipInPlaceResult] = None
94
103
 
95
104
  _ImplementationCls: Type[
96
105
  UnsafeDropTipInPlaceImplementation
@@ -52,10 +52,7 @@ class UnsafeEngageAxesImplementation(
52
52
  """Enable exes."""
53
53
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
54
54
  await ot3_hardware_api.engage_axes(
55
- [
56
- self._gantry_mover.motor_axis_to_hardware_axis(axis)
57
- for axis in params.axes
58
- ]
55
+ self._gantry_mover.motor_axes_to_present_hardware_axes(params.axes)
59
56
  )
60
57
  return SuccessData(
61
58
  public=UnsafeEngageAxesResult(),
@@ -69,7 +66,7 @@ class UnsafeEngageAxes(
69
66
 
70
67
  commandType: UnsafeEngageAxesCommandType = "unsafe/engageAxes"
71
68
  params: UnsafeEngageAxesParams
72
- result: Optional[UnsafeEngageAxesResult]
69
+ result: Optional[UnsafeEngageAxesResult] = None
73
70
 
74
71
  _ImplementationCls: Type[
75
72
  UnsafeEngageAxesImplementation
@@ -192,7 +192,7 @@ class UnsafePlaceLabware(
192
192
 
193
193
  commandType: UnsafePlaceLabwareCommandType = "unsafe/placeLabware"
194
194
  params: UnsafePlaceLabwareParams
195
- result: Optional[UnsafePlaceLabwareResult]
195
+ result: Optional[UnsafePlaceLabwareResult] = None
196
196
 
197
197
  _ImplementationCls: Type[
198
198
  UnsafePlaceLabwareImplementation
@@ -61,7 +61,7 @@ class UnsafeUngripLabware(
61
61
 
62
62
  commandType: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware"
63
63
  params: UnsafeUngripLabwareParams
64
- result: Optional[UnsafeUngripLabwareResult]
64
+ result: Optional[UnsafeUngripLabwareResult] = None
65
65
 
66
66
  _ImplementationCls: Type[
67
67
  UnsafeUngripLabwareImplementation
@@ -58,10 +58,7 @@ class UpdatePositionEstimatorsImplementation(
58
58
  """Update axis position estimators from their encoders."""
59
59
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
60
60
  await ot3_hardware_api.update_axis_position_estimations(
61
- [
62
- self._gantry_mover.motor_axis_to_hardware_axis(axis)
63
- for axis in params.axes
64
- ]
61
+ self._gantry_mover.motor_axes_to_present_hardware_axes(params.axes)
65
62
  )
66
63
  return SuccessData(
67
64
  public=UpdatePositionEstimatorsResult(),
@@ -77,7 +74,7 @@ class UpdatePositionEstimators(
77
74
 
78
75
  commandType: UpdatePositionEstimatorsCommandType = "unsafe/updatePositionEstimators"
79
76
  params: UpdatePositionEstimatorsParams
80
- result: Optional[UpdatePositionEstimatorsResult]
77
+ result: Optional[UpdatePositionEstimatorsResult] = None
81
78
 
82
79
  _ImplementationCls: Type[
83
80
  UpdatePositionEstimatorsImplementation
@@ -1,8 +1,9 @@
1
1
  """Verify tip presence command request, result and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
3
4
 
4
5
  from pydantic import Field, BaseModel
5
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
6
7
  from typing_extensions import Literal
7
8
 
8
9
  from .pipetting_common import PipetteIdMixin
@@ -18,14 +19,20 @@ if TYPE_CHECKING:
18
19
  VerifyTipPresenceCommandType = Literal["verifyTipPresence"]
19
20
 
20
21
 
22
+ def _remove_default(s: dict[str, Any]) -> None:
23
+ s.pop("default", None)
24
+
25
+
21
26
  class VerifyTipPresenceParams(PipetteIdMixin):
22
27
  """Payload required for a VerifyTipPresence command."""
23
28
 
24
29
  expectedState: TipPresenceStatus = Field(
25
30
  ..., description="The expected tip presence status on the pipette."
26
31
  )
27
- followSingularSensor: Optional[InstrumentSensorId] = Field(
28
- default=None, description="The sensor id to follow if the other can be ignored."
32
+ followSingularSensor: InstrumentSensorId | SkipJsonSchema[None] = Field(
33
+ default=None,
34
+ description="The sensor id to follow if the other can be ignored.",
35
+ json_schema_extra=_remove_default,
29
36
  )
30
37
 
31
38
 
@@ -77,7 +84,7 @@ class VerifyTipPresence(
77
84
 
78
85
  commandType: VerifyTipPresenceCommandType = "verifyTipPresence"
79
86
  params: VerifyTipPresenceParams
80
- result: Optional[VerifyTipPresenceResult]
87
+ result: Optional[VerifyTipPresenceResult] = None
81
88
 
82
89
  _ImplementationCls: Type[
83
90
  VerifyTipPresenceImplementation
@@ -1,7 +1,9 @@
1
1
  """Wait for duration command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
+
3
5
  from pydantic import BaseModel, Field
4
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
@@ -14,13 +16,18 @@ if TYPE_CHECKING:
14
16
  WaitForDurationCommandType = Literal["waitForDuration"]
15
17
 
16
18
 
19
+ def _remove_default(s: dict[str, Any]) -> None:
20
+ s.pop("default", None)
21
+
22
+
17
23
  class WaitForDurationParams(BaseModel):
18
24
  """Payload required to pause the protocol."""
19
25
 
20
26
  seconds: float = Field(..., description="Duration, in seconds, to wait for.")
21
- message: Optional[str] = Field(
27
+ message: str | SkipJsonSchema[None] = Field(
22
28
  None,
23
29
  description="A user-facing message associated with the pause",
30
+ json_schema_extra=_remove_default,
24
31
  )
25
32
 
26
33
 
@@ -53,7 +60,7 @@ class WaitForDuration(
53
60
 
54
61
  commandType: WaitForDurationCommandType = "waitForDuration"
55
62
  params: WaitForDurationParams
56
- result: Optional[WaitForDurationResult]
63
+ result: Optional[WaitForDurationResult] = None
57
64
 
58
65
  _ImplementationCls: Type[
59
66
  WaitForDurationImplementation
@@ -1,7 +1,9 @@
1
1
  """Wait for resume command request, result, and implementation models."""
2
2
  from __future__ import annotations
3
+ from typing import TYPE_CHECKING, Optional, Type, Any
4
+
3
5
  from pydantic import BaseModel, Field
4
- from typing import TYPE_CHECKING, Optional, Type
6
+ from pydantic.json_schema import SkipJsonSchema
5
7
  from typing_extensions import Literal
6
8
 
7
9
  from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
@@ -16,12 +18,17 @@ if TYPE_CHECKING:
16
18
  WaitForResumeCommandType = Literal["waitForResume", "pause"]
17
19
 
18
20
 
21
+ def _remove_default(s: dict[str, Any]) -> None:
22
+ s.pop("default", None)
23
+
24
+
19
25
  class WaitForResumeParams(BaseModel):
20
26
  """Payload required to pause the protocol."""
21
27
 
22
- message: Optional[str] = Field(
28
+ message: str | SkipJsonSchema[None] = Field(
23
29
  None,
24
30
  description="A user-facing message associated with the pause",
31
+ json_schema_extra=_remove_default,
25
32
  )
26
33
 
27
34
 
@@ -54,7 +61,7 @@ class WaitForResume(
54
61
 
55
62
  commandType: WaitForResumeCommandType = "waitForResume"
56
63
  params: WaitForResumeParams
57
- result: Optional[WaitForResumeResult]
64
+ result: Optional[WaitForResumeResult] = None
58
65
 
59
66
  _ImplementationCls: Type[WaitForResumeImplementation] = WaitForResumeImplementation
60
67
 
@@ -11,6 +11,7 @@ from .exceptions import (
11
11
  PickUpTipTipNotAttachedError,
12
12
  TipAttachedError,
13
13
  CommandDoesNotExistError,
14
+ UnsupportedLabwareForActionError,
14
15
  LabwareNotLoadedError,
15
16
  LabwareNotLoadedOnModuleError,
16
17
  LabwareNotLoadedOnLabwareError,
@@ -18,12 +19,14 @@ from .exceptions import (
18
19
  LiquidDoesNotExistError,
19
20
  LabwareDefinitionDoesNotExistError,
20
21
  LabwareCannotBeStackedError,
22
+ LabwareCannotSitOnDeckError,
21
23
  LabwareIsInStackError,
22
24
  LabwareOffsetDoesNotExistError,
23
25
  LabwareIsNotTipRackError,
24
26
  LabwareIsTipRackError,
25
27
  LabwareIsAdapterError,
26
28
  TouchTipDisabledError,
29
+ TouchTipIncompatibleArgumentsError,
27
30
  WellDoesNotExistError,
28
31
  PipetteNotLoadedError,
29
32
  ModuleNotLoadedError,
@@ -78,6 +81,9 @@ from .exceptions import (
78
81
  OperationLocationNotInWellError,
79
82
  InvalidDispenseVolumeError,
80
83
  StorageLimitReachedError,
84
+ InvalidLiquidError,
85
+ LiquidClassDoesNotExistError,
86
+ LiquidClassRedefinitionError,
81
87
  )
82
88
 
83
89
  from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
@@ -99,14 +105,17 @@ __all__ = [
99
105
  "LabwareNotLoadedOnLabwareError",
100
106
  "LabwareNotOnDeckError",
101
107
  "LiquidDoesNotExistError",
108
+ "UnsupportedLabwareForActionError",
102
109
  "LabwareDefinitionDoesNotExistError",
103
110
  "LabwareCannotBeStackedError",
111
+ "LabwareCannotSitOnDeckError",
104
112
  "LabwareIsInStackError",
105
113
  "LabwareOffsetDoesNotExistError",
106
114
  "LabwareIsNotTipRackError",
107
115
  "LabwareIsTipRackError",
108
116
  "LabwareIsAdapterError",
109
117
  "TouchTipDisabledError",
118
+ "TouchTipIncompatibleArgumentsError",
110
119
  "WellDoesNotExistError",
111
120
  "PipetteNotLoadedError",
112
121
  "ModuleNotLoadedError",
@@ -138,6 +147,7 @@ __all__ = [
138
147
  "InvalidTargetSpeedError",
139
148
  "InvalidBlockVolumeError",
140
149
  "InvalidHoldTimeError",
150
+ "InvalidLiquidError",
141
151
  "InvalidWavelengthError",
142
152
  "CannotPerformModuleAction",
143
153
  "ResumeFromRecoveryNotAllowedError",
@@ -164,4 +174,6 @@ __all__ = [
164
174
  "OperationLocationNotInWellError",
165
175
  "InvalidDispenseVolumeError",
166
176
  "StorageLimitReachedError",
177
+ "LiquidClassDoesNotExistError",
178
+ "LiquidClassRedefinitionError",
167
179
  ]
@@ -4,7 +4,7 @@ from logging import getLogger
4
4
  from datetime import datetime
5
5
  from textwrap import dedent
6
6
  from typing import Any, Dict, Mapping, List, Type, Union, Optional, Sequence
7
- from pydantic import BaseModel, Field
7
+ from pydantic import BaseModel, Field, ConfigDict
8
8
  from opentrons_shared_data.errors.codes import ErrorCodes
9
9
  from .exceptions import ProtocolEngineError
10
10
  from opentrons_shared_data.errors.exceptions import EnumeratedError
@@ -29,7 +29,7 @@ class ErrorOccurrence(BaseModel):
29
29
  wrappedErrors = [
30
30
  cls.from_failed(id, createdAt, err) for err in error.wrapping
31
31
  ]
32
- return cls.construct(
32
+ return cls.model_construct(
33
33
  id=id,
34
34
  createdAt=createdAt,
35
35
  errorType=type(error).__name__,
@@ -39,6 +39,21 @@ class ErrorOccurrence(BaseModel):
39
39
  wrappedErrors=wrappedErrors,
40
40
  )
41
41
 
42
+ @staticmethod
43
+ def schema_extra(schema: Dict[str, Any], model: object) -> None:
44
+ """Append the schema to make the errorCode appear required.
45
+
46
+ `errorCode`, `wrappedErrors`, and `errorInfo` have defaults because they are not included in earlier
47
+ versions of this model, _and_ this model is loaded directly from
48
+ the on-robot store. That means that, without a default, it will
49
+ fail to parse. Once a default is defined, the automated schema will
50
+ mark this as a non-required field, which is misleading as this is
51
+ a response from the server to the client and it will always have an
52
+ errorCode defined. This hack is required because it informs the client
53
+ that it does not, in fact, have to account for a missing errorCode, wrappedError, or errorInfo.
54
+ """
55
+ schema["required"].extend(["errorCode", "wrappedErrors", "errorInfo"])
56
+
42
57
  id: str = Field(..., description="Unique identifier of this error occurrence.")
43
58
  createdAt: datetime = Field(..., description="When the error occurred.")
44
59
 
@@ -145,23 +160,7 @@ class ErrorOccurrence(BaseModel):
145
160
  default=[], description="Errors that may have caused this one."
146
161
  )
147
162
 
148
- class Config:
149
- """Customize configuration for this model."""
150
-
151
- @staticmethod
152
- def schema_extra(schema: Dict[str, Any], model: object) -> None:
153
- """Append the schema to make the errorCode appear required.
154
-
155
- `errorCode`, `wrappedErrors`, and `errorInfo` have defaults because they are not included in earlier
156
- versions of this model, _and_ this model is loaded directly from
157
- the on-robot store. That means that, without a default, it will
158
- fail to parse. Once a default is defined, the automated schema will
159
- mark this as a non-required field, which is misleading as this is
160
- a response from the server to the client and it will always have an
161
- errorCode defined. This hack is required because it informs the client
162
- that it does not, in fact, have to account for a missing errorCode, wrappedError, or errorInfo.
163
- """
164
- schema["required"].extend(["errorCode", "wrappedErrors", "errorInfo"])
163
+ model_config = ConfigDict(json_schema_extra=schema_extra)
165
164
 
166
165
 
167
166
  # TODO (tz, 7-12-23): move this to exceptions.py when we stop relaying on ErrorOccurrence.
@@ -180,4 +179,4 @@ class ProtocolCommandFailedError(ProtocolEngineError):
180
179
  self.original_error = original_error
181
180
 
182
181
 
183
- ErrorOccurrence.update_forward_refs()
182
+ ErrorOccurrence.model_rebuild()