opentrons 8.6.0a1__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 (600) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +501 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +183 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +552 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/simulator_setup.py +260 -0
  200. opentrons/hardware_control/thread_manager.py +431 -0
  201. opentrons/hardware_control/threaded_async_lock.py +97 -0
  202. opentrons/hardware_control/types.py +792 -0
  203. opentrons/hardware_control/util.py +234 -0
  204. opentrons/legacy_broker.py +53 -0
  205. opentrons/legacy_commands/__init__.py +1 -0
  206. opentrons/legacy_commands/commands.py +483 -0
  207. opentrons/legacy_commands/helpers.py +153 -0
  208. opentrons/legacy_commands/module_commands.py +215 -0
  209. opentrons/legacy_commands/protocol_commands.py +54 -0
  210. opentrons/legacy_commands/publisher.py +155 -0
  211. opentrons/legacy_commands/robot_commands.py +51 -0
  212. opentrons/legacy_commands/types.py +1115 -0
  213. opentrons/motion_planning/__init__.py +32 -0
  214. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  215. opentrons/motion_planning/deck_conflict.py +396 -0
  216. opentrons/motion_planning/errors.py +35 -0
  217. opentrons/motion_planning/types.py +42 -0
  218. opentrons/motion_planning/waypoints.py +218 -0
  219. opentrons/ordered_set.py +138 -0
  220. opentrons/protocol_api/__init__.py +105 -0
  221. opentrons/protocol_api/_liquid.py +157 -0
  222. opentrons/protocol_api/_liquid_properties.py +814 -0
  223. opentrons/protocol_api/_nozzle_layout.py +31 -0
  224. opentrons/protocol_api/_parameter_context.py +300 -0
  225. opentrons/protocol_api/_parameters.py +31 -0
  226. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  227. opentrons/protocol_api/_types.py +43 -0
  228. opentrons/protocol_api/config.py +23 -0
  229. opentrons/protocol_api/core/__init__.py +23 -0
  230. opentrons/protocol_api/core/common.py +33 -0
  231. opentrons/protocol_api/core/core_map.py +74 -0
  232. opentrons/protocol_api/core/engine/__init__.py +22 -0
  233. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  234. opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
  235. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  236. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  237. opentrons/protocol_api/core/engine/labware.py +238 -0
  238. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  239. opentrons/protocol_api/core/engine/module_core.py +1025 -0
  240. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  241. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  242. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  243. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  244. opentrons/protocol_api/core/engine/robot.py +139 -0
  245. opentrons/protocol_api/core/engine/stringify.py +74 -0
  246. opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
  247. opentrons/protocol_api/core/engine/well.py +241 -0
  248. opentrons/protocol_api/core/instrument.py +459 -0
  249. opentrons/protocol_api/core/labware.py +151 -0
  250. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  251. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  252. opentrons/protocol_api/core/legacy/deck.py +369 -0
  253. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  254. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  255. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  256. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  257. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  258. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  259. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  260. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  261. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  262. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  263. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  265. opentrons/protocol_api/core/module.py +484 -0
  266. opentrons/protocol_api/core/protocol.py +311 -0
  267. opentrons/protocol_api/core/robot.py +51 -0
  268. opentrons/protocol_api/core/well.py +116 -0
  269. opentrons/protocol_api/core/well_grid.py +45 -0
  270. opentrons/protocol_api/create_protocol_context.py +177 -0
  271. opentrons/protocol_api/deck.py +223 -0
  272. opentrons/protocol_api/disposal_locations.py +244 -0
  273. opentrons/protocol_api/instrument_context.py +3212 -0
  274. opentrons/protocol_api/labware.py +1579 -0
  275. opentrons/protocol_api/module_contexts.py +1425 -0
  276. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  277. opentrons/protocol_api/protocol_context.py +1688 -0
  278. opentrons/protocol_api/robot_context.py +303 -0
  279. opentrons/protocol_api/validation.py +761 -0
  280. opentrons/protocol_engine/__init__.py +155 -0
  281. opentrons/protocol_engine/actions/__init__.py +65 -0
  282. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  283. opentrons/protocol_engine/actions/action_handler.py +13 -0
  284. opentrons/protocol_engine/actions/actions.py +302 -0
  285. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  286. opentrons/protocol_engine/clients/__init__.py +5 -0
  287. opentrons/protocol_engine/clients/sync_client.py +174 -0
  288. opentrons/protocol_engine/clients/transports.py +197 -0
  289. opentrons/protocol_engine/commands/__init__.py +757 -0
  290. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  296. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  297. opentrons/protocol_engine/commands/aspirate.py +244 -0
  298. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  299. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  300. opentrons/protocol_engine/commands/blow_out.py +146 -0
  301. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  302. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  303. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  306. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  307. opentrons/protocol_engine/commands/command.py +308 -0
  308. opentrons/protocol_engine/commands/command_unions.py +974 -0
  309. opentrons/protocol_engine/commands/comment.py +57 -0
  310. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  311. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  312. opentrons/protocol_engine/commands/custom.py +67 -0
  313. opentrons/protocol_engine/commands/dispense.py +194 -0
  314. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  315. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  316. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  317. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  318. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  319. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  320. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  321. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  322. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  323. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  324. opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
  325. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  326. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  327. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  328. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  329. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  330. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  331. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  333. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  334. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  336. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  337. opentrons/protocol_engine/commands/home.py +100 -0
  338. opentrons/protocol_engine/commands/identify_module.py +86 -0
  339. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  340. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  341. opentrons/protocol_engine/commands/load_labware.py +210 -0
  342. opentrons/protocol_engine/commands/load_lid.py +154 -0
  343. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  344. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  345. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  346. opentrons/protocol_engine/commands/load_module.py +223 -0
  347. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  348. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  349. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  350. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  351. opentrons/protocol_engine/commands/move_labware.py +546 -0
  352. opentrons/protocol_engine/commands/move_relative.py +102 -0
  353. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  355. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  356. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  357. opentrons/protocol_engine/commands/movement_common.py +338 -0
  358. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  359. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  360. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  361. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  362. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  363. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  364. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  365. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  366. opentrons/protocol_engine/commands/robot/common.py +18 -0
  367. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  369. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  370. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  371. opentrons/protocol_engine/commands/save_position.py +109 -0
  372. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  373. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  374. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  375. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  376. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  377. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  378. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  379. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  380. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  381. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  384. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  386. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  388. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  390. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  391. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  392. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  401. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  402. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  403. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  404. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  405. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  406. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  407. opentrons/protocol_engine/engine_support.py +28 -0
  408. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  409. opentrons/protocol_engine/errors/__init__.py +191 -0
  410. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  411. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  412. opentrons/protocol_engine/execution/__init__.py +50 -0
  413. opentrons/protocol_engine/execution/command_executor.py +216 -0
  414. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  415. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  416. opentrons/protocol_engine/execution/equipment.py +819 -0
  417. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  418. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  419. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  420. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  421. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  422. opentrons/protocol_engine/execution/movement.py +349 -0
  423. opentrons/protocol_engine/execution/pipetting.py +607 -0
  424. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  425. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  426. opentrons/protocol_engine/execution/run_control.py +33 -0
  427. opentrons/protocol_engine/execution/status_bar.py +34 -0
  428. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  429. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  430. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  431. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  432. opentrons/protocol_engine/notes/__init__.py +17 -0
  433. opentrons/protocol_engine/notes/notes.py +59 -0
  434. opentrons/protocol_engine/plugins.py +104 -0
  435. opentrons/protocol_engine/protocol_engine.py +683 -0
  436. opentrons/protocol_engine/resources/__init__.py +26 -0
  437. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  438. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  439. opentrons/protocol_engine/resources/file_provider.py +161 -0
  440. opentrons/protocol_engine/resources/fixture_validation.py +58 -0
  441. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  442. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  443. opentrons/protocol_engine/resources/model_utils.py +32 -0
  444. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  445. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  446. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  447. opentrons/protocol_engine/slot_standardization.py +128 -0
  448. opentrons/protocol_engine/state/__init__.py +1 -0
  449. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  450. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  451. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  452. opentrons/protocol_engine/state/_move_types.py +83 -0
  453. opentrons/protocol_engine/state/_well_math.py +193 -0
  454. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  455. opentrons/protocol_engine/state/command_history.py +309 -0
  456. opentrons/protocol_engine/state/commands.py +1158 -0
  457. opentrons/protocol_engine/state/config.py +39 -0
  458. opentrons/protocol_engine/state/files.py +57 -0
  459. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  460. opentrons/protocol_engine/state/geometry.py +2359 -0
  461. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  462. opentrons/protocol_engine/state/labware.py +1459 -0
  463. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  464. opentrons/protocol_engine/state/liquids.py +73 -0
  465. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  466. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  467. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  468. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  469. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  471. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  472. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  473. opentrons/protocol_engine/state/modules.py +1500 -0
  474. opentrons/protocol_engine/state/motion.py +373 -0
  475. opentrons/protocol_engine/state/pipettes.py +905 -0
  476. opentrons/protocol_engine/state/state.py +421 -0
  477. opentrons/protocol_engine/state/state_summary.py +36 -0
  478. opentrons/protocol_engine/state/tips.py +420 -0
  479. opentrons/protocol_engine/state/update_types.py +904 -0
  480. opentrons/protocol_engine/state/wells.py +290 -0
  481. opentrons/protocol_engine/types/__init__.py +308 -0
  482. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  483. opentrons/protocol_engine/types/command_annotations.py +53 -0
  484. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  485. opentrons/protocol_engine/types/execution.py +96 -0
  486. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  487. opentrons/protocol_engine/types/instrument.py +47 -0
  488. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  489. opentrons/protocol_engine/types/labware.py +131 -0
  490. opentrons/protocol_engine/types/labware_movement.py +22 -0
  491. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  492. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  493. opentrons/protocol_engine/types/liquid.py +40 -0
  494. opentrons/protocol_engine/types/liquid_class.py +59 -0
  495. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  496. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  497. opentrons/protocol_engine/types/location.py +194 -0
  498. opentrons/protocol_engine/types/module.py +303 -0
  499. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  500. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  501. opentrons/protocol_engine/types/tip.py +18 -0
  502. opentrons/protocol_engine/types/util.py +21 -0
  503. opentrons/protocol_engine/types/well_position.py +124 -0
  504. opentrons/protocol_reader/__init__.py +37 -0
  505. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  506. opentrons/protocol_reader/file_format_validator.py +152 -0
  507. opentrons/protocol_reader/file_hasher.py +27 -0
  508. opentrons/protocol_reader/file_identifier.py +284 -0
  509. opentrons/protocol_reader/file_reader_writer.py +90 -0
  510. opentrons/protocol_reader/input_file.py +16 -0
  511. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  512. opentrons/protocol_reader/protocol_reader.py +188 -0
  513. opentrons/protocol_reader/protocol_source.py +124 -0
  514. opentrons/protocol_reader/role_analyzer.py +86 -0
  515. opentrons/protocol_runner/__init__.py +26 -0
  516. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  517. opentrons/protocol_runner/json_file_reader.py +55 -0
  518. opentrons/protocol_runner/json_translator.py +314 -0
  519. opentrons/protocol_runner/legacy_command_mapper.py +848 -0
  520. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  521. opentrons/protocol_runner/protocol_runner.py +530 -0
  522. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  523. opentrons/protocol_runner/run_orchestrator.py +496 -0
  524. opentrons/protocol_runner/task_queue.py +95 -0
  525. opentrons/protocols/__init__.py +6 -0
  526. opentrons/protocols/advanced_control/__init__.py +0 -0
  527. opentrons/protocols/advanced_control/common.py +38 -0
  528. opentrons/protocols/advanced_control/mix.py +60 -0
  529. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  530. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  531. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  532. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  533. opentrons/protocols/api_support/__init__.py +0 -0
  534. opentrons/protocols/api_support/constants.py +8 -0
  535. opentrons/protocols/api_support/deck_type.py +110 -0
  536. opentrons/protocols/api_support/definitions.py +18 -0
  537. opentrons/protocols/api_support/instrument.py +151 -0
  538. opentrons/protocols/api_support/labware_like.py +233 -0
  539. opentrons/protocols/api_support/tip_tracker.py +175 -0
  540. opentrons/protocols/api_support/types.py +32 -0
  541. opentrons/protocols/api_support/util.py +403 -0
  542. opentrons/protocols/bundle.py +89 -0
  543. opentrons/protocols/duration/__init__.py +4 -0
  544. opentrons/protocols/duration/errors.py +5 -0
  545. opentrons/protocols/duration/estimator.py +628 -0
  546. opentrons/protocols/execution/__init__.py +0 -0
  547. opentrons/protocols/execution/dev_types.py +181 -0
  548. opentrons/protocols/execution/errors.py +40 -0
  549. opentrons/protocols/execution/execute.py +84 -0
  550. opentrons/protocols/execution/execute_json_v3.py +275 -0
  551. opentrons/protocols/execution/execute_json_v4.py +359 -0
  552. opentrons/protocols/execution/execute_json_v5.py +28 -0
  553. opentrons/protocols/execution/execute_python.py +169 -0
  554. opentrons/protocols/execution/json_dispatchers.py +87 -0
  555. opentrons/protocols/execution/types.py +7 -0
  556. opentrons/protocols/geometry/__init__.py +0 -0
  557. opentrons/protocols/geometry/planning.py +297 -0
  558. opentrons/protocols/labware.py +312 -0
  559. opentrons/protocols/models/__init__.py +0 -0
  560. opentrons/protocols/models/json_protocol.py +679 -0
  561. opentrons/protocols/parameters/__init__.py +0 -0
  562. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  563. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  564. opentrons/protocols/parameters/exceptions.py +34 -0
  565. opentrons/protocols/parameters/parameter_definition.py +272 -0
  566. opentrons/protocols/parameters/types.py +17 -0
  567. opentrons/protocols/parameters/validation.py +267 -0
  568. opentrons/protocols/parse.py +671 -0
  569. opentrons/protocols/types.py +159 -0
  570. opentrons/py.typed +0 -0
  571. opentrons/resources/scripts/lpc21isp +0 -0
  572. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  573. opentrons/simulate.py +1065 -0
  574. opentrons/system/__init__.py +6 -0
  575. opentrons/system/camera.py +51 -0
  576. opentrons/system/log_control.py +59 -0
  577. opentrons/system/nmcli.py +856 -0
  578. opentrons/system/resin.py +24 -0
  579. opentrons/system/smoothie_update.py +15 -0
  580. opentrons/system/wifi.py +204 -0
  581. opentrons/tools/__init__.py +0 -0
  582. opentrons/tools/args_handler.py +22 -0
  583. opentrons/tools/write_pipette_memory.py +157 -0
  584. opentrons/types.py +618 -0
  585. opentrons/util/__init__.py +1 -0
  586. opentrons/util/async_helpers.py +166 -0
  587. opentrons/util/broker.py +84 -0
  588. opentrons/util/change_notifier.py +47 -0
  589. opentrons/util/entrypoint_util.py +278 -0
  590. opentrons/util/get_union_elements.py +26 -0
  591. opentrons/util/helpers.py +6 -0
  592. opentrons/util/linal.py +178 -0
  593. opentrons/util/logging_config.py +265 -0
  594. opentrons/util/logging_queue_handler.py +61 -0
  595. opentrons/util/performance_helpers.py +157 -0
  596. opentrons-8.6.0a1.dist-info/METADATA +37 -0
  597. opentrons-8.6.0a1.dist-info/RECORD +600 -0
  598. opentrons-8.6.0a1.dist-info/WHEEL +4 -0
  599. opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
  600. opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,671 @@
1
+ """
2
+ opentrons.protocols.parse: functions and state for parsing protocols
3
+ """
4
+
5
+ import ast
6
+ import enum
7
+ import functools
8
+ import itertools
9
+ import json
10
+ import logging
11
+ import re
12
+ import traceback
13
+ from io import BytesIO
14
+ from zipfile import ZipFile
15
+ from typing import Any, Dict, Optional, Union, Tuple, TYPE_CHECKING
16
+
17
+ import jsonschema # type: ignore
18
+
19
+ from opentrons_shared_data.labware import load_schema as load_labware_schema
20
+ from opentrons_shared_data.protocol import (
21
+ Schema as JSONProtocolSchema,
22
+ load_schema as load_protocol_schema,
23
+ )
24
+ from opentrons_shared_data.robot.types import RobotType
25
+
26
+ from opentrons.ordered_set import OrderedSet
27
+
28
+ from .api_support.definitions import MIN_SUPPORTED_VERSION_FOR_FLEX
29
+ from .api_support.types import APIVersion
30
+ from .types import (
31
+ RUN_FUNCTION_MESSAGE,
32
+ Protocol,
33
+ PythonProtocol,
34
+ JsonProtocol,
35
+ StaticPythonInfo,
36
+ PythonProtocolMetadata,
37
+ PythonProtocolRequirements,
38
+ MalformedPythonProtocolError,
39
+ ApiDeprecationError,
40
+ )
41
+ from .bundle import extract_bundle
42
+
43
+ if TYPE_CHECKING:
44
+ from opentrons_shared_data.labware.types import LabwareDefinition
45
+ from opentrons_shared_data.protocol.types import JsonProtocol as JsonProtocolDef
46
+
47
+ MODULE_LOG = logging.getLogger(__name__)
48
+
49
+ # match e.g. "2.0" but not "hi", "2", "2.0.1"
50
+ API_VERSION_RE = re.compile(r"^(\d+)\.(\d+)$")
51
+ MAX_SUPPORTED_JSON_SCHEMA_VERSION = 5
52
+ API_VERSION_FOR_JSON_V5_AND_BELOW = APIVersion(2, 8)
53
+
54
+
55
+ class JSONSchemaVersionTooNewError(RuntimeError):
56
+ def __init__(self, attempted_schema_version: int) -> None:
57
+ super().__init__(attempted_schema_version)
58
+ self.attempted_schema_version = attempted_schema_version
59
+
60
+ def __str__(self) -> str:
61
+ return (
62
+ f"The protocol you are trying to open is a"
63
+ f" JSONv{self.attempted_schema_version} protocol,"
64
+ f" which is not supported by this software version."
65
+ )
66
+
67
+
68
+ class PythonParseMode(enum.Enum):
69
+ """Configure optional rules for when `opentrons.protocols.parse.parse()` parses Python files.
70
+
71
+ This is an August 2023 temporary measure to let us add more validation to Python files without
72
+ disrupting our many internal users testing the Flex.
73
+ https://opentrons.atlassian.net/browse/RSS-306
74
+ """
75
+
76
+ NORMAL = enum.auto()
77
+ """Enforce the normal, strict, officially customer-facing rules.
78
+
79
+ You should use this mode when handling protocol files that are not already on a robot.
80
+ """
81
+
82
+ ALLOW_LEGACY_METADATA_AND_REQUIREMENTS = enum.auto()
83
+ """Disable enforcement of certain rules, allowing more questionable protocol files.
84
+
85
+ You should use this mode when handling protocol files that are already stored on a robot.
86
+
87
+ Certain rules were added late in Flex development, after protocol files that disobey them
88
+ were already put on internal testing robots. robot-server and the app generally do not
89
+ gracefully handle it when files that are already on a robot suddenly fail to parse; it currently
90
+ requires a disruptive factory-reset to get the robot usable again. To avoid making all our
91
+ internal users do that, we have this mode, as a temporary measure.
92
+
93
+ The specific differences from normal mode are:
94
+
95
+ 1. Normally, if a protocol is for the Flex, it's an error to specify `apiLevel` 2.14 or older.
96
+ In this mode, the parser will allow those older `apiLevel`s. Actually running one of those
97
+ protocols may happen to work, or it may have obscure problems.
98
+
99
+ 2. Normally, it's an error to specify unrecognized fields in the `requirements` dict.
100
+ In this mode, the parser ignores unrecognized fields.
101
+
102
+ 3. Normally, it's an error to specify `apiLevel` in both the `metadata` and `requirements`
103
+ dicts simultaneously. You need to choose just one. In this mode, it's allowed, and
104
+ `requirements` will override `metadata` if they're different.
105
+ """
106
+
107
+
108
+ def _validate_v2_ast(protocol_ast: ast.Module) -> None:
109
+ defs = [fdef for fdef in protocol_ast.body if isinstance(fdef, ast.FunctionDef)]
110
+ rundefs = [fdef for fdef in defs if fdef.name == "run"]
111
+ # There must be precisely 1 one run function
112
+ if len(rundefs) > 1:
113
+ lines = [str(d.lineno) for d in rundefs]
114
+ linestr = ", ".join(lines)
115
+ raise MalformedPythonProtocolError(
116
+ short_message=f"More than one run function is defined (lines {linestr}).",
117
+ long_additional_message=RUN_FUNCTION_MESSAGE,
118
+ )
119
+ if not rundefs:
120
+ raise MalformedPythonProtocolError(
121
+ short_message="No function 'run(ctx)' defined",
122
+ long_additional_message=RUN_FUNCTION_MESSAGE,
123
+ )
124
+ if _has_api_v1_imports(protocol_ast):
125
+ raise MalformedPythonProtocolError(
126
+ short_message=(
127
+ "Protocol API v1 modules such as robot, instruments, and labware "
128
+ "may not be imported in Protocol API V2 protocols"
129
+ )
130
+ )
131
+
132
+
133
+ def _validate_v2_static_info(static_info: StaticPythonInfo) -> None:
134
+ # Unlike the metadata dict, in the requirements dict, we only allow you to specify
135
+ # officially known keys. This lets us add new keys in the future without having to worry about
136
+ # conflicting with other random junk that people might have put there, and it prevents silly
137
+ # typos from causing confusing downstream problems.
138
+ allowed_requirements_keys = {
139
+ "apiLevel",
140
+ "robotType",
141
+ # NOTE(mm, 2023-08-08): If we add new allowed keys to this dict in the future,
142
+ # we should probably gate them behind new apiLevels.
143
+ }
144
+ # OrderedSet just to make the error message deterministic and easy to test.
145
+ actual_requirements_keys = OrderedSet((static_info.requirements or {}).keys())
146
+ unexpected_requirements_keys = actual_requirements_keys - allowed_requirements_keys
147
+ if unexpected_requirements_keys:
148
+ raise MalformedPythonProtocolError(
149
+ f"Unrecognized {'key' if len(unexpected_requirements_keys) == 1 else 'keys'}"
150
+ f" in requirements dict:"
151
+ f" {', '.join(repr(k) for k in unexpected_requirements_keys)}."
152
+ f" Allowed keys:"
153
+ f" {', '.join(repr(k) for k in allowed_requirements_keys)}."
154
+ )
155
+
156
+ api_level_in_metadata = "apiLevel" in (static_info.metadata or {})
157
+ api_level_in_requirements = "apiLevel" in (static_info.requirements or {})
158
+ if api_level_in_metadata and api_level_in_requirements:
159
+ # If a user does this, it's almost certainly a mistake. Forbid it to avoid complexity in
160
+ # which dict takes precedence, and in what happens when you upload to an old software
161
+ # version that only knows about the metadata dict, not the requirements dict.
162
+ raise MalformedPythonProtocolError(
163
+ "You may only put apiLevel in the metadata dict or the requirements dict, not both."
164
+ )
165
+
166
+
167
+ def _validate_robot_type_at_version(robot_type: RobotType, version: APIVersion) -> None:
168
+ if robot_type == "OT-3 Standard" and version < MIN_SUPPORTED_VERSION_FOR_FLEX:
169
+ raise MalformedPythonProtocolError(
170
+ short_message=(
171
+ f"The Opentrons Flex only supports apiLevel"
172
+ f" {MIN_SUPPORTED_VERSION_FOR_FLEX} or newer."
173
+ )
174
+ )
175
+
176
+
177
+ def version_from_string(vstr: str) -> APIVersion:
178
+ """Parse an API version from a string
179
+
180
+ :param str vstr: The version string to parse
181
+ :returns APIVersion: The parsed version
182
+ :raises ValueError: if the version string is the wrong format
183
+ """
184
+ matches = API_VERSION_RE.match(vstr)
185
+ if not matches:
186
+ raise MalformedPythonProtocolError(
187
+ short_message=(
188
+ f"apiLevel {vstr} is incorrectly formatted. It should be "
189
+ "major.minor, where both major and minor are numbers."
190
+ )
191
+ )
192
+ return APIVersion(major=int(matches.group(1)), minor=int(matches.group(2)))
193
+
194
+
195
+ def _parse_json(
196
+ protocol_contents: Union[str, bytes], filename: Optional[str] = None
197
+ ) -> JsonProtocol:
198
+ """Parse a protocol known or at least suspected to be json"""
199
+ protocol_json = json.loads(protocol_contents)
200
+ version, validated = validate_json(protocol_json)
201
+ return JsonProtocol(
202
+ text=protocol_contents,
203
+ filename=filename,
204
+ contents=validated,
205
+ schema_version=version,
206
+ api_level=API_VERSION_FOR_JSON_V5_AND_BELOW,
207
+ metadata=validated["metadata"],
208
+ robot_type=validated["robot"]["model"],
209
+ )
210
+
211
+
212
+ def _parse_python(
213
+ protocol_contents: Union[str, bytes],
214
+ python_parse_mode: PythonParseMode,
215
+ filename: Optional[str] = None,
216
+ bundled_labware: Optional[Dict[str, "LabwareDefinition"]] = None,
217
+ bundled_data: Optional[Dict[str, bytes]] = None,
218
+ bundled_python: Optional[Dict[str, str]] = None,
219
+ extra_labware: Optional[Dict[str, "LabwareDefinition"]] = None,
220
+ ) -> PythonProtocol:
221
+ """Parse a protocol known or at least suspected to be python"""
222
+ if filename is None:
223
+ # The fallback "<protocol>" needs to match what opentrons.protocols.execution.execute_python
224
+ # looks for when it extracts tracebacks.
225
+ ast_filename = "<protocol>"
226
+ elif filename.endswith(".zip"):
227
+ # The extension ".zip" and the fallback "protocol.ot2.py" need to match what
228
+ # opentrons.protocols.execution.execute_python looks for when it extracts tracebacks.
229
+ ast_filename = "protocol.ot2.py"
230
+ else:
231
+ ast_filename = filename
232
+
233
+ # todo(mm, 2021-09-13): By default, ast.parse will inherit compiler options
234
+ # and future features from this module. This may not be appropriate.
235
+ # Investigate switching to compile() with dont_inherit=True.
236
+ try:
237
+ parsed = ast.parse(protocol_contents, filename=ast_filename)
238
+ except SyntaxError as syntax_error:
239
+ raise MalformedPythonProtocolError(
240
+ short_message=str(syntax_error),
241
+ # Get Python's nice syntax error message with carets pointing to where in the line
242
+ # had the problem.
243
+ long_additional_message="\n".join(
244
+ traceback.format_exception_only(type(syntax_error), syntax_error)
245
+ ),
246
+ ) from syntax_error
247
+ except ValueError as null_bytes_error:
248
+ # ast.parse() raises SyntaxError for most errors,
249
+ # but ValueError if the source contains null bytes.
250
+ raise MalformedPythonProtocolError(short_message=str(null_bytes_error))
251
+
252
+ static_info = _extract_static_python_info(parsed)
253
+ protocol = compile(parsed, filename=ast_filename, mode="exec")
254
+ version = _get_version(static_info, parsed, ast_filename)
255
+ robot_type = _robot_type_from_static_python_info(static_info)
256
+
257
+ if version >= APIVersion(2, 0):
258
+ _validate_v2_ast(parsed)
259
+ if python_parse_mode != PythonParseMode.ALLOW_LEGACY_METADATA_AND_REQUIREMENTS:
260
+ _validate_v2_static_info(static_info)
261
+ _validate_robot_type_at_version(robot_type, version)
262
+ else:
263
+ raise ApiDeprecationError(version)
264
+
265
+ result = PythonProtocol(
266
+ text=protocol_contents,
267
+ filename=filename,
268
+ contents=protocol,
269
+ metadata=static_info.metadata,
270
+ api_level=version,
271
+ robot_type=robot_type,
272
+ bundled_labware=bundled_labware,
273
+ bundled_data=bundled_data,
274
+ bundled_python=bundled_python,
275
+ extra_labware=extra_labware,
276
+ )
277
+
278
+ return result
279
+
280
+
281
+ def _parse_bundle(
282
+ bundle: ZipFile, python_parse_mode: PythonParseMode, filename: Optional[str] = None
283
+ ) -> PythonProtocol:
284
+ """Parse a bundled Python protocol"""
285
+ contents = extract_bundle(bundle)
286
+
287
+ result = _parse_python(
288
+ protocol_contents=contents.protocol,
289
+ python_parse_mode=python_parse_mode,
290
+ filename=filename,
291
+ bundled_labware=contents.bundled_labware,
292
+ bundled_data=contents.bundled_data,
293
+ bundled_python=contents.bundled_python,
294
+ )
295
+
296
+ if result.api_level < APIVersion(2, 0):
297
+ raise MalformedPythonProtocolError(
298
+ short_message=f"Bundled protocols must use Protocol API v2, got {result.api_level}."
299
+ )
300
+
301
+ return result
302
+
303
+
304
+ def parse(
305
+ protocol_file: Union[str, bytes],
306
+ filename: Optional[str] = None,
307
+ extra_labware: Optional[Dict[str, "LabwareDefinition"]] = None,
308
+ extra_data: Optional[Dict[str, bytes]] = None,
309
+ # TODO(mm, 2023-08-10): Remove python_parse_mode after the Flex launch, when the malformed
310
+ # protocols are no longer on any robots. https://opentrons.atlassian.net/browse/RSS-306
311
+ python_parse_mode: PythonParseMode = PythonParseMode.NORMAL,
312
+ ) -> Protocol:
313
+ """Parse a protocol from text.
314
+
315
+ :param protocol_file: The protocol file, or for single-file protocols, a
316
+ string of the protocol contents.
317
+ :param filename: The name of the protocol. Optional, but helps with
318
+ deducing the kind of protocol (e.g. if it ends with
319
+ '.json' we can treat it like json)
320
+ :param extra_labware: Any extra labware defs that should be given to the
321
+ protocol. Ignored if the protocol is json or zipped
322
+ python.
323
+ :param extra_data: Any extra data files that should be provided to the
324
+ protocol. Ignored if the protocol is json or zipped
325
+ python.
326
+ :param python_parse_mode: See `PythonParseMode`.
327
+ :return types.Protocol: The protocol holder, a named tuple that stores the
328
+ data in the protocol for later simulation or
329
+ execution.
330
+ """
331
+ if filename and filename.endswith(".zip"):
332
+ if not isinstance(protocol_file, bytes):
333
+ raise RuntimeError(
334
+ "Please update your Run App version to support uploading a .zip file"
335
+ )
336
+
337
+ with ZipFile(BytesIO(protocol_file)) as bundle:
338
+ result = _parse_bundle(
339
+ bundle=bundle, python_parse_mode=python_parse_mode, filename=filename
340
+ )
341
+ return result
342
+ else:
343
+ if filename and filename.endswith(".json"):
344
+ return _parse_json(protocol_file, filename)
345
+ elif filename and filename.endswith(".py"):
346
+ return _parse_python(
347
+ protocol_contents=protocol_file,
348
+ python_parse_mode=python_parse_mode,
349
+ filename=filename,
350
+ extra_labware=extra_labware,
351
+ bundled_data=extra_data,
352
+ )
353
+
354
+ # our jsonschema says the top level json kind is object so we can
355
+ # rely on it starting with a { if it's valid. that could either be
356
+ # a string or bytes.
357
+ #
358
+ # if it's a string, then if the protocol file starts with a { and
359
+ # we do protocol_file[0] then we get the string "{".
360
+ #
361
+ # if it's a bytes, then if the protocol file starts with the ascii or
362
+ # utf-8 representation of { and we do protocol_file[0] we get 123,
363
+ # because while single elements of strings are strings, single elements
364
+ # of bytes are the byte value as a number.
365
+ #
366
+ # to get that number we could either use ord() or do what we do here
367
+ # which I think is a little nicer, if any of the above can be called
368
+ # "nice".
369
+ if protocol_file and protocol_file[0] in ("{", b"{"[0]):
370
+ return _parse_json(protocol_file, filename)
371
+ else:
372
+ return _parse_python(
373
+ protocol_contents=protocol_file,
374
+ python_parse_mode=python_parse_mode,
375
+ filename=filename,
376
+ extra_labware=extra_labware,
377
+ bundled_data=extra_data,
378
+ )
379
+
380
+
381
+ def _extract_static_python_info(parsed: ast.Module) -> StaticPythonInfo:
382
+ """Extract statically analyzable info from a Python protocol, like its metadata.
383
+
384
+ Raises:
385
+ ValueError: If the places that we expect to be statically analyzable are
386
+ actually not, or if they contain unsupported types.
387
+ """
388
+ extracted_metadata: PythonProtocolMetadata = None
389
+ extracted_requirements: PythonProtocolRequirements = None
390
+
391
+ assignments = (obj for obj in parsed.body if isinstance(obj, ast.Assign))
392
+ for assignment in assignments:
393
+ target = assignment.targets[0]
394
+ assigned_value = assignment.value
395
+
396
+ if isinstance(target, ast.Name) and isinstance(assigned_value, ast.Dict):
397
+ target_name = target.id
398
+ if target_name == "metadata":
399
+ extracted_metadata = _extract_static_dict(
400
+ static_dict=assigned_value, name="metadata"
401
+ )
402
+
403
+ # `requirements` was added later. This is technically a breaking change if
404
+ # anyone happened to declare a module-level `requirements` variable in their
405
+ # protocol, since we'll now raise an error if it isn't statically parseable
406
+ # or if it contains unsupported types.
407
+ elif target_name == "requirements":
408
+ extracted_requirements = _extract_static_dict(
409
+ static_dict=assigned_value, name="requirements"
410
+ )
411
+
412
+ return StaticPythonInfo(
413
+ metadata=extracted_metadata, requirements=extracted_requirements
414
+ )
415
+
416
+
417
+ def _extract_static_dict(static_dict: ast.Dict, name: str) -> Dict[str, str]:
418
+ """Statically read a `metadata`-like dict from a Python Protocol API file.
419
+
420
+ Args:
421
+ static_dict: The AST node representing the dict.
422
+ name: The name of the dict in the user's Python source code,
423
+ for error reporting.
424
+
425
+ Raises:
426
+ MalformedPythonError: If the dict is too complex for this function to understand
427
+ statically, or if it contains unsupported types.
428
+ """
429
+ try:
430
+ evaluated_literal = ast.literal_eval(static_dict)
431
+ except ValueError as exception:
432
+ # Undocumented, but ast.literal_eval() seems to raise ValueError for
433
+ # expressions that aren't statically or "safely" evaluable, like
434
+ # `{"o": object()}` or `{"s": "abc"[0]}`.
435
+ raise MalformedPythonProtocolError(
436
+ short_message=(
437
+ f"Could not read the contents of the {name} dict."
438
+ f" Make sure it doesn't contain any complex expressions, such as"
439
+ f" function calls or array indexings."
440
+ )
441
+ ) from exception
442
+
443
+ # ast.literal_eval() is typed as returning Any, but we're pretty sure it
444
+ # should return a dict in this case because we passed it an ast.Dict.
445
+ assert isinstance(evaluated_literal, dict)
446
+
447
+ # Make sure we don't return anything outside of our declared return type.
448
+ for key, value in evaluated_literal.items():
449
+ if not isinstance(key, str):
450
+ raise MalformedPythonProtocolError(
451
+ short_message=(
452
+ f'Keys in the {name} dict must be strings, but key "{key}"'
453
+ f' has type "{type(key).__name__}".'
454
+ )
455
+ )
456
+ if not isinstance(value, str):
457
+ raise MalformedPythonProtocolError(
458
+ short_message=(
459
+ f'Values in the {name} dict must be strings, but value "{value}"'
460
+ f' has type "{type(value).__name__}".'
461
+ )
462
+ )
463
+
464
+ return evaluated_literal
465
+
466
+
467
+ @functools.lru_cache(1)
468
+ def _has_api_v1_imports(parsed: ast.Module) -> bool:
469
+ """Return whether a Python protocol has import statements specific to PAPIv1."""
470
+ # Imports in the form of `import opentrons.robot` will have an entry in
471
+ # parsed.body[i].names[j].name in the form "opentrons.robot". Find those
472
+ # imports and transform them to strip away the 'opentrons.' part.
473
+ ot_imports = [
474
+ ".".join(name.name.split(".")[1:])
475
+ for name in itertools.chain.from_iterable(
476
+ [obj.names for obj in parsed.body if isinstance(obj, ast.Import)]
477
+ )
478
+ if "opentrons" in name.name
479
+ ]
480
+
481
+ # Imports in the form of `from opentrons import robot` (with or without an
482
+ # `as ___` statement) will have an entry in parsed.body[i].module
483
+ # containing "opentrons"
484
+ ot_from_imports = [
485
+ name.name
486
+ for name in itertools.chain.from_iterable(
487
+ [
488
+ obj.names
489
+ for obj in parsed.body
490
+ if isinstance(obj, ast.ImportFrom)
491
+ and obj.module
492
+ and "opentrons" in obj.module
493
+ ]
494
+ )
495
+ ]
496
+
497
+ # If any of these are populated, filter for entries with v1-specific terms
498
+ opentrons_imports = set(ot_imports + ot_from_imports)
499
+ v1_markers = set(("robot", "instruments", "modules", "containers"))
500
+ return bool(v1_markers.intersection(opentrons_imports))
501
+
502
+
503
+ def _version_from_static_python_info(
504
+ static_python_info: StaticPythonInfo,
505
+ ) -> Optional[APIVersion]:
506
+ """Get an explicitly specified apiLevel from static info, if we can.
507
+
508
+ If the protocol doesn't declare apiLevel at all, return None.
509
+ If the protocol declares apiLevel incorrectly, raise a ValueError.
510
+ """
511
+ from_requirements = (static_python_info.requirements or {}).get("apiLevel", None)
512
+ from_metadata = (static_python_info.metadata or {}).get("apiLevel", None)
513
+
514
+ requested_level = from_requirements or from_metadata
515
+ if requested_level is None:
516
+ return None
517
+ elif requested_level == "1":
518
+ # TODO(mm, 2022-10-21): Can we safely move this special case to
519
+ # version_from_string()?
520
+ return APIVersion(1, 0)
521
+ else:
522
+ return version_from_string(requested_level)
523
+
524
+
525
+ def robot_type_from_python_identifier(python_robot_type: str) -> RobotType:
526
+ if python_robot_type == "OT-2":
527
+ return "OT-2 Standard"
528
+ # Allow "OT-3" as a deprecated alias of "Flex" to support internal-to-Opentrons Python protocols
529
+ # that were written before the "Flex" name existed.
530
+ elif python_robot_type in ("Flex", "OT-3"):
531
+ return "OT-3 Standard"
532
+ else:
533
+ raise MalformedPythonProtocolError(
534
+ short_message=f"robotType must be 'OT-2' or 'Flex', not {repr(python_robot_type)}."
535
+ )
536
+
537
+
538
+ def _robot_type_from_static_python_info(
539
+ static_python_info: StaticPythonInfo,
540
+ ) -> RobotType:
541
+ python_robot_type = (static_python_info.requirements or {}).get("robotType", None)
542
+ if python_robot_type is None:
543
+ return "OT-2 Standard"
544
+ else:
545
+ return robot_type_from_python_identifier(python_robot_type)
546
+
547
+
548
+ def _get_version(
549
+ static_python_info: StaticPythonInfo, parsed: ast.Module, filename: str
550
+ ) -> APIVersion:
551
+ """
552
+ Infer protocol API version based on a combination of metadata and imports.
553
+
554
+ If a protocol specifies its API version using the 'apiLevel' key of a top-
555
+ level dict variable named `metadata`, the value for that key will be
556
+ returned as the version.
557
+
558
+ If that variable does not exist or if it does not contain the 'apiLevel'
559
+ key, the API version will be inferred from the imports. A script with an
560
+ import containing 'robot', 'instruments', or 'modules' will be assumed to
561
+ be an APIv1 protocol. If none of these are present, it is assumed to be an
562
+ APIv2 protocol (note that 'labware' is not in this list, as there is a
563
+ valid APIv2 import named 'labware').
564
+ """
565
+ declared_version = _version_from_static_python_info(static_python_info)
566
+ if declared_version:
567
+ return declared_version
568
+ else:
569
+ # No apiLevel key, may be apiv1
570
+ if not _has_api_v1_imports(parsed):
571
+ raise MalformedPythonProtocolError(
572
+ short_message=(
573
+ f"apiLevel not declared in {filename}. "
574
+ f"You must specify the target API version "
575
+ f"in the apiLevel key of the metadata dict. For instance, "
576
+ f'metadata={{"apiLevel": "2.0"}}'
577
+ )
578
+ )
579
+ return APIVersion(1, 0)
580
+
581
+
582
+ def _get_protocol_schema_version(protocol_json: Dict[Any, Any]) -> int:
583
+ # v3 and above uses `schemaVersion: integer`
584
+ version = protocol_json.get("schemaVersion")
585
+ if version:
586
+ return int(version)
587
+ # v1 uses 1.x.x and v2 uses 2.x.x
588
+ legacyKebabVersion = protocol_json.get("protocol-schema")
589
+ # No minor/patch schemas ever were released,
590
+ # do not permit protocols with nonexistent schema versions to load
591
+ if legacyKebabVersion == "1.0.0":
592
+ return 1
593
+ elif legacyKebabVersion == "2.0.0":
594
+ return 2
595
+ elif legacyKebabVersion:
596
+ raise RuntimeError(
597
+ f'No such schema version: "{legacyKebabVersion}". Did you mean '
598
+ + '"1.0.0" or "2.0.0"?'
599
+ )
600
+ # no truthy value for schemaVersion or protocol-schema
601
+ raise RuntimeError(
602
+ "Could not determine schema version for protocol. "
603
+ + 'Make sure there is a version number under "schemaVersion"'
604
+ )
605
+
606
+
607
+ def _get_schema_for_protocol(version_num: int) -> JSONProtocolSchema:
608
+ """Retrieve the json schema for a protocol schema version"""
609
+ # TODO(IL, 2020/03/05): use $otSharedSchema, but maybe wait until
610
+ # deprecating v1/v2 JSON protocols?
611
+ if version_num > MAX_SUPPORTED_JSON_SCHEMA_VERSION:
612
+ raise RuntimeError(
613
+ f"JSON Protocol version {version_num} is not yet "
614
+ + "supported in this version of the API"
615
+ )
616
+ try:
617
+ return load_protocol_schema(version=version_num)
618
+ except FileNotFoundError:
619
+ raise RuntimeError(
620
+ 'JSON Protocol schema "{}" does not exist'.format(version_num)
621
+ ) from None
622
+
623
+
624
+ def validate_json(protocol_json: Dict[Any, Any]) -> Tuple[int, "JsonProtocolDef"]:
625
+ """Validates a json protocol and returns its schema version"""
626
+ # Check if this is actually a labware
627
+ labware_schema_v2 = load_labware_schema()
628
+ try:
629
+ jsonschema.validate(protocol_json, labware_schema_v2)
630
+ except jsonschema.ValidationError:
631
+ pass
632
+ else:
633
+ MODULE_LOG.error("labware uploaded instead of protocol")
634
+ raise RuntimeError(
635
+ "The file you are trying to open is a JSON labware definition, "
636
+ "and therefore can not be opened here. Please try "
637
+ "uploading a JSON protocol file instead."
638
+ )
639
+
640
+ # this is now either a protocol or something corrupt
641
+ version_num = _get_protocol_schema_version(protocol_json)
642
+ if version_num <= 2:
643
+ raise RuntimeError(
644
+ f"JSON protocol version {version_num} is "
645
+ "deprecated. Please upload your protocol into Protocol "
646
+ "Designer and save it to migrate the protocol to a later "
647
+ "version. This error might mean a labware "
648
+ "definition was specified instead of a protocol."
649
+ )
650
+ if version_num > MAX_SUPPORTED_JSON_SCHEMA_VERSION:
651
+ raise JSONSchemaVersionTooNewError(attempted_schema_version=version_num)
652
+ protocol_schema = _get_schema_for_protocol(version_num)
653
+
654
+ # instruct schema how to resolve all $ref's used in protocol schemas
655
+ resolver = jsonschema.RefResolver(
656
+ protocol_schema.get("$id", ""),
657
+ protocol_schema,
658
+ store={"opentronsLabwareSchemaV2": labware_schema_v2},
659
+ )
660
+
661
+ # do the validation
662
+ try:
663
+ jsonschema.validate(protocol_json, protocol_schema, resolver=resolver)
664
+ except jsonschema.ValidationError:
665
+ MODULE_LOG.exception("JSON protocol validation failed")
666
+ raise RuntimeError(
667
+ "This may be a corrupted file or a JSON file that is not an "
668
+ "Opentrons JSON protocol."
669
+ )
670
+ else:
671
+ return version_num, protocol_json # type: ignore