Skip to content

Commit

Permalink
docs(api): overhaul Labware docstrings (#14401)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecormany authored Feb 13, 2024
1 parent e0aa995 commit 0a410c8
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 78 deletions.
2 changes: 2 additions & 0 deletions api/docs/v2/new_labware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ After you've created your labware, save it as a ``.json`` file and add it to the

If other people need to use your custom labware definition, they must also add it to their Opentrons App.

.. _loading-labware:

***************
Loading Labware
***************
Expand Down
197 changes: 119 additions & 78 deletions api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
transform from labware symbolic points (such as "well a1 of an opentrons
tiprack") to points in deck coordinates.
"""

from __future__ import annotations

import logging
Expand Down Expand Up @@ -296,29 +297,22 @@ def __hash__(self) -> int:

class Labware:
"""
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]
This class represents a piece of labware.
Labware available in the API generally fall under two categories.
- Consumable labware: well plates, tubes in racks, reservoirs, tip racks, etc.
- Adapters: durable items that hold other labware, either on modules or directly
on the deck.
The ``Labware`` class defines the physical geometry of the labware
and provides methods for :ref:`accessing wells <new-well-access>` within the labware.
Create ``Labware`` objects by calling the appropriate ``load_labware()`` method,
depending on where you are loading the labware. For example, to load labware on a
Thermocycler Module, use :py:meth:`.ThermocyclerContext.load_labware`. To load
labware directly on the deck, use :py:meth:`.ProtocolContext.load_labware`. See
:ref:`loading-labware`.
"""

Expand Down Expand Up @@ -370,6 +364,7 @@ def separate_calibration(self) -> bool:
@property
@requires_version(2, 0)
def api_version(self) -> APIVersion:
"""See :py:obj:`.ProtocolContext.api_version`."""
return self._api_version

def __getitem__(self, key: str) -> Well:
Expand All @@ -380,14 +375,17 @@ def __getitem__(self, key: str) -> Well:
def uri(self) -> str:
"""A string fully identifying the labware.
:returns: The URI, ``"namespace/loadname/version"``
The URI has three parts and follows the pattern ``"namespace/load_name/version"``.
For example, ``opentrons/corning_96_wellplate_360ul_flat/2``.
"""
return self._core.get_uri()

@property
@requires_version(2, 0)
def parent(self) -> Union[str, Labware, ModuleTypes, OffDeckType]:
"""The parent of this labware---where this labware is loaded.
"""Where the labware is loaded.
This corresponds to the physical object that the labware *directly* rests upon.
Returns:
If the labware is directly on the robot's deck, the ``str`` name of the deck slot,
Expand All @@ -401,10 +399,10 @@ def parent(self) -> Union[str, Labware, ModuleTypes, OffDeckType]:
.. versionchanged:: 2.14
Return type for module parent changed.
Prior to this version, an internal geometry interface was returned.
Formerly, the API returned an internal geometry interface.
.. versionchanged:: 2.15
Will return a :py:class:`Labware` if the labware is loaded onto a labware/adapter.
Will now return :py:obj:`OFF_DECK` if the labware is off-deck.
Returns a :py:class:`Labware` if the labware is loaded onto a labware/adapter.
Returns :py:obj:`OFF_DECK` if the labware is off-deck.
Formerly, if the labware was removed by using ``del`` on :py:obj:`.deck`,
this would return where it was before its removal.
"""
Expand All @@ -424,8 +422,13 @@ def parent(self) -> Union[str, Labware, ModuleTypes, OffDeckType]:
@property
@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."""
"""The display name of the labware.
If you specified a value for ``label`` when loading the labware, ``name`` is
that value.
Otherwise, it is the :py:obj:`~.Labware.load_name` of the labware.
"""
return self._core.get_name()

@name.setter
Expand Down Expand Up @@ -536,13 +539,13 @@ def load_labware(
def load_labware_from_definition(
self, definition: LabwareDefinition, label: Optional[str] = None
) -> Labware:
"""Load a labware onto the module using an inline definition.
"""Load a compatible labware onto the labware using an inline definition.
:param definition: The labware definition.
:param str label: An optional special name to give the labware. If
specified, this is the name the labware will appear
as in the run log and the calibration view in the
Opentrons App.
:param str label: An optional special name to give the labware. If specified,
this is how the labware will appear in the run log, Labware Position
Check, and elsewhere in the Opentrons App and on the touchscreen.
:returns: The initialized and loaded labware object.
"""
load_params = self._protocol_core.add_labware_definition(definition)
Expand All @@ -556,7 +559,7 @@ def load_labware_from_definition(

def set_calibration(self, delta: Point) -> None:
"""
An internal, deprecated method used for updating the offset on the object.
An internal, deprecated method used for updating the labware offset.
.. deprecated:: 2.14
"""
Expand All @@ -576,21 +579,19 @@ def set_offset(self, x: float, y: float, z: float) -> None:
(see :ref:`protocol-api-deck-coords`) that the motion system
will add to any movement targeting this labware instance.
The offset *will not* apply to any other labware instances,
The offset *will not apply* to any other labware instances,
even if those labware are of the same type.
.. caution::
This method is *only* for use with mechanisms like
:obj:`opentrons.execute.get_protocol_api`, which lack an interactive way
to adjust labware offsets. (See :ref:`advanced-control`.)
This method is *only* for use with mechanisms like
:obj:`opentrons.execute.get_protocol_api`, which lack an interactive way
to adjust labware offsets. (See :ref:`advanced-control`.)
.. warning::
If you're uploading a protocol via the Opentrons App, don't use this method,
because it will produce undefined behavior.
Instead, use Labware Position Check in the app.
Instead, use Labware Position Check in the app or on the touchscreen.
Because protocols using :ref:`API version <v2-versioning>` 2.14 or higher
can currently *only* be uploaded via the Opentrons App, it doesn't make
sense to use this method with them. Trying to do so will raise an exception.
"""
if self._api_version >= ENGINE_CORE_API_VERSION:
# TODO(mm, 2023-02-13): See Jira RCORE-535.
Expand All @@ -610,6 +611,11 @@ def set_offset(self, x: float, y: float, z: float) -> None:
@property
@requires_version(2, 0)
def calibrated_offset(self) -> Point:
"""The front-left-bottom corner of the labware, including its labware offset.
When running a protocol in the Opentrons App or on the touchscreen, Labware
Position Check sets the labware offset.
"""
return self._core.get_calibrated_offset()

@requires_version(2, 0)
Expand All @@ -627,14 +633,19 @@ def well(self, idx: Union[int, str]) -> Well:
@requires_version(2, 0)
def wells(self, *args: Union[str, int]) -> List[Well]:
"""
Accessor function that generates a list of wells in a top down,
left to right order. This is representative of moving down rows and
across columns (i.e., A1, B1, C1…A2, B2, C2…).
Accessor function to navigate a labware top to bottom, left to right.
With indexing one can treat it as a typical python
list. For example, access well A1 with ``labware.wells()[0]``.
i.e., this method returns a list ordered A1, B1, C1…A2, B2, C2….
Note that this method takes args for backward-compatibility. But using args is deprecated and will be removed in future versions. Args can be either strings or integers, but must all be the same type. For example, ``self.columns(1, 4, 8)`` or ``self.columns('1', '2')`` are valid, but ``self.columns('1', 4)`` is not.
Use indexing to access individual wells contained in the list.
For example, access well A1 with ``labware.wells()[0]``.
.. note::
Using args with this method is deprecated. Use indexing instead.
If your code uses args, they can be either strings or integers, but not a
mix of the two. For example, ``.wells(1, 4)`` or ``.wells("1", "4")`` is
valid, but ``.wells("1", 4)`` is not.
:return: Ordered list of all wells in a labware.
"""
Expand All @@ -658,11 +669,10 @@ def wells(self, *args: Union[str, int]) -> List[Well]:
@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.
Accessor function used to navigate through a labware by well name.
With indexing one can treat it as a typical Python
dictionary whose keys are well names. For example, access well A1
with ``labware.wells_by_name()['A1']``.
Use indexing to access individual wells contained in the dictionary.
For example, access well A1 with ``labware.wells_by_name()["A1"]``.
:return: Dictionary of :py:class:`.Well` objects keyed by well name.
"""
Expand All @@ -682,13 +692,18 @@ def wells_by_index(self) -> Dict[str, Well]:
@requires_version(2, 0)
def rows(self, *args: Union[int, str]) -> List[List[Well]]:
"""
Accessor function used to navigate through a labware by row.
Accessor function to navigate through a labware by row.
With indexing one can treat it as a typical python nested list.
For example, access row A with ``labware.rows()[0]``. This
will output ``['A1', 'A2', 'A3', 'A4'...]``.
Use indexing to access individual rows or wells contained in the nested list.
On a standard 96-well plate, this will output a list of :py:class:`.Well`
objects containing A1 through A12.
Note that this method takes args for backward-compatibility. But using args is deprecated and will be removed in future versions. Args can be either strings or integers, but must all be the same type. For example, ``self.columns(1, 4, 8)`` or ``self.columns('1', '2')`` are valid, but ``self.columns('1', 4)`` is not.
.. note::
Using args with this method is deprecated. Use indexing instead.
If your code uses args, they can be either strings or integers, but not a
mix of the two. For example, ``.rows(1, 4)`` or ``.rows("1", "4")`` is
valid, but ``.rows("1", 4)`` is not.
:return: A list of row lists.
"""
Expand All @@ -715,11 +730,12 @@ def rows(self, *args: Union[int, str]) -> List[List[Well]]:
@requires_version(2, 0)
def rows_by_name(self) -> Dict[str, List[Well]]:
"""
Accessor function used to navigate through a labware by row name.
Accessor function to navigate through a labware by row name.
With indexing one can treat it as a typical python dictionary.
For example, access row A with ``labware.rows_by_name()['A']``.
This will output ``['A1', 'A2', 'A3', 'A4'...]``.
Use indexing to access individual rows or wells contained in the dictionary.
For example, access row A with ``labware.rows_by_name()["A"]``.
On a standard 96-well plate, this will output a list of :py:class:`.Well`
objects containing A1 through A12.
:return: Dictionary of :py:class:`.Well` lists keyed by row name.
"""
Expand All @@ -740,16 +756,19 @@ def rows_by_index(self) -> Dict[str, List[Well]]:
@requires_version(2, 0)
def columns(self, *args: Union[int, str]) -> List[List[Well]]:
"""
Accessor function used to navigate through a labware by column.
Accessor function to navigate through a labware by column.
Use indexing to access individual columns or wells contained in the nested list.
For example, access column 1 with ``labware.columns()[0]``.
On a standard 96-well plate, this will output a list of :py:class:`.Well`
objects containing A1 through H1.
With indexing one can treat it as a typical python nested list.
For example, access row A with ``labware.columns()[0]``.
This will output ``['A1', 'B1', 'C1', 'D1'...]``.
.. note::
Using args with this method is deprecated. Use indexing instead.
Note that this method takes args for backward-compatibility. But using args is deprecated and will be removed in future versions. Args
can be either strings or integers, but must all be the same type. For example,
``self.columns(1, 4, 8)`` or ``self.columns('1', '2')`` are valid, but
``self.columns('1', 4)`` is not.
If your code uses args, they can be either strings or integers, but not a
mix of the two. For example, ``.columns(1, 4)`` or ``.columns("1", "4")`` is
valid, but ``.columns("1", 4)`` is not.
:return: A list of column lists.
"""
Expand All @@ -776,11 +795,12 @@ def columns(self, *args: Union[int, str]) -> List[List[Well]]:
@requires_version(2, 0)
def columns_by_name(self) -> Dict[str, List[Well]]:
"""
Accessor function used to navigate through a labware by column name.
Accessor function to navigate through a labware by column name.
With indexing one can treat it as a typical python dictionary.
For example, access row A with ``labware.columns_by_name()['1']``.
This will output ``['A1', 'B1', 'C1', 'D1'...]``.
Use indexing to access individual columns or wells contained in the dictionary.
For example, access column 1 with ``labware.columns_by_name()["1"]``.
On a standard 96-well plate, this will output a list of :py:class:`.Well`
objects containing A1 through H1.
:return: Dictionary of :py:class:`.Well` lists keyed by column name.
"""
Expand All @@ -805,7 +825,7 @@ def highest_z(self) -> float:
The z-coordinate of the highest single point anywhere on the labware.
This is taken from the ``zDimension`` property of the ``dimensions`` object in the
labware definition and takes into account the calibration offset.
labware definition and takes into account the labware offset.
"""
return self._core.highest_z

Expand All @@ -817,16 +837,31 @@ def _is_tiprack(self) -> bool:
@property
@requires_version(2, 0)
def is_tiprack(self) -> bool:
"""Whether the labware behaves as a tip rack.
Returns ``True`` if the labware definition specifies ``isTiprack`` as ``True``.
"""
return self._is_tiprack

@property
@requires_version(2, 15)
def is_adapter(self) -> bool:
"""Whether the labware behaves as an adapter.
Returns ``True`` if the labware definition specifies ``adapter`` as one of the
labware's ``allowedRoles``.
"""
return self._core.is_adapter()

@property
@requires_version(2, 0)
def tip_length(self) -> float:
"""For a tip rack labware, the length of the tips it holds, in mm.
This is taken from the ``tipLength`` property of the ``parameters`` object in the labware definition.
This method will raise an exception if you call it on a labware that isn’t a tip rack.
"""
return self._core.get_tip_length()

@tip_length.setter
Expand Down Expand Up @@ -1000,7 +1035,13 @@ def return_tips(self, start_well: Well, num_channels: int = 1) -> None:

@requires_version(2, 0)
def reset(self) -> None:
"""Reset all tips in a tip rack.
"""Reset tip tracking for a tip rack.
After resetting, the API treats all wells on the rack as if they contain unused tips.
This is useful if you want to reuse tips after calling :py:meth:`.return_tip()`.
If you need to physically replace an empty tip rack in the middle of your protocol,
use :py:meth:`.move_labware()` instead. See :ref:`off-deck-location` for an example.
.. versionchanged:: 2.14
This method will raise an exception if you call it on a labware that isn't
Expand Down

0 comments on commit 0a410c8

Please sign in to comment.