opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  9. opentrons/drivers/asyncio/communication/errors.py +16 -3
  10. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  11. opentrons/drivers/command_builder.py +2 -2
  12. opentrons/drivers/flex_stacker/__init__.py +9 -0
  13. opentrons/drivers/flex_stacker/abstract.py +89 -0
  14. opentrons/drivers/flex_stacker/driver.py +260 -0
  15. opentrons/drivers/flex_stacker/simulator.py +109 -0
  16. opentrons/drivers/flex_stacker/types.py +138 -0
  17. opentrons/drivers/heater_shaker/driver.py +18 -3
  18. opentrons/drivers/temp_deck/driver.py +13 -3
  19. opentrons/drivers/thermocycler/driver.py +17 -3
  20. opentrons/execute.py +3 -1
  21. opentrons/hardware_control/__init__.py +1 -2
  22. opentrons/hardware_control/api.py +28 -20
  23. opentrons/hardware_control/backends/flex_protocol.py +4 -6
  24. opentrons/hardware_control/backends/ot3controller.py +177 -59
  25. opentrons/hardware_control/backends/ot3simulator.py +10 -8
  26. opentrons/hardware_control/backends/ot3utils.py +3 -13
  27. opentrons/hardware_control/dev_types.py +2 -0
  28. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  29. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  30. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  31. opentrons/hardware_control/emulation/settings.py +3 -4
  32. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  33. opentrons/hardware_control/instruments/ot2/pipette.py +9 -21
  34. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  35. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  36. opentrons/hardware_control/instruments/ot3/pipette.py +13 -22
  37. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  38. opentrons/hardware_control/modules/mod_abc.py +2 -2
  39. opentrons/hardware_control/motion_utilities.py +68 -0
  40. opentrons/hardware_control/nozzle_manager.py +39 -41
  41. opentrons/hardware_control/ot3_calibration.py +1 -1
  42. opentrons/hardware_control/ot3api.py +34 -22
  43. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  44. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  45. opentrons/hardware_control/protocols/liquid_handler.py +18 -0
  46. opentrons/hardware_control/protocols/motion_controller.py +6 -0
  47. opentrons/hardware_control/robot_calibration.py +1 -1
  48. opentrons/hardware_control/types.py +61 -0
  49. opentrons/protocol_api/__init__.py +20 -1
  50. opentrons/protocol_api/_liquid.py +24 -49
  51. opentrons/protocol_api/_liquid_properties.py +754 -0
  52. opentrons/protocol_api/_types.py +24 -0
  53. opentrons/protocol_api/core/common.py +2 -0
  54. opentrons/protocol_api/core/engine/instrument.py +67 -10
  55. opentrons/protocol_api/core/engine/labware.py +29 -7
  56. opentrons/protocol_api/core/engine/protocol.py +130 -5
  57. opentrons/protocol_api/core/engine/robot.py +139 -0
  58. opentrons/protocol_api/core/engine/well.py +4 -1
  59. opentrons/protocol_api/core/instrument.py +42 -4
  60. opentrons/protocol_api/core/labware.py +13 -4
  61. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -3
  62. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  63. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  64. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  65. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +34 -3
  66. opentrons/protocol_api/core/protocol.py +34 -1
  67. opentrons/protocol_api/core/robot.py +51 -0
  68. opentrons/protocol_api/instrument_context.py +145 -43
  69. opentrons/protocol_api/labware.py +231 -7
  70. opentrons/protocol_api/module_contexts.py +21 -17
  71. opentrons/protocol_api/protocol_context.py +125 -4
  72. opentrons/protocol_api/robot_context.py +204 -32
  73. opentrons/protocol_api/validation.py +261 -3
  74. opentrons/protocol_engine/__init__.py +4 -0
  75. opentrons/protocol_engine/actions/actions.py +2 -3
  76. opentrons/protocol_engine/clients/sync_client.py +18 -0
  77. opentrons/protocol_engine/commands/__init__.py +81 -0
  78. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
  79. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
  80. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
  81. opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
  82. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  83. opentrons/protocol_engine/commands/aspirate.py +103 -53
  84. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  85. opentrons/protocol_engine/commands/blow_out.py +44 -39
  86. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  87. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  88. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  89. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  90. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  91. opentrons/protocol_engine/commands/command.py +73 -66
  92. opentrons/protocol_engine/commands/command_unions.py +101 -1
  93. opentrons/protocol_engine/commands/comment.py +1 -1
  94. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  95. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  96. opentrons/protocol_engine/commands/custom.py +6 -12
  97. opentrons/protocol_engine/commands/dispense.py +82 -48
  98. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  99. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  100. opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
  101. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  102. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  103. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  104. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  105. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  106. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  107. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  108. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  109. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  111. opentrons/protocol_engine/commands/home.py +13 -4
  112. opentrons/protocol_engine/commands/liquid_probe.py +60 -25
  113. opentrons/protocol_engine/commands/load_labware.py +29 -7
  114. opentrons/protocol_engine/commands/load_lid.py +146 -0
  115. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  116. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  117. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  118. opentrons/protocol_engine/commands/load_module.py +31 -10
  119. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  120. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  121. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  122. opentrons/protocol_engine/commands/move_labware.py +19 -6
  123. opentrons/protocol_engine/commands/move_relative.py +35 -25
  124. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  125. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  126. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  127. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  128. opentrons/protocol_engine/commands/movement_common.py +338 -0
  129. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  130. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  131. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  132. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  133. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  134. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  135. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  136. opentrons/protocol_engine/commands/robot/common.py +18 -0
  137. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  138. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  139. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  140. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  141. opentrons/protocol_engine/commands/save_position.py +14 -5
  142. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  143. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  144. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  145. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  146. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  147. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  148. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  149. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  150. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  151. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
  152. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  153. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  154. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  157. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  158. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
  159. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
  160. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
  161. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
  162. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  163. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  164. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  165. opentrons/protocol_engine/errors/__init__.py +8 -0
  166. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  167. opentrons/protocol_engine/errors/exceptions.py +50 -0
  168. opentrons/protocol_engine/execution/command_executor.py +1 -1
  169. opentrons/protocol_engine/execution/equipment.py +73 -5
  170. opentrons/protocol_engine/execution/gantry_mover.py +364 -8
  171. opentrons/protocol_engine/execution/movement.py +27 -0
  172. opentrons/protocol_engine/execution/pipetting.py +5 -1
  173. opentrons/protocol_engine/execution/tip_handler.py +4 -6
  174. opentrons/protocol_engine/notes/notes.py +1 -1
  175. opentrons/protocol_engine/protocol_engine.py +7 -6
  176. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  177. opentrons/protocol_engine/resources/labware_validation.py +5 -0
  178. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  179. opentrons/protocol_engine/resources/pipette_data_provider.py +12 -0
  180. opentrons/protocol_engine/slot_standardization.py +9 -9
  181. opentrons/protocol_engine/state/_move_types.py +9 -5
  182. opentrons/protocol_engine/state/_well_math.py +193 -0
  183. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  184. opentrons/protocol_engine/state/command_history.py +12 -0
  185. opentrons/protocol_engine/state/commands.py +17 -13
  186. opentrons/protocol_engine/state/files.py +10 -12
  187. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  188. opentrons/protocol_engine/state/frustum_helpers.py +57 -32
  189. opentrons/protocol_engine/state/geometry.py +47 -1
  190. opentrons/protocol_engine/state/labware.py +79 -25
  191. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  192. opentrons/protocol_engine/state/liquids.py +16 -4
  193. opentrons/protocol_engine/state/modules.py +52 -70
  194. opentrons/protocol_engine/state/motion.py +6 -1
  195. opentrons/protocol_engine/state/pipettes.py +135 -58
  196. opentrons/protocol_engine/state/state.py +21 -2
  197. opentrons/protocol_engine/state/state_summary.py +4 -2
  198. opentrons/protocol_engine/state/tips.py +11 -44
  199. opentrons/protocol_engine/state/update_types.py +343 -48
  200. opentrons/protocol_engine/state/wells.py +19 -11
  201. opentrons/protocol_engine/types.py +176 -28
  202. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  203. opentrons/protocol_reader/file_format_validator.py +5 -5
  204. opentrons/protocol_runner/json_file_reader.py +9 -3
  205. opentrons/protocol_runner/json_translator.py +51 -25
  206. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  207. opentrons/protocol_runner/protocol_runner.py +35 -4
  208. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  209. opentrons/protocol_runner/run_orchestrator.py +13 -3
  210. opentrons/protocols/advanced_control/common.py +38 -0
  211. opentrons/protocols/advanced_control/mix.py +1 -1
  212. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  213. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  214. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  215. opentrons/protocols/api_support/definitions.py +1 -1
  216. opentrons/protocols/api_support/instrument.py +1 -1
  217. opentrons/protocols/api_support/util.py +10 -0
  218. opentrons/protocols/labware.py +70 -8
  219. opentrons/protocols/models/json_protocol.py +5 -9
  220. opentrons/simulate.py +3 -1
  221. opentrons/types.py +162 -2
  222. opentrons/util/entrypoint_util.py +2 -5
  223. opentrons/util/logging_config.py +1 -1
  224. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/METADATA +16 -15
  225. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/RECORD +229 -202
  226. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/WHEEL +1 -1
  227. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/LICENSE +0 -0
  228. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/entry_points.txt +0 -0
  229. {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/top_level.txt +0 -0
@@ -2,13 +2,14 @@ from __future__ import annotations
2
2
  import logging
3
3
  from contextlib import ExitStack
4
4
  from typing import Any, List, Optional, Sequence, Union, cast, Dict
5
- from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError
6
5
  from opentrons_shared_data.errors.exceptions import (
7
6
  CommandPreconditionViolated,
8
7
  CommandParameterLimitViolated,
9
8
  UnexpectedTipRemovalError,
10
9
  UnsupportedHardwareCommand,
11
10
  )
11
+ from opentrons_shared_data.robot.types import RobotTypeEnum
12
+
12
13
  from opentrons.legacy_broker import LegacyBroker
13
14
  from opentrons.hardware_control.dev_types import PipetteDict
14
15
  from opentrons import types
@@ -16,8 +17,7 @@ from opentrons.legacy_commands import commands as cmds
16
17
 
17
18
  from opentrons.legacy_commands import publisher
18
19
  from opentrons.protocols.advanced_control.mix import mix_from_kwargs
19
- from opentrons.protocols.advanced_control import transfers
20
-
20
+ from opentrons.protocols.advanced_control.transfers import transfer as v1_transfer
21
21
  from opentrons.protocols.api_support.deck_type import NoTrashDefinedError
22
22
  from opentrons.protocols.api_support.types import APIVersion
23
23
  from opentrons.protocols.api_support import instrument
@@ -29,7 +29,6 @@ from opentrons.protocols.api_support.util import (
29
29
  APIVersionError,
30
30
  UnsupportedAPIError,
31
31
  )
32
- from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
33
32
 
34
33
  from .core.common import InstrumentCore, ProtocolCore
35
34
  from .core.engine import ENGINE_CORE_API_VERSION
@@ -37,10 +36,13 @@ from .core.legacy.legacy_instrument_core import LegacyInstrumentCore
37
36
  from .config import Clearances
38
37
  from .disposal_locations import TrashBin, WasteChute
39
38
  from ._nozzle_layout import NozzleLayout
39
+ from ._liquid import LiquidClass
40
40
  from . import labware, validation
41
-
42
-
43
- AdvancedLiquidHandling = transfers.AdvancedLiquidHandling
41
+ from ..config import feature_flags
42
+ from ..protocols.advanced_control.transfers.common import (
43
+ TransferTipPolicyV2,
44
+ TransferTipPolicyV2Type,
45
+ )
44
46
 
45
47
  _DEFAULT_ASPIRATE_CLEARANCE = 1.0
46
48
  _DEFAULT_DISPENSE_CLEARANCE = 1.0
@@ -61,6 +63,10 @@ _DISPOSAL_LOCATION_OFFSET_ADDED_IN = APIVersion(2, 18)
61
63
  """The version after which offsets for deck configured trash containers and changes to alternating tip drop behavior were introduced."""
62
64
  _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN = APIVersion(2, 20)
63
65
  """The version after which partial nozzle configurations of single, row, and partial column layouts became available."""
66
+ _AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
67
+ """The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking."""
68
+
69
+ AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
64
70
 
65
71
 
66
72
  class InstrumentContext(publisher.CommandPublisher):
@@ -258,7 +264,7 @@ class InstrumentContext(publisher.CommandPublisher):
258
264
  self.api_version >= APIVersion(2, 20)
259
265
  and well is not None
260
266
  and self.liquid_presence_detection
261
- and self._96_tip_config_valid()
267
+ and self._core.nozzle_configuration_valid_for_lld()
262
268
  and self._core.get_current_volume() == 0
263
269
  ):
264
270
  self._raise_if_pressure_not_supported_by_pipette()
@@ -515,6 +521,8 @@ class InstrumentContext(publisher.CommandPublisher):
515
521
  ``pipette.mix(1, location=wellplate['A1'])`` is a valid call, but
516
522
  ``pipette.mix(1, wellplate['A1'])`` is not.
517
523
 
524
+ .. versionchanged:: 2.21
525
+ Does not repeatedly check for liquid presence.
518
526
  """
519
527
  _log.debug(
520
528
  "mixing {}uL with {} repetitions in {} at rate={}".format(
@@ -755,7 +763,11 @@ class InstrumentContext(publisher.CommandPublisher):
755
763
  ``pipette.air_gap(height=2)``. If you call ``air_gap`` with a single,
756
764
  unnamed argument, it will always be interpreted as a volume.
757
765
 
758
-
766
+ .. TODO: restore this as a note block for 2.22 docs
767
+ Before API version 2.22, this function was implemented as an aspirate, and
768
+ dispensing into a well would add the air gap volume to the liquid tracked in
769
+ the well. At or above API version 2.22, air gap volume is not counted as liquid
770
+ when dispensing into a well.
759
771
  """
760
772
  if not self._core.has_tip():
761
773
  raise UnexpectedTipRemovalError("air_gap", self.name, self.mount)
@@ -767,7 +779,12 @@ class InstrumentContext(publisher.CommandPublisher):
767
779
  raise RuntimeError("No previous Well cached to perform air gap")
768
780
  target = loc.labware.as_well().top(height)
769
781
  self.move_to(target, publish=False)
770
- self.aspirate(volume)
782
+ if self.api_version >= _AIR_GAP_TRACKING_ADDED_IN:
783
+ c_vol = self._core.get_available_volume() if volume is None else volume
784
+ flow_rate = self._core.get_aspirate_flow_rate()
785
+ self._core.air_gap_in_place(c_vol, flow_rate)
786
+ else:
787
+ self.aspirate(volume)
771
788
  return self
772
789
 
773
790
  @publisher.publish(command=cmds.return_tip)
@@ -936,7 +953,7 @@ class InstrumentContext(publisher.CommandPublisher):
936
953
  if location is None:
937
954
  if (
938
955
  nozzle_map is not None
939
- and nozzle_map.configuration != NozzleConfigurationType.FULL
956
+ and nozzle_map.configuration != types.NozzleConfigurationType.FULL
940
957
  and self.starting_tip is not None
941
958
  ):
942
959
  # Disallowing this avoids concerning the system with the direction
@@ -1208,7 +1225,6 @@ class InstrumentContext(publisher.CommandPublisher):
1208
1225
  self._core.home_plunger()
1209
1226
  return self
1210
1227
 
1211
- # TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling
1212
1228
  @publisher.publish(command=cmds.distribute)
1213
1229
  @requires_version(2, 0)
1214
1230
  def distribute(
@@ -1248,7 +1264,6 @@ class InstrumentContext(publisher.CommandPublisher):
1248
1264
 
1249
1265
  return self.transfer(volume, source, dest, **kwargs)
1250
1266
 
1251
- # TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling
1252
1267
  @publisher.publish(command=cmds.consolidate)
1253
1268
  @requires_version(2, 0)
1254
1269
  def consolidate(
@@ -1398,9 +1413,9 @@ class InstrumentContext(publisher.CommandPublisher):
1398
1413
  mix_strategy, mix_opts = mix_from_kwargs(kwargs)
1399
1414
 
1400
1415
  if trash:
1401
- drop_tip = transfers.DropTipStrategy.TRASH
1416
+ drop_tip = v1_transfer.DropTipStrategy.TRASH
1402
1417
  else:
1403
- drop_tip = transfers.DropTipStrategy.RETURN
1418
+ drop_tip = v1_transfer.DropTipStrategy.RETURN
1404
1419
 
1405
1420
  new_tip = kwargs.get("new_tip")
1406
1421
  if isinstance(new_tip, str):
@@ -1422,19 +1437,19 @@ class InstrumentContext(publisher.CommandPublisher):
1422
1437
 
1423
1438
  if blow_out and not blowout_location:
1424
1439
  if self.current_volume:
1425
- blow_out_strategy = transfers.BlowOutStrategy.SOURCE
1440
+ blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE
1426
1441
  else:
1427
- blow_out_strategy = transfers.BlowOutStrategy.TRASH
1442
+ blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH
1428
1443
  elif blow_out and blowout_location:
1429
1444
  if blowout_location == "source well":
1430
- blow_out_strategy = transfers.BlowOutStrategy.SOURCE
1445
+ blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE
1431
1446
  elif blowout_location == "destination well":
1432
- blow_out_strategy = transfers.BlowOutStrategy.DEST
1447
+ blow_out_strategy = v1_transfer.BlowOutStrategy.DEST
1433
1448
  elif blowout_location == "trash":
1434
- blow_out_strategy = transfers.BlowOutStrategy.TRASH
1449
+ blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH
1435
1450
 
1436
1451
  if new_tip != types.TransferTipPolicy.NEVER:
1437
- tr, next_tip = labware.next_available_tip(
1452
+ _, next_tip = labware.next_available_tip(
1438
1453
  self.starting_tip,
1439
1454
  self.tip_racks,
1440
1455
  active_channels,
@@ -1446,9 +1461,9 @@ class InstrumentContext(publisher.CommandPublisher):
1446
1461
 
1447
1462
  touch_tip = None
1448
1463
  if kwargs.get("touch_tip"):
1449
- touch_tip = transfers.TouchTipStrategy.ALWAYS
1464
+ touch_tip = v1_transfer.TouchTipStrategy.ALWAYS
1450
1465
 
1451
- default_args = transfers.Transfer()
1466
+ default_args = v1_transfer.Transfer()
1452
1467
 
1453
1468
  disposal = kwargs.get("disposal_volume")
1454
1469
  if disposal is None:
@@ -1461,7 +1476,7 @@ class InstrumentContext(publisher.CommandPublisher):
1461
1476
  f"working volume, {max_volume}uL"
1462
1477
  )
1463
1478
 
1464
- transfer_args = transfers.Transfer(
1479
+ transfer_args = v1_transfer.Transfer(
1465
1480
  new_tip=new_tip or default_args.new_tip,
1466
1481
  air_gap=air_gap,
1467
1482
  carryover=kwargs.get("carryover") or default_args.carryover,
@@ -1474,10 +1489,10 @@ class InstrumentContext(publisher.CommandPublisher):
1474
1489
  blow_out_strategy=blow_out_strategy or default_args.blow_out_strategy,
1475
1490
  touch_tip_strategy=(touch_tip or default_args.touch_tip_strategy),
1476
1491
  )
1477
- transfer_options = transfers.TransferOptions(
1492
+ transfer_options = v1_transfer.TransferOptions(
1478
1493
  transfer=transfer_args, mix=mix_opts
1479
1494
  )
1480
- plan = transfers.TransferPlan(
1495
+ plan = v1_transfer.TransferPlan(
1481
1496
  volume,
1482
1497
  source,
1483
1498
  dest,
@@ -1490,10 +1505,113 @@ class InstrumentContext(publisher.CommandPublisher):
1490
1505
  self._execute_transfer(plan)
1491
1506
  return self
1492
1507
 
1493
- def _execute_transfer(self, plan: transfers.TransferPlan) -> None:
1508
+ def _execute_transfer(self, plan: v1_transfer.TransferPlan) -> None:
1494
1509
  for cmd in plan:
1495
1510
  getattr(self, cmd["method"])(*cmd["args"], **cmd["kwargs"])
1496
1511
 
1512
+ def transfer_liquid(
1513
+ self,
1514
+ liquid_class: LiquidClass,
1515
+ volume: float,
1516
+ source: Union[
1517
+ labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1518
+ ],
1519
+ dest: Union[
1520
+ labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
1521
+ ],
1522
+ new_tip: TransferTipPolicyV2Type = "once",
1523
+ tip_drop_location: Optional[
1524
+ Union[types.Location, labware.Well, TrashBin, WasteChute]
1525
+ ] = None, # Maybe call this 'tip_drop_location' which is similar to PD
1526
+ ) -> InstrumentContext:
1527
+ """Transfer liquid from source to dest using the specified liquid class properties.
1528
+
1529
+ TODO: Add args description.
1530
+ """
1531
+ if not feature_flags.allow_liquid_classes(
1532
+ robot_type=RobotTypeEnum.robot_literal_to_enum(
1533
+ self._protocol_core.robot_type
1534
+ )
1535
+ ):
1536
+ raise NotImplementedError("This method is not implemented.")
1537
+
1538
+ flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(
1539
+ source
1540
+ )
1541
+ flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest)
1542
+ for well in flat_sources_list + flat_dests_list:
1543
+ instrument.validate_takes_liquid(
1544
+ location=well.top(),
1545
+ reject_module=True,
1546
+ reject_adapter=True,
1547
+ )
1548
+ if len(flat_sources_list) != len(flat_dests_list):
1549
+ raise ValueError(
1550
+ "Sources and destinations should be of the same length in order to perform a transfer."
1551
+ " To transfer liquid from one source to many destinations, use 'distribute_liquid',"
1552
+ " to transfer liquid onto one destinations from many sources, use 'consolidate_liquid'."
1553
+ )
1554
+
1555
+ valid_new_tip = validation.ensure_new_tip_policy(new_tip)
1556
+ if valid_new_tip == TransferTipPolicyV2.NEVER:
1557
+ if self._last_tip_picked_up_from is None:
1558
+ raise RuntimeError(
1559
+ "Pipette has no tip attached to perform transfer."
1560
+ " Either do a pick_up_tip beforehand or specify a new_tip parameter"
1561
+ " of 'once' or 'always'."
1562
+ )
1563
+ else:
1564
+ tiprack = self._last_tip_picked_up_from.parent
1565
+ else:
1566
+ tiprack, well = labware.next_available_tip(
1567
+ starting_tip=self.starting_tip,
1568
+ tip_racks=self.tip_racks,
1569
+ channels=self.active_channels,
1570
+ nozzle_map=self._core.get_nozzle_map(),
1571
+ )
1572
+ if self.current_volume != 0:
1573
+ raise RuntimeError(
1574
+ "A transfer on a liquid class cannot start with liquid already in the tip."
1575
+ " Ensure that all previously aspirated liquid is dispensed before starting"
1576
+ " a new transfer."
1577
+ )
1578
+
1579
+ _trash_location: Union[types.Location, labware.Well, TrashBin, WasteChute]
1580
+ if tip_drop_location is None:
1581
+ saved_trash = self.trash_container
1582
+ if isinstance(saved_trash, labware.Labware):
1583
+ _trash_location = saved_trash.wells()[0]
1584
+ else:
1585
+ _trash_location = saved_trash
1586
+ else:
1587
+ _trash_location = tip_drop_location
1588
+
1589
+ checked_trash_location = (
1590
+ validation.ensure_valid_tip_drop_location_for_transfer_v2(
1591
+ tip_drop_location=_trash_location
1592
+ )
1593
+ )
1594
+ liquid_class_id = self._core.load_liquid_class(
1595
+ liquid_class=liquid_class,
1596
+ pipette_load_name=self.name,
1597
+ tiprack_uri=tiprack.uri,
1598
+ )
1599
+
1600
+ self._core.transfer_liquid(
1601
+ liquid_class_id=liquid_class_id,
1602
+ volume=volume,
1603
+ source=[well._core for well in flat_sources_list],
1604
+ dest=[well._core for well in flat_dests_list],
1605
+ new_tip=valid_new_tip,
1606
+ trash_location=(
1607
+ checked_trash_location._core
1608
+ if isinstance(checked_trash_location, labware.Well)
1609
+ else checked_trash_location
1610
+ ),
1611
+ )
1612
+
1613
+ return self
1614
+
1497
1615
  @requires_version(2, 0)
1498
1616
  def delay(self, *args: Any, **kwargs: Any) -> None:
1499
1617
  """
@@ -1874,19 +1992,6 @@ class InstrumentContext(publisher.CommandPublisher):
1874
1992
  else:
1875
1993
  return self._protocol_core.get_last_location()
1876
1994
 
1877
- def _96_tip_config_valid(self) -> bool:
1878
- n_map = self._core.get_nozzle_map()
1879
- channels = self._core.get_active_channels()
1880
- if channels == 96:
1881
- if (
1882
- n_map.back_left != n_map.full_instrument_back_left
1883
- and n_map.front_right != n_map.full_instrument_front_right
1884
- ):
1885
- raise TipNotAttachedError(
1886
- "Either the front right or the back left nozzle must have a tip attached to do LLD."
1887
- )
1888
- return True
1889
-
1890
1995
  def __repr__(self) -> str:
1891
1996
  return "<{}: {} in {}>".format(
1892
1997
  self.__class__.__name__,
@@ -2149,7 +2254,6 @@ class InstrumentContext(publisher.CommandPublisher):
2149
2254
  """
2150
2255
  self._raise_if_pressure_not_supported_by_pipette()
2151
2256
  loc = well.top()
2152
- self._96_tip_config_valid()
2153
2257
  return self._core.detect_liquid_presence(well._core, loc)
2154
2258
 
2155
2259
  @requires_version(2, 20)
@@ -2163,7 +2267,6 @@ class InstrumentContext(publisher.CommandPublisher):
2163
2267
  """
2164
2268
  self._raise_if_pressure_not_supported_by_pipette()
2165
2269
  loc = well.top()
2166
- self._96_tip_config_valid()
2167
2270
  self._core.liquid_probe_with_recovery(well._core, loc)
2168
2271
 
2169
2272
  @requires_version(2, 20)
@@ -2178,7 +2281,6 @@ class InstrumentContext(publisher.CommandPublisher):
2178
2281
  """
2179
2282
  self._raise_if_pressure_not_supported_by_pipette()
2180
2283
  loc = well.top()
2181
- self._96_tip_config_valid()
2182
2284
  height = self._core.liquid_probe_without_recovery(well._core, loc)
2183
2285
  return height
2184
2286
 
@@ -1,4 +1,4 @@
1
- """ opentrons.protocol_api.labware: classes and functions for labware handling
1
+ """opentrons.protocol_api.labware: classes and functions for labware handling
2
2
 
3
3
  This module provides things like :py:class:`Labware`, and :py:class:`Well`
4
4
  to encapsulate labware instances used in protocols
@@ -13,18 +13,29 @@ from __future__ import annotations
13
13
  import logging
14
14
 
15
15
  from itertools import dropwhile
16
- from typing import TYPE_CHECKING, Any, List, Dict, Optional, Union, Tuple, cast
16
+ from typing import (
17
+ TYPE_CHECKING,
18
+ Any,
19
+ List,
20
+ Dict,
21
+ Optional,
22
+ Union,
23
+ Tuple,
24
+ cast,
25
+ Sequence,
26
+ Mapping,
27
+ )
17
28
 
18
29
  from opentrons_shared_data.labware.types import LabwareDefinition, LabwareParameters
19
30
 
20
- from opentrons.types import Location, Point
31
+ from opentrons.types import Location, Point, NozzleMapInterface
21
32
  from opentrons.protocols.api_support.types import APIVersion
22
33
  from opentrons.protocols.api_support.util import (
23
34
  requires_version,
24
35
  APIVersionError,
25
36
  UnsupportedAPIError,
26
37
  )
27
- from opentrons.hardware_control.nozzle_manager import NozzleMap
38
+
28
39
 
29
40
  # TODO(mc, 2022-09-02): re-exports provided for backwards compatibility
30
41
  # remove when their usage is no longer needed
@@ -280,6 +291,10 @@ class Well:
280
291
 
281
292
  :param Liquid liquid: The liquid to load into the well.
282
293
  :param float volume: The volume of liquid to load, in µL.
294
+
295
+ .. TODO: flag as deprecated in 2.22 docs
296
+ In API version 2.22 and later, use :py:meth:`~Labware.load_liquid`, :py:meth:`~Labware.load_liquid_by_well`,
297
+ or :py:meth:`~Labware.load_empty` to load liquid into a well.
283
298
  """
284
299
  self._core.load_liquid(
285
300
  liquid=liquid,
@@ -529,6 +544,7 @@ class Labware:
529
544
  self,
530
545
  name: str,
531
546
  label: Optional[str] = None,
547
+ lid: Optional[str] = None,
532
548
  namespace: Optional[str] = None,
533
549
  version: Optional[int] = None,
534
550
  ) -> Labware:
@@ -558,6 +574,20 @@ class Labware:
558
574
 
559
575
  self._core_map.add(labware_core, labware)
560
576
 
577
+ if lid is not None:
578
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
579
+ raise APIVersionError(
580
+ api_element="Loading a Lid on a Labware",
581
+ until_version="2.23",
582
+ current_version=f"{self._api_version}",
583
+ )
584
+ self._protocol_core.load_lid(
585
+ load_name=lid,
586
+ location=labware_core,
587
+ namespace=namespace,
588
+ version=version,
589
+ )
590
+
561
591
  return labware
562
592
 
563
593
  @requires_version(2, 15)
@@ -582,6 +612,65 @@ class Labware:
582
612
  label=label,
583
613
  )
584
614
 
615
+ @requires_version(2, 23)
616
+ def load_lid_stack(
617
+ self,
618
+ load_name: str,
619
+ quantity: int,
620
+ namespace: Optional[str] = None,
621
+ version: Optional[int] = None,
622
+ ) -> Labware:
623
+ """
624
+ Load a stack of Lids onto a valid Deck Location or Adapter.
625
+
626
+ :param str load_name: A string to use for looking up a lid definition.
627
+ You can find the ``load_name`` for any standard lid on the Opentrons
628
+ `Labware Library <https://labware.opentrons.com>`_.
629
+ :param int quantity: The quantity of lids to be loaded in the stack.
630
+ :param str namespace: The namespace that the lid labware definition belongs to.
631
+ If unspecified, the API will automatically search two namespaces:
632
+
633
+ - ``"opentrons"``, to load standard Opentrons labware definitions.
634
+ - ``"custom_beta"``, to load custom labware definitions created with the
635
+ `Custom Labware Creator <https://labware.opentrons.com/create>`__.
636
+
637
+ You might need to specify an explicit ``namespace`` if you have a custom
638
+ definition whose ``load_name`` is the same as an Opentrons-verified
639
+ definition, and you want to explicitly choose one or the other.
640
+
641
+ :param version: The version of the labware definition. You should normally
642
+ leave this unspecified to let ``load_lid_stack()`` choose a version
643
+ automatically.
644
+
645
+ :return: The initialized and loaded labware object representing the Lid Stack.
646
+ """
647
+ if self._api_version < validation.LID_STACK_VERSION_GATE:
648
+ raise APIVersionError(
649
+ api_element="Loading a Lid Stack",
650
+ until_version="2.23",
651
+ current_version=f"{self._api_version}",
652
+ )
653
+
654
+ load_location = self._core
655
+
656
+ load_name = validation.ensure_lowercase_name(load_name)
657
+
658
+ result = self._protocol_core.load_lid_stack(
659
+ load_name=load_name,
660
+ location=load_location,
661
+ quantity=quantity,
662
+ namespace=namespace,
663
+ version=version,
664
+ )
665
+
666
+ labware = Labware(
667
+ core=result,
668
+ api_version=self._api_version,
669
+ protocol_core=self._protocol_core,
670
+ core_map=self._core_map,
671
+ )
672
+ return labware
673
+
585
674
  def set_calibration(self, delta: Point) -> None:
586
675
  """
587
676
  An internal, deprecated method used for updating the labware offset.
@@ -932,7 +1021,7 @@ class Labware:
932
1021
  num_tips: int = 1,
933
1022
  starting_tip: Optional[Well] = None,
934
1023
  *,
935
- nozzle_map: Optional[NozzleMap] = None,
1024
+ nozzle_map: Optional[NozzleMapInterface] = None,
936
1025
  ) -> Optional[Well]:
937
1026
  """
938
1027
  Find the next valid well for pick-up.
@@ -1105,6 +1194,141 @@ class Labware:
1105
1194
  """
1106
1195
  self._core.reset_tips()
1107
1196
 
1197
+ @requires_version(2, 22)
1198
+ def load_liquid(
1199
+ self, wells: Sequence[Union[str, Well]], volume: float, liquid: Liquid
1200
+ ) -> None:
1201
+ """Mark several wells as containing the same amount of liquid.
1202
+
1203
+ This method should be called at the beginning of a protocol, soon after loading the labware and before
1204
+ liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware
1205
+ has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
1206
+ :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
1207
+
1208
+ For example, to load 10µL of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
1209
+ into all the wells of a labware, you could call ``labware.load_liquid(labware.wells(), 10, water)``.
1210
+
1211
+ If you want to load different volumes of liquid into different wells, use :py:meth:`~Labware.load_liquid_by_well`.
1212
+
1213
+ If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
1214
+
1215
+ :param wells: The wells to load the liquid into.
1216
+ :type wells: List of well names or list of Well objects, for instance from :py:meth:`~Labware.wells`.
1217
+
1218
+ :param volume: The volume of liquid to load into each well, in 10µL.
1219
+ :type volume: float
1220
+
1221
+ :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
1222
+ :type liquid: Liquid
1223
+ """
1224
+ well_names: List[str] = []
1225
+ for well in wells:
1226
+ if isinstance(well, str):
1227
+ if well not in self.wells_by_name():
1228
+ raise KeyError(
1229
+ f"{well} is not a well in labware {self.name}. The elements of wells should name wells in this labware."
1230
+ )
1231
+ well_names.append(well)
1232
+ elif isinstance(well, Well):
1233
+ if well.parent is not self:
1234
+ raise KeyError(
1235
+ f"{well.well_name} is not a well in labware {self.name}. The elements of wells should be wells of this labware."
1236
+ )
1237
+ well_names.append(well.well_name)
1238
+ else:
1239
+ raise TypeError(
1240
+ f"Unexpected type for element {repr(well)}. The elements of wells should be Well instances or well names."
1241
+ )
1242
+ if not isinstance(volume, (float, int)):
1243
+ raise TypeError(
1244
+ f"Unexpected type for volume {repr(volume)}. Volume should be a number in microliters."
1245
+ )
1246
+ self._core.load_liquid({well_name: volume for well_name in well_names}, liquid)
1247
+
1248
+ @requires_version(2, 22)
1249
+ def load_liquid_by_well(
1250
+ self, volumes: Mapping[Union[str, Well], float], liquid: Liquid
1251
+ ) -> None:
1252
+ """Mark several wells as containing unique volumes of liquid.
1253
+
1254
+ This method should be called at the beginning of a protocol, soon after loading the labware and before
1255
+ liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware
1256
+ has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or
1257
+ :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked.
1258
+
1259
+ For example, to load a decreasing amount of of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`)
1260
+ into each successive well of a row, you could call
1261
+ ``labware.load_liquid_by_well({'A1': 1000, 'A2': 950, 'A3': 900, ..., 'A12': 600}, water)``
1262
+
1263
+ If you want to load the same volume of a liquid into multiple wells, it is often easier to use :py:meth:`~Labware.load_liquid`.
1264
+
1265
+ If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`.
1266
+
1267
+ :param volumes: A dictionary of well names (or :py:class:`Well` objects, for instance from ``labware['A1']``)
1268
+ :type wells: Dict[Union[str, Well], float]
1269
+
1270
+ :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid`
1271
+ :type liquid: Liquid
1272
+ """
1273
+ verified_volumes: Dict[str, float] = {}
1274
+ for well, volume in volumes.items():
1275
+ if isinstance(well, str):
1276
+ if well not in self.wells_by_name():
1277
+ raise KeyError(
1278
+ f"{well} is not a well in {self.name}. The keys of volumes should name wells in this labware"
1279
+ )
1280
+ verified_volumes[well] = volume
1281
+ elif isinstance(well, Well):
1282
+ if well.parent is not self:
1283
+ raise KeyError(
1284
+ f"{well.well_name} is not a well in {self.name}. The keys of volumes should be wells of this labware"
1285
+ )
1286
+ verified_volumes[well.well_name] = volume
1287
+ else:
1288
+ raise TypeError(
1289
+ f"Unexpected type for well name {repr(well)}. The keys of volumes should be Well instances or well names."
1290
+ )
1291
+ if not isinstance(volume, (float, int)):
1292
+ raise TypeError(
1293
+ f"Unexpected type for volume {repr(volume)}. The values of volumes should be numbers in microliters."
1294
+ )
1295
+ self._core.load_liquid(verified_volumes, liquid)
1296
+
1297
+ @requires_version(2, 22)
1298
+ def load_empty(self, wells: Sequence[Union[Well, str]]) -> None:
1299
+ """Mark several wells as empty.
1300
+
1301
+ This method should be called at the beginning of a protocol, soon after loading the labware and before liquid handling
1302
+ operations begin. It is a base of information for liquid tracking functionality. If a well in a labware has not been named
1303
+ in a call to :py:meth:`Labware.load_empty`, :py:meth:`Labware.load_liquid`, or :py:meth:`Labware.load_liquid_by_well`, the
1304
+ volume it contains is unknown and the well's liquid will not be tracked.
1305
+
1306
+ For instance, to mark all wells in the labware as empty, you can call ``labware.load_empty(labware.wells())``.
1307
+
1308
+ :param wells: The list of wells to mark empty. To mark all wells as empty, pass ``labware.wells()``. You can also specify
1309
+ wells by their names (for instance, ``labware.load_empty(['A1', 'A2'])``).
1310
+ :type wells: Union[List[Well], List[str]]
1311
+ """
1312
+ well_names: List[str] = []
1313
+ for well in wells:
1314
+ if isinstance(well, str):
1315
+ if well not in self.wells_by_name():
1316
+ raise KeyError(
1317
+ f"{well} is not a well in {self.name}. The elements of wells should name wells in this labware."
1318
+ )
1319
+ well_names.append(well)
1320
+ elif isinstance(well, Well):
1321
+ if well.parent is not self:
1322
+ raise KeyError(
1323
+ f"{well.well_name} is not a well in {self.name}. The elements of wells should be wells of this labware."
1324
+ )
1325
+ well_names.append(well.well_name)
1326
+ else:
1327
+ raise TypeError(
1328
+ f"Unexpected type for well name {repr(well)}. The elements of wells should be Well instances or well names."
1329
+ )
1330
+ self._core.load_empty(well_names)
1331
+
1108
1332
 
1109
1333
  # TODO(mc, 2022-11-09): implementation detail, move to core
1110
1334
  def split_tipracks(tip_racks: List[Labware]) -> Tuple[Labware, List[Labware]]:
@@ -1121,7 +1345,7 @@ def select_tiprack_from_list(
1121
1345
  num_channels: int,
1122
1346
  starting_point: Optional[Well] = None,
1123
1347
  *,
1124
- nozzle_map: Optional[NozzleMap] = None,
1348
+ nozzle_map: Optional[NozzleMapInterface] = None,
1125
1349
  ) -> Tuple[Labware, Well]:
1126
1350
  try:
1127
1351
  first, rest = split_tipracks(tip_racks)
@@ -1159,7 +1383,7 @@ def next_available_tip(
1159
1383
  tip_racks: List[Labware],
1160
1384
  channels: int,
1161
1385
  *,
1162
- nozzle_map: Optional[NozzleMap] = None,
1386
+ nozzle_map: Optional[NozzleMapInterface] = None,
1163
1387
  ) -> Tuple[Labware, Well]:
1164
1388
  start = starting_tip
1165
1389
  if start is None: