text: """ opentrons.protocol_api.labware: classes and functions for labware handling
  
  This module provides things like :py:class:`Labware`, and :py:class:`Well`
  to encapsulate labware instances used in protocols
  and their wells. It also provides helper functions to load and save labware
  and labware calibration offsets. It contains all the code necessary to
  transform from labware symbolic points (such as "well a1 of an opentrons
  tiprack") to points in deck coordinates.
  """
  import logging
  
  from pathlib import Path
  from itertools import dropwhile
  from typing import Any, AnyStr, List, Dict, Optional, Union, Tuple, TYPE_CHECKING
  
  
  from opentrons.protocols.api_support.util import requires_version, labware_column_shift
  from opentrons.protocols.context.labware import AbstractLabware
  from opentrons.protocols.geometry.well_geometry import WellGeometry
  from opentrons.protocols import labware as labware_module
  from opentrons.protocols.context.well import WellImplementation
  from opentrons.types import Location, Point, LocationLabware
  from opentrons.protocols.api_support.types import APIVersion
  from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION
  from opentrons.protocols.geometry.deck_item import DeckItem
  
  if TYPE_CHECKING:
      from opentrons.protocols.geometry.module_geometry import (  # noqa: F401
          ModuleGeometry,
      )
      from opentrons_shared_data.labware.dev_types import (
          LabwareDefinition,
          LabwareParameters,
      )
  
  
  MODULE_LOG = logging.getLogger(__name__)
  
  
  class TipSelectionError(Exception):
      pass
  
  
  class OutOfTipsError(Exception):
      pass
  
  
  class Well:
      """
      The Well class represents a  single well in a :py:class:`Labware`
  
      It provides functions to return positions used in operations on the well
      such as :py:meth:`top`, :py:meth:`bottom`
      """
  
      def __init__(
          self,
          well_implementation: WellImplementation,
          api_level: Optional[APIVersion] = None,
      ):
          """
          Create a well, and track the Point corresponding to the top-center of
          the well (this Point is in absolute deck coordinates)
  
          """
          self._api_version = api_level or MAX_SUPPORTED_VERSION
          self._impl = well_implementation
          self._geometry = well_implementation.get_geometry()
  
      @property  # type: ignore
      @requires_version(2, 0)
      def api_version(self) -> APIVersion:
          return self._api_version
  
      @property  # type: ignore
      @requires_version(2, 0)
      def parent(self) -> "Labware":
          return Labware(implementation=self._geometry.parent, api_level=self.api_version)
  
      @property  # type: ignore
      @requires_version(2, 0)
      def has_tip(self) -> bool:
          return self._impl.has_tip()
  
      @has_tip.setter
      def has_tip(self, value: bool):
          self._impl.set_has_tip(value)
  
      @property
      def max_volume(self) -> float:
          return self._geometry.max_volume
  
      @property
      def geometry(self) -> WellGeometry:
          return self._geometry
  
      @property  # type: ignore
      @requires_version(2, 0)
      def diameter(self) -> Optional[float]:
          return self._geometry.diameter
  
      @property  # type: ignore
      @requires_version(2, 9)
      def length(self) -> Optional[float]:
          """
          The length of a well, if the labware has
          square wells.
          """
          return self._geometry._length
  
      @property  # type: ignore
      @requires_version(2, 9)
      def width(self) -> Optional[float]:
          """
          The width of a well, if the labware has
          square wells.
          """
          return self._geometry._width
  
      @property  # type: ignore
      @requires_version(2, 9)
      def depth(self) -> float:
          """
          The depth of a well in a labware.
          """
          return self._geometry._depth
  
      @property
      def display_name(self):
          return self._impl.get_display_name()
  
      @property  # type: ignore
      @requires_version(2, 7)
      def well_name(self) -> str:
          return self._impl.get_name()
  
      @requires_version(2, 0)
      def top(self, z: float = 0.0) -> Location:
          """
          :param z: the z distance in mm
          :return: a Point corresponding to the absolute position of the
                   top-center of the well relative to the deck (with the
                   front-left corner of slot 1 as (0,0,0)). If z is specified,
                   returns a point offset by z mm from top-center
          """
          return Location(self._geometry.top(z), self)
  
      @requires_version(2, 0)
      def bottom(self, z: float = 0.0) -> Location:
          """
          :param z: the z distance in mm
          :return: a Point corresponding to the absolute position of the
                   bottom-center of the well (with the front-left corner of
                   slot 1 as (0,0,0)). If z is specified, returns a point
                   offset by z mm from bottom-center
          """
          return Location(self._geometry.bottom(z), self)
  
      @requires_version(2, 0)
      def center(self) -> Location:
          """
          :return: a Point corresponding to the absolute position of the center
                   of the well relative to the deck (with the front-left corner
                   of slot 1 as (0,0,0))
          """
          return Location(self._geometry.center(), self)
  
      @requires_version(2, 8)
      def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
          """
          Specifies an arbitrary point in deck coordinates based
          on percentages of the radius in each axis. For example, to specify the
          back-right corner of a well at 1/4 of the well depth from the bottom,
          the call would be ``from_center_cartesian(1, 1, -0.5)``.
  
          No checks are performed to ensure that the resulting position will be
          inside of the well.
  
          :param x: a float in the range [-1.0, 1.0] for a percentage of half of
              the radius/length in the X axis
          :param y: a float in the range [-1.0, 1.0] for a percentage of half of
              the radius/width in the Y axis
          :param z: a float in the range [-1.0, 1.0] for a percentage of half of
              the height above/below the center
  
          :return: a :py:class:`opentrons.types.Point` representing the specified
                   location in absolute deck coordinates
          """
          return self._geometry.from_center_cartesian(x, y, z)
  
      def _from_center_cartesian(self, x: float, y: float, z: float) -> Point:
          """
          Private version of from_center_cartesian. Present only for backward
          compatibility.
          """
          MODULE_LOG.warning(
              "This method is deprecated. Please use " "'from_center_cartesian' instead."
          )
          return self.from_center_cartesian(x, y, z)
  
      def __repr__(self):
          return self._impl.get_display_name()
  
      def __eq__(self, other: object) -> bool:
          """
          Assuming that equality of wells in this system is having the same
          absolute coordinates for the top.
          """
          if not isinstance(other, Well):
              return NotImplemented
          return self.top().point == other.top().point
  
      def __hash__(self):
          return hash(self.top().point)
  
  
  class Labware(DeckItem):
      """
      This class represents a labware, such as a PCR plate, a tube rack,
      reservoir, tip rack, etc. It defines the physical geometry of the labware,
      and provides methods for accessing wells within the labware.
  
      It is commonly created by calling ``ProtocolContext.load_labware()``.
  
      To access a labware's wells, you can use its well accessor methods:
      :py:meth:`wells_by_name`, :py:meth:`wells`, :py:meth:`columns`,
      :py:meth:`rows`, :py:meth:`rows_by_name`, and :py:meth:`columns_by_name`.
      You can also use an instance of a labware as a Python dictionary, accessing
      wells by their names. The following example shows how to use all of these
      methods to access well A1:
  
      .. code-block :: python
  
         labware = context.load_labware('corning_96_wellplate_360ul_flat', 1)
         labware['A1']
         labware.wells_by_name()['A1']
         labware.wells()[0]
         labware.rows()[0][0]
         labware.columns()[0][0]
         labware.rows_by_name()['A'][0]
         labware.columns_by_name()[0][0]
  
      """
  
      def __init__(
          self, implementation: AbstractLabware, api_level: Optional[APIVersion] = None
      ) -> None:
          """
          :param implementation: The class that implements the public interface
                                 of the class.
          :param APIVersion api_level: the API version to set for the instance.
                                       The :py:class:`.Labware` will
                                       conform to this level. If not specified,
                                       defaults to
                                       :py:attr:`.MAX_SUPPORTED_VERSION`.
          """
          if not api_level:
              api_level = MAX_SUPPORTED_VERSION
          if api_level > MAX_SUPPORTED_VERSION:
              raise RuntimeError(
                  f"API version {api_level} is not supported by this "
                  f"robot software. Please either reduce your requested API "
                  f"version or update your robot."
              )
          self._api_version = api_level
          self._implementation = implementation
  
      @property
      def separate_calibration(self) -> bool:
          return self._implementation.separate_calibration
  
      @property  # type: ignore
      @requires_version(2, 0)
      def api_version(self) -> APIVersion:
          return self._api_version
  
      def __getitem__(self, key: str) -> Well:
          return self.wells_by_name()[key]
  
      @property  # type: ignore
      @requires_version(2, 0)
      def uri(self) -> str:
          """A string fully identifying the labware.
  
          :returns: The uri, ``"namespace/loadname/version"``
          """
          return self._implementation.get_uri()
  
      @property  # type: ignore
      @requires_version(2, 0)
      def parent(self) -> LocationLabware:
          """The parent of this labware. Usually a slot name."""
          return self._implementation.get_geometry().parent.labware.object
  
      @property  # type: ignore
      @requires_version(2, 0)
      def name(self) -> str:
          """Can either be the canonical name of the labware, which is used to
          load it, or the label of the labware specified by a user."""
          return self._implementation.get_name()
  
      @name.setter
      def name(self, new_name):
          """Set the labware name"""
          self._implementation.set_name(new_name)
  
      @property  # type: ignore
      @requires_version(2, 0)
      def load_name(self) -> str:
          """The API load name of the labware definition"""
          return self._implementation.load_name
  
      @property  # type: ignore
      @requires_version(2, 0)
      def parameters(self) -> "LabwareParameters":
          """Internal properties of a labware including type and quirks"""
          return self._implementation.get_parameters()
  
      @property  # type: ignore
      @requires_version(2, 0)
      def quirks(self) -> List[str]:
          """Quirks specific to this labware."""
          return self._implementation.get_quirks()
  
      @property  # type: ignore
      @requires_version(2, 0)
      def magdeck_engage_height(self) -> Optional[float]:
          p = self._implementation.get_parameters()
          if not p["isMagneticModuleCompatible"]:
              return None
          else:
              return p["magneticModuleEngageHeight"]
  
      def set_calibration(self, delta: Point):
          """
          Called by save calibration in order to update the offset on the object.
          """
          self._implementation.set_calibration(delta)
  
      @property  # type: ignore
      @requires_version(2, 0)
      def calibrated_offset(self) -> Point:
          return self._implementation.get_calibrated_offset()
  
      @requires_version(2, 0)
      def well(self, idx) -> Well:
          """Deprecated---use result of `wells` or `wells_by_name`"""
          if isinstance(idx, int):
              res = self._implementation.get_wells()[idx]
          elif isinstance(idx, str):
              res = self._implementation.get_wells_by_name()[idx]
          else:
              res = NotImplemented
          return self._well_from_impl(res)
  
      @requires_version(2, 0)
      def wells(self, *args) -> List[Well]:
          """
          Accessor function used to generate a list of wells in top -> down,
          left -> right order. This is representative of moving down `rows` and
          across `columns` (e.g. 'A1', 'B1', 'C1'...'A2', 'B2', 'C2')
  
          With indexing one can treat it as a typical python
          list. To access well A1, for example, simply write: labware.wells()[0]
  
          Note that this method takes args for backward-compatibility, but use
          of args is deprecated and will be removed in future versions. Args
          can be either strings or integers, but must all be the same type (e.g.:
          `self.wells(1, 4, 8)` or `self.wells('A1', 'B2')`, but
          `self.wells('A1', 4)` is invalid.
  
          :return: Ordered list of all wells in a labware
          """
          if not args:
              res = self._implementation.get_wells()
          elif isinstance(args[0], int):
              res = [self._implementation.get_wells()[idx] for idx in args]
          elif isinstance(args[0], str):
              by_name = self._implementation.get_wells_by_name()
              res = [by_name[idx] for idx in args]
          else:
              raise TypeError
          return [self._well_from_impl(w) for w in res]
  
      @requires_version(2, 0)
      def wells_by_name(self) -> Dict[str, Well]:
          """
          Accessor function used to create a look-up table of Wells by name.
  
          With indexing one can treat it as a typical python
          dictionary whose keys are well names. To access well A1, for example,
          simply write: labware.wells_by_name()['A1']
  
          :return: Dictionary of well objects keyed by well name
          """
          wells = self._implementation.get_wells_by_name()
          return {k: self._well_from_impl(v) for k, v in wells.items()}
  
      @requires_version(2, 0)
      def wells_by_index(self) -> Dict[str, Well]:
          """
          .. deprecated:: 2.0
              Use :py:meth:`wells_by_name` or dict access instead.
          """
          MODULE_LOG.warning(
              "wells_by_index is deprecated. Use wells_by_name or dict access instead."
          )
          return self.wells_by_name()
  
      @requires_version(2, 0)
      def rows(self, *args) -> List[List[Well]]:
          """
          Accessor function used to navigate through a labware by row.
  
          With indexing one can treat it as a typical python nested list.
          To access row A for example, simply write: labware.rows()[0]. This
          will output ['A1', 'A2', 'A3', 'A4'...]
  
          Note that this method takes args for backward-compatibility, but use
          of args is deprecated and will be removed in future versions. Args
          can be either strings or integers, but must all be the same type (e.g.:
          `self.rows(1, 4, 8)` or `self.rows('A', 'B')`, but  `self.rows('A', 4)`
          is invalid.
  
          :return: A list of row lists
          """
          grid = self._implementation.get_well_grid()
          if not args:
              res = grid.get_rows()
          elif isinstance(args[0], int):
              res = [grid.get_rows()[idx] for idx in args]
          elif isinstance(args[0], str):
              res = [grid.get_row(idx) for idx in args]
          else:
              raise TypeError
          return [[self._well_from_impl(w) for w in row] for row in res]
  
      @requires_version(2, 0)
      def rows_by_name(self) -> Dict[str, List[Well]]:
          """
          Accessor function used to navigate through a labware by row name.
  
          With indexing one can treat it as a typical python dictionary.
          To access row A for example, simply write: labware.rows_by_name()['A']
          This will output ['A1', 'A2', 'A3', 'A4'...].
  
          :return: Dictionary of Well lists keyed by row name
          """
          row_dict = self._implementation.get_well_grid().get_row_dict()
          return {k: [self._well_from_impl(w) for w in v] for k, v in row_dict.items()}
  
      @requires_version(2, 0)
      def rows_by_index(self) -> Dict[str, List[Well]]:
          """
          .. deprecated:: 2.0
              Use :py:meth:`rows_by_name` instead.
          """
          MODULE_LOG.warning("rows_by_index is deprecated. Use rows_by_name instead.")
          return self.rows_by_name()
  
      @requires_version(2, 0)
      def columns(self, *args) -> List[List[Well]]:
          """
          Accessor function used to navigate through a labware by column.
  
          With indexing one can treat it as a typical python nested list.
          To access row A for example,
          simply write: labware.columns()[0]
          This will output ['A1', 'B1', 'C1', 'D1'...].
  
          Note that this method takes args for backward-compatibility, but use
          of args is deprecated and will be removed in future versions. Args
          can be either strings or integers, but must all be the same type (e.g.:
          `self.columns(1, 4, 8)` or `self.columns('1', '2')`, but
          `self.columns('1', 4)` is invalid.
  
          :return: A list of column lists
          """
          grid = self._implementation.get_well_grid()
          if not args:
              res = grid.get_columns()
          elif isinstance(args[0], int):
              res = [grid.get_columns()[idx] for idx in args]
          elif isinstance(args[0], str):
              res = [grid.get_column(idx) for idx in args]
          else:
              raise TypeError
          return [[self._well_from_impl(w) for w in col] for col in res]
  
      @requires_version(2, 0)
      def columns_by_name(self) -> Dict[str, List[Well]]:
          """
          Accessor function used to navigate through a labware by column name.
  
          With indexing one can treat it as a typical python dictionary.
          To access row A for example,
          simply write: labware.columns_by_name()['1']
          This will output ['A1', 'B1', 'C1', 'D1'...].
  
          :return: Dictionary of Well lists keyed by column name
          """
          column_dict = self._implementation.get_well_grid().get_column_dict()
          return {k: [self._well_from_impl(w) for w in v] for k, v in column_dict.items()}
  
      @requires_version(2, 0)
      def columns_by_index(self) -> Dict[str, List[Well]]:
          """
          .. deprecated:: 2.0
              Use :py:meth:`columns_by_name` instead.
          """
          MODULE_LOG.warning(
              "columns_by_index is deprecated. Use columns_by_name instead."
          )
          return self.columns_by_name()
  
      @property  # type: ignore
      @requires_version(2, 0)
      def highest_z(self) -> float:
          """
          The z-coordinate of the tallest single point anywhere on the labware.
  
          This is drawn from the 'dimensions'/'zDimension' elements of the
          labware definition and takes into account the calibration offset.
          """
          return self._implementation.highest_z
  
      @property
      def _is_tiprack(self) -> bool:
          """as is_tiprack but not subject to version checking for speed"""
          return self._implementation.is_tiprack()
  
      @property  # type: ignore
      @requires_version(2, 0)
      def is_tiprack(self) -> bool:
          return self._is_tiprack
  
      @property  # type: ignore
      @requires_version(2, 0)
      def tip_length(self) -> float:
          return self._implementation.get_tip_length()
  
      @tip_length.setter
      def tip_length(self, length: float):
          self._implementation.set_tip_length(length)
  
      def next_tip(
          self, num_tips: int = 1, starting_tip: Optional[Well] = None
      ) -> Optional[Well]:
          """
          Find the next valid well for pick-up.
  
          Determines the next valid start tip from which to retrieve the
          specified number of tips. There must be at least `num_tips` sequential
          wells for which all wells have tips, in the same column.
  
          :param num_tips: target number of sequential tips in the same column
          :type num_tips: int
          :param starting_tip: The :py:class:`.Well` from which to start search.
                  for an available tip.
          :type starting_tip: :py:class:`.Well`
          :return: the :py:class:`.Well` meeting the target criteria, or None
          """
          assert num_tips > 0, "Bad call to next_tip: num_tips <= 0"
  
          well = self._implementation.get_tip_tracker().next_tip(
              num_tips=num_tips, starting_tip=starting_tip._impl if starting_tip else None
          )
          return self._well_from_impl(well) if well else None
  
      def use_tips(self, start_well: Well, num_channels: int = 1):
          """
          Removes tips from the tip tracker.
  
          This method should be called when a tip is picked up. Generally, it
          will be called with `num_channels=1` or `num_channels=8` for single-
          and multi-channel respectively. If picking up with more than one
          channel, this method will automatically determine which tips are used
          based on the start well, the number of channels, and the geometry of
          the tiprack.
  
          :param start_well: The :py:class:`.Well` from which to pick up a tip.
                             For a single-channel pipette, this is the well to
                             send the pipette to. For a multi-channel pipette,
                             this is the well to send the back-most nozzle of the
                             pipette to.
          :type start_well: :py:class:`.Well`
          :param num_channels: The number of channels for the current pipette
          :type num_channels: int
          """
          assert num_channels > 0, "Bad call to use_tips: num_channels<=0"
  
          fail_if_full = self._api_version < APIVersion(2, 2)
  
          self._implementation.get_tip_tracker().use_tips(
              start_well=start_well._impl,
              num_channels=num_channels,
              fail_if_full=fail_if_full,
          )
  
      def __repr__(self):
          return self._implementation.get_display_name()
  
      def __eq__(self, other: object) -> bool:
          if not isinstance(other, Labware):
              return NotImplemented
          return self._implementation == other._implementation
  
      def __hash__(self):
          return hash((self._implementation, self._api_version))
  
      def previous_tip(self, num_tips: int = 1) -> Optional[Well]:
          """
          Find the best well to drop a tip in.
  
          This is the well from which the last tip was picked up, if there's
          room. It can be used to return tips to the tip tracker.
  
          :param num_tips: target number of tips to return, sequential in a
                           column
          :type num_tips: int
          :return: The :py:class:`.Well` meeting the target criteria, or ``None``
          """
          # This logic is the inverse of :py:meth:`next_tip`
          assert num_tips > 0, "Bad call to previous_tip: num_tips <= 0"
  
          well = self._implementation.get_tip_tracker().previous_tip(num_tips=num_tips)
          return self._well_from_impl(well) if well else None
  
      def return_tips(self, start_well: Well, num_channels: int = 1):
          """
          Re-adds tips to the tip tracker
  
          This method should be called when a tip is dropped in a tiprack. It
          should be called with ``num_channels=1`` or ``num_channels=8`` for
          single- and multi-channel respectively. If returning more than one
          channel, this method will automatically determine which tips are
          returned based on the start well, the number of channels,
          and the tiprack geometry.
  
          Note that unlike :py:meth:`use_tips`, calling this method in a way
          that would drop tips into wells with tips in them will raise an
          exception; this should only be called on a valid return of
          :py:meth:`previous_tip`.
  
          :param start_well: The :py:class:`.Well` into which to return a tip.
          :type start_well: :py:class:`.Well`
          :param num_channels: The number of channels for the current pipette
          :type num_channels: int
          """
          # This logic is the inverse of :py:meth:`use_tips`
          assert num_channels > 0, "Bad call to return_tips: num_channels <= 0"
  
          self._implementation.get_tip_tracker().return_tips(
              start_well=start_well._impl, num_channels=num_channels
          )
  
      @requires_version(2, 0)
      def reset(self):
          """Reset all tips in a tiprack"""
          if self._is_tiprack:
              self._implementation.reset_tips()
  
      def _well_from_impl(self, well: WellImplementation) -> Well:
          return Well(well_implementation=well, api_level=self._api_version)
  
  
  def save_definition(
      labware_def: "LabwareDefinition",
      force: bool = False,
      location: Optional[Path] = None,
  ) -> None:
      """
      Save a labware definition
  
      :param labware_def: A deserialized JSON labware definition
      :param bool force: If true, overwrite an existing definition if found.
          Cannot overwrite Opentrons definitions.
      :param location: The path of the labware definition.
      """
      labware_module.save_definition(
          labware_def=labware_def, force=force, location=location
      )
  
  
  def verify_definition(
      contents: Union[AnyStr, "LabwareDefinition", Dict[str, Any]]
  ) -> "LabwareDefinition":
      """Verify that an input string is a labware definition and return it.
  
      If the definition is invalid, an exception is raised; otherwise parse the
      json and return the valid definition.
  
      :raises json.JSONDecodeError: If the definition is not valid json
      :raises: ``jsonschema.ValidationError`` -- if the definition is not valid.
      :returns: The parsed definition
      """
      return labware_module.verify_definition(contents=contents)
  
  
  def get_labware_definition(
      load_name: str,
      namespace: Optional[str] = None,
      version: Optional[int] = None,
      bundled_defs: Dict[str, "LabwareDefinition"] = None,
      extra_defs: Dict[str, "LabwareDefinition"] = None,
  ) -> "LabwareDefinition":
      """
      Look up and return a definition by load_name + namespace + version and
          return it or raise an exception
  
      :param str load_name: corresponds to 'loadName' key in definition
      :param str namespace: The namespace the labware definition belongs to.
          If unspecified, will search 'opentrons' then 'custom_beta'
      :param int version: The version of the labware definition. If unspecified,
          will use version 1.
      :param bundled_defs: A bundle of labware definitions to exlusively use for
          finding labware definitions, if specified
      :param extra_defs: An extra set of definitions (in addition to the system
          definitions) in which to search
      """
      return labware_module.get_labware_definition(
          load_name=load_name,
          namespace=namespace,
          version=version,
          bundled_defs=bundled_defs,
          extra_defs=extra_defs,
      )
  
  
  def get_all_labware_definitions() -> List[str]:
      """
      Return a list of standard and custom labware definitions with load_name +
          name_space + version existing on the robot
      """
      return labware_module.get_all_labware_definitions()
  
  
  def split_tipracks(tip_racks: List[Labware]) -> Tuple[Labware, List[Labware]]:
      try:
          rest = tip_racks[1:]
      except IndexError:
          rest = []
      return tip_racks[0], rest
  
  
  def select_tiprack_from_list(
      tip_racks: List[Labware], num_channels: int, starting_point: Optional[Well] = None
  ) -> Tuple[Labware, Well]:
  
      try:
          first, rest = split_tipracks(tip_racks)
      except IndexError:
          raise OutOfTipsError
  
      if starting_point and starting_point.parent != first:
          raise TipSelectionError(
              "The starting tip you selected " f"does not exist in {first}"
          )
      elif starting_point:
          first_well = starting_point
      else:
          first_well = first.wells()[0]
  
      next_tip = first.next_tip(num_channels, first_well)
      if next_tip:
          return first, next_tip
      else:
          return select_tiprack_from_list(rest, num_channels)
  
  
  def select_tiprack_from_list_paired_pipettes(
      tip_racks: List[Labware],
      p_channels: int,
      s_channels: int,
      starting_point: Optional[Well] = None,
  ) -> Tuple[Labware, Well]:
      """
      Helper function utilized in ``PairedInstrumentContext``
      to determine which pipette tiprack to pick up from.
  
      If a starting point is specified, this method with check
      that the parent of that tip was correctly filtered.
  
      If a starting point is not specified, this method will filter
      tipracks until it finds a well that is not empty.
  
      :return: A Tuple of the tiprack and well to move to. In this
               instance the starting well is specific to the primary pipette.
      :raises TipSelectionError: if the starting tip specified
                                 does not exist in the filtered tipracks.
      """
      try:
          first, rest = split_tipracks(tip_racks)
      except IndexError:
          raise OutOfTipsError
  
      if starting_point and starting_point.parent != first:
          raise TipSelectionError(
              "The starting tip you selected " f"does not exist in {first}"
          )
      elif starting_point:
          primary_well = starting_point
      else:
          primary_well = first.wells()[0]
  
      try:
          secondary_point = labware_column_shift(primary_well, first)
      except IndexError:
          return select_tiprack_from_list_paired_pipettes(rest, p_channels, s_channels)
  
      primary_next_tip = first.next_tip(p_channels, starting_point)
      secondary_next_tip = first.next_tip(s_channels, secondary_point)
      if primary_next_tip and secondary_next_tip:
          return first, primary_next_tip
      else:
          return select_tiprack_from_list_paired_pipettes(rest, p_channels, s_channels)
  
  
  def filter_tipracks_to_start(
      starting_point: Well, tipracks: List[Labware]
  ) -> List[Labware]:
      return list(dropwhile(lambda tr: starting_point.parent != tr, tipracks))
  
  
  def next_available_tip(
      starting_tip: Optional[Well], tip_racks: List[Labware], channels: int
  ) -> Tuple[Labware, Well]:
      start = starting_tip
      if start is None:
          return select_tiprack_from_list(tip_racks, channels)
      else:
          return select_tiprack_from_list(
              filter_tipracks_to_start(start, tip_racks), channels, start
          )
  
  
  def get_labware_hash(labware: "Labware") -> str:
      return labware_module.get_labware_hash(labware._implementation)
  
  
  def get_labware_hash_with_parent(labware: "Labware") -> str:
      return labware_module.get_labware_hash_with_parent(labware._implementation)
  
  
  def load_from_definition(
      definition: "LabwareDefinition",
      parent: Location,
      label: Optional[str] = None,
      api_level: Optional[APIVersion] = None,
  ) -> Labware:
      """
      Return a labware object constructed from a provided labware definition dict
  
      :param definition: A dict representing all required data for a labware,
          including metadata such as the display name of the labware, a
          definition of the order to iterate over wells, the shape of wells
          (shape, physical dimensions, etc), and so on. The correct shape of
          this definition is governed by the "labware-designer" project in
          the Opentrons/opentrons repo.
      :param parent: A :py:class:`.Location` representing the location where
                     the front and left most point of the outside of labware is
                     (often the front-left corner of a slot on the deck).
      :param str label: An optional label that will override the labware's
                        display name from its definition
      :param api_level: the API version to set for the loaded labware
                        instance. The :py:class:`.Labware` will
                        conform to this level. If not specified,
                        defaults to ``MAX_SUPPORTED_VERSION``.
      """
      return Labware(
          implementation=labware_module.load_from_definition(
              definition=definition, parent=parent, label=label
          ),
          api_level=api_level,
      )
  
  
  def save_calibration(labware: "Labware", delta: Point):
      labware_module.save_calibration(labware._implementation, delta)
  
  
  def load(
      load_name: str,
      parent: Location,
      label: Optional[str] = None,
      namespace: Optional[str] = None,
      version: int = 1,
      bundled_defs: Optional[Dict[str, "LabwareDefinition"]] = None,
      extra_defs: Optional[Dict[str, "LabwareDefinition"]] = None,
      api_level: Optional[APIVersion] = None,
  ) -> Labware:
      """
      Return a labware object constructed from a labware definition dict looked
      up by name (definition must have been previously stored locally on the
      robot)
  
      :param load_name: A string to use for looking up a labware definition
          previously saved to disc. The definition file must have been saved in a
          known location
      :param parent: A :py:class:`.Location` representing the location where
                     the front and left most point of the outside of labware is
                     (often the front-left corner of a slot on the deck).
      :param str label: An optional label that will override the labware's
                        display name from its definition
      :param str namespace: The namespace the labware definition belongs to.
          If unspecified, will search 'opentrons' then 'custom_beta'
      :param int version: The version of the labware definition. If unspecified,
          will use version 1.
      :param bundled_defs: If specified, a mapping of labware names to labware
          definitions. Only the bundle will be searched for definitions.
      :param extra_defs: If specified, a mapping of labware names to labware
          definitions. If no bundle is passed, these definitions will also be
          searched.
      :param api_level: the API version to set for the loaded labware
                        instance. The :py:class:`.Labware` will
                        conform to this level. If not specified,
                        defaults to ``MAX_SUPPORTED_VERSION``.
      """
  
      return Labware(
          implementation=labware_module.load(
              load_name=load_name,
              parent=parent,
              label=label,
              namespace=namespace,
              version=version,
              bundled_defs=bundled_defs,
              extra_defs=extra_defs,
          ),
          api_level=api_level,
      )
  