-
Notifications
You must be signed in to change notification settings - Fork 172
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(protocol-engine): implement well & labware's well accessor methods #8151
Conversation
} | ||
self._well_grid = self._get_well_grid() | ||
else: | ||
raise Exception("Labware definition not found. Definition required for " |
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.
what kind of Exception do we want here? Make a custom LabwareDefinitionNotFound
error?
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.
labware.get_labware_definition
(which calls labware.get_labware_data_by_id
and labware.get_definition_by_uri
) will already raise if the labware ID or labware definition isn't found. For example:
opentrons/api/src/opentrons/protocol_engine/state/labware.py
Lines 144 to 151 in d9c7ed2
def get_definition_by_uri(self, uri: LabwareUri) -> LabwareDefinition: | |
"""Get the labware definition matching loadName namespace and version.""" | |
try: | |
return self._state.labware_definitions_by_uri[uri] | |
except KeyError: | |
raise errors.LabwareDefinitionDoesNotExistError( | |
f"Labware definition for matching {uri} not found." | |
) |
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.
Some early initial feedback
} | ||
self._well_grid = self._get_well_grid() | ||
else: | ||
raise Exception("Labware definition not found. Definition required for " |
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.
labware.get_labware_definition
(which calls labware.get_labware_data_by_id
and labware.get_definition_by_uri
) will already raise if the labware ID or labware definition isn't found. For example:
opentrons/api/src/opentrons/protocol_engine/state/labware.py
Lines 144 to 151 in d9c7ed2
def get_definition_by_uri(self, uri: LabwareUri) -> LabwareDefinition: | |
"""Get the labware definition matching loadName namespace and version.""" | |
try: | |
return self._state.labware_definitions_by_uri[uri] | |
except KeyError: | |
raise errors.LabwareDefinitionDoesNotExistError( | |
f"Labware definition for matching {uri} not found." | |
) |
for well_name in col: | ||
match = pattern.match(well_name) | ||
assert match, f"Well name did not match pattern {pattern}" | ||
wells_by_rows[match.group(1)].append(well_name) |
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.
So, I couldn't find a way to avoid doing pattern matching for rows because we refer to rows with row letters. So, in a situation where wells.ordering
deviates from our expected naming pattern of letter+number, e.g: [['ab', 'cd'], ['wx', 'yz']]
, the columns would be parsed easily with a {'1': ['ab', 'cd'], '2': ['wx', 'yz']}
, but for rows, a {'A': ['ab', 'wx'], 'B': ['cd', 'yz']}
wouldn't make sense, and, doing something like {1: ['ab', 'wx'], 2: ['cd', 'yz']}
would totally alter the behavior of rows_by_name()
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.
So, to me, it seems like we need to stick to the r'^([A-Z]+)([0-9]+)$'
pattern and expect the well definition to have wells named according to that pattern.
In which case, it feels a bit weird to have different ways of parsing rows and columns but I can get used to it, esp since column parsing like this (using well.ordering
instead of pattern matching) is much easier.
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.
Could we maybe add a TODO to evaluate if there are any differences we can find between ordering
and well names in our existing definition?
Edit: I re-read this implementation more fully, and I think this is a good compromise for now
@@ -31,6 +31,9 @@ def __init__( | |||
self._engine_client = engine_client | |||
self._labware = labware | |||
self._well_name = well_name | |||
self._well_definition = self._engine_client.state.labware.get_well_definition( |
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.
Note from call:
Will be keeping this cached in __init__
as is for now but will probably need to be changed to a lazy property once we start adding more tests that access wells.
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 well_name in col: | ||
match = pattern.match(well_name) | ||
assert match, f"Well name did not match pattern {pattern}" | ||
wells_by_rows[match.group(1)].append(well_name) |
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.
Could we maybe add a TODO to evaluate if there are any differences we can find between ordering
and well names in our existing definition?
Edit: I re-read this implementation more fully, and I think this is a good compromise for now
"""Get well rows.""" | ||
definition = self.get_labware_definition(labware_id=labware_id) | ||
wells_by_rows = defaultdict(list) | ||
pattern = re.compile(WELL_NAME_PATTERN, re.X) |
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.
Should this compile
be at the module level?
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.
Since the regex's used only in this function, I like it being contained in here.
for col in definition.ordering: | ||
for well_name in col: | ||
match = pattern.match(well_name) | ||
assert match, f"Well name did not match pattern {pattern}" |
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.
Ok to leave around for now, but this assert
makes me feel a little weird
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.
Co-authored-by: Max Marrone <max@opentrons.com>
Overview
Adds well access methods to
Labware
classChangelog
wells()
,wells_by_name()
,rows()
,columns()
,rows_by_name()
,columns_by_name()
Labware
now caches the labware definition as well aswells_by_name()
,rows_by_name()
,columns_by_name()
Well
property methodsReview requests
The caching of labware definition & well properties during labware initialization
makes stubbingfeels right to me since some of the well access methods are a bit expensive (potentially more for labware with lots of wells, esp when accessed many times in a protocol). But open to other thoughts.Labware
a bit more involved but stillUpdate: After much discussion, decided caching is the right approach but caching in
__init__
or usinglru_cache
isn't because the former makes test fixtures unnecessarily involved whilelru_cache
creates problems in testing if a Labware's id is not consistent (as the method refers toself
). So, decided to make all cached methods use lazy properties which solves all testing issues.Related to above, the protocol contextNot an issue anymore because of lazy properties.load_labware
tests now fail because we don't mock outLabware
class. What's the best approach to refactoring these tests?Risk assessment
Medium impact on PE. None on non-P.E code