-
Notifications
You must be signed in to change notification settings - Fork 178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(api, shared-data): Expand Labware architecture to accommodate Lids #17072
base: edge
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still need to get through more of the protocol engine code, but I already have some comments and suggestions for change
:param str load_name: A string to use for looking up a lid definition. | ||
You can find the ``load_name`` for any standard lid on the Opentrons | ||
`Labware Library <https://labware.opentrons.com>`_. | ||
:param location: ither a :ref:`deck slot <deck-slots>`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:param location: ither a :ref:`deck slot <deck-slots>`, | |
:param location: Either a :ref:`deck slot <deck-slots>`, |
def load_lid_stack( | ||
self, | ||
load_name: str, | ||
location: Union[DeckLocation, Labware], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this load_lid_stack
in ProtocolContext should only be responsible for loading on a DeckLocation, and there should be a separate load_lid_stack
in the Labware
context, which fits with our existing pattern of loading adapters and labware onto an existing labware
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm you're right that there should be one available for the labware context as well (to do things like adapter.load_lid_stack(load_name, quantity)
), however in fitting with our current structure for load_labware
, where we allow loading labware and it's adapter simultaneously, I would actually propose adding an optional adapter field to this new command so that we more closely mirror the setup of load_labware
's current cross-context behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main difference there with the optional adapter field is it's also loading/creating the adapter along with the labware, not placing an existing adapter object on the labware. It's the reason why I have no issue with the optional lid
field added to load_labware
and the like. I'm not sure I'd like putting an optional labware name string here instead and loading that, than the lid stack on top of that, but it'd be closer to that pattern we've established
quantity: int, | ||
namespace: Optional[str] = None, | ||
version: Optional[int] = None, | ||
) -> Union[DeckLocation, Labware]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why we are returning the location when it's already one of the arguments? This also is going against our pattern of returning the object being loaded, I'd rather just return nothing if we're not representing lids as Python object in the protocol.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of the items currently in debate. Seth had an interesting proposal that the "Lid Stack" item should be a literal flat empty labware object that we return, which constitutes the "bottom" of the stack and is what will get handled when determining sources and destinations for lid stacks. The current approach is based on the idea that stacks are really just living dictionaries of "stacks" of lids around the deck, keyed by location. In theory both of these approaches get the same end result, however what the user handles in their protocol changes slightly between an Abstract "Labware" like object and a literal "Location" they'd refer to as a general alias for their stack. I'll be exploring what the former implementation idea would look like, though deallocating them or dealing with "empty" stacks might be a little awkward in places.
""" | ||
load_location: Union[DeckSlotName, StagingSlotName, Labware] | ||
if isinstance(location, Labware): | ||
load_location = location |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we are going to be passing a labware here (see my above comment for why I think we shouldn't), it should be a labware core
quantity=params.quantity, | ||
labware_ids=None, | ||
) | ||
for i in range(params.quantity): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this whole loop is confusing to read I feel like this whole thing can be rewritten as a sequence of list comprehensions, which might be easier to comprehend (pun semi-intended). As it is it's a lot of index manipulation on very similarly named lists and sets in a single block
) | ||
|
||
if labware_ids is None: | ||
labware_ids = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Easy list comprehension rewrite
version=version, | ||
) | ||
|
||
if definition.stackLimit is not None and quantity > definition.stackLimit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we not have a default stack limit?
|
||
# Allow propagation of ModuleNotLoadedError. | ||
load_labware_data_list = [] | ||
for i in range(quantity): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I dislike the pattern of using for i in range(...)
and special logic for a specific index number, since most of them can be rewritten as list comprehensions. This one for instance could easily be (after checking the initial location)
locations = [location]
locations.extend([OnLabwareLocation(labwareId=labware_id) for labware_id in labware_Ids)
load_labware_data_list = [LoadedLabwareData(....) for location in locations]
Something like this
def _add_loaded_lid_stack(self, state_update: update_types.StateUpdate) -> None: | ||
loaded_lid_stack_update = state_update.loaded_lid_stack | ||
if loaded_lid_stack_update != update_types.NO_CHANGE: | ||
for i in range(len(loaded_lid_stack_update.labware_ids)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for i in range(len(loaded_lid_stack_update.labware_ids)): | |
for labware_id in loaded_lid_stack_update.labware_ids: |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## edge #17072 +/- ##
===========================================
- Coverage 92.43% 74.06% -18.38%
===========================================
Files 77 43 -34
Lines 1283 3258 +1975
===========================================
+ Hits 1186 2413 +1227
- Misses 97 845 +748
Flags with carried forward coverage won't be shown. Click here to find out more.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me pending consensus with @jbleon95
@@ -443,6 +444,10 @@ def load_labware( | |||
values as the ``load_name`` parameter of :py:meth:`.load_adapter`. The | |||
adapter will use the same namespace as the labware, and the API will | |||
choose the adapter's version automatically. | |||
:param lid: An lid to load the on top of the main labware. Accepts the same |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:param lid: An lid to load the on top of the main labware. Accepts the same | |
:param lid: A lid to load the on top of the main labware. Accepts the same |
Overview
Covers EXEC-1000, EXEC-1001, EXEC-1002, EXEC-1004
This PR seeks to remove Lids as a handled labware concept during a protocol, and treat them more as an attribute of a labware rather than a primary labware themselves.
TODO:
Test Plan and Hands on Testing
(Assuming Max Supported Version is 2,22)
load_lid_stack
command causes analysis failureload_labware
withlid
parameter causes analysis failure(Assuming Max Supported Version bumped to 2,23)
load_lid_stack
command loads a stack of X lids at given location, passes analysisload_labware
command while utilizing thelid
parameter results in a loaded labware and lid, passes analysisChangelog
load_labware
PAPI commands to accept optionallid
fieldload_lid_stack
command to allow for the loading of lids in batchesReview requests
Risk assessment
Medium - we will be gating the behavior behind an API version so that users working with TC lids in existing protocols on existing API versions are unaffected, but future protocol drafting and labware behavior will be effected.