-
Notifications
You must be signed in to change notification settings - Fork 179
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
refactor(protocol-engine): split labware state into state/store/view classes #7873
Conversation
Codecov Report
@@ Coverage Diff @@
## edge #7873 +/- ##
==========================================
+ Coverage 83.61% 83.95% +0.33%
==========================================
Files 351 355 +4
Lines 21669 21954 +285
==========================================
+ Hits 18119 18431 +312
+ Misses 3550 3523 -27
Continue to review full report at Codecov.
|
"""Basic labware data state and getter methods.""" | ||
"""State of all loaded labware resources.""" | ||
|
||
labware_by_id: Dict[str, LabwareData] |
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'm going to type these as Mapping
instead, which is read-only at a type level. This won't give us runtime immutability of the dict, but I think the type safety is more than enough protection for now. (I'll do the same for any lists I find and set them as Sequence
's)
Edit: made this change in #7882
load_name=command.result.definition.parameters.loadName, | ||
version=command.result.definition.version, | ||
) | ||
self._state.labware_definitions_by_uri[uri] = command.result.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.
This should be treating self._state
as immutable, but it is not. Fixed in #7882
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.
Mostly clarifying questions.
The rationale described in the PR description sounds great to me. Making the state dataclass public makes me a little nervous, but I don't think that matters right now.
Haven't made my way through all the tests, yet.
# TODO(mc, 2021-06-03): continue evaluation of which selectors should go here | ||
# vs which selectors should be in LabwareView |
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.
TBH, I'd be down to stuff both labware and non-labware geometry things into GeometryView
.
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.
Or even, maybe, absolutely everything under one ProtocolEngineView
class. For example, this would disallow discourage mocking out geometry view stuff without also mocking out command view stuff, which is maybe a good thing?
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 agree with both these points.
- Moving more stuff out of GeometryView can be accomplished in another PR (it might even make
GeometryView
unnecessary) - refactor(protocol-engine): split top-level engine state into state/store/view classes #7882 adds a top-level
StateView
for external users. Mocking out subviews is useful (and necessary) for testing other subviews, but outside of ProtocolEngine everything will interact with theStateView
classs
Arguments: | ||
state: Labware state dataclass used for all calculations. | ||
""" | ||
self._state = state |
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 I'm reading this right:
LabwareState
isfrozen=True
. This meansLabwareStore
will have to represent modifications to it as a rebinding of itsself._state
attribute, rather than as an in-place modification of that object. (Your comment above says it doesn't do this now, but it will in refactor(protocol-engine): split top-level engine state into state/store/view classes #7882).- A
LabwareView
is initialized with aLabwareState
and never updated. So, aLabwareView
will only provide a view of the state snapshotted at theLabwareView
's__init__
time. The view won't automatically update.
e.g. if I wrote code like this, it would be wrong:
view = LabwareView(store.state)
with pytest.raises(LabwareDoesNotExistError):
view.get_labware_data_by_id("some-labware-id") # Nothing loaded yet.
store.handle_completed_command(some_load_labware_command)
assert view.get_labware_data_by_id("some-labware-id") == some_labware_data # Now it's loaded.
It would instead have to be:
with pytest.raises(LabwareDoesNotExistError):
LabwareView(store.state).get_labware_data_by_id("some-labware-id") # Nothing loaded yet.
store.handle_completed_command(some_load_labware_command)
assert LabwareView(store.state).get_labware_data_by_id("some-labware-id") == some_labware_data # Now it's loaded.
Is this intentional?
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.
Forgetting to update LabwareStore
to rebind self._state
in this PR was not intentional, and pushing it to #7882 was out of laziness and not wanting to do a whole rebase chain.
The LabwareState > LabwareView
thing is intentional, though! In #7882, where all substores are converted, the top-level StateStore
has the responsibility of updating the view whenever the underlying state dataclass needs to change:
opentrons/api/src/opentrons/protocol_engine/state/state.py
Lines 74 to 81 in 272fb9d
def _update_state(self) -> None: | |
"""Set state data and view interface to latest underlying values.""" | |
self._state = State( | |
commands=self._command_store.state, | |
labware=self._labware_store.state, | |
pipettes=self._pipette_store.state, | |
) | |
self._state_view = StateView(state=self._state) |
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.
Scrolling through the tests, it looks like they're all ports from the old class layout to the new one—i.e. we're not doing anything like adding new tests in this PR. If this is wrong, I might need a pointer to which tests I should focus on.
Otherwise LGTM. 👍
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.
Reviewed in person
👍
Overview
This PR is part of some engine simplification work in advance of #7871. In order to make using, testing, and building on the ProtocolEngine state all around better, I'd like to shift things to the following structure moving forward:
The big changes from the previous system are:
This PR implements this change only for the LabwareStore, while leaving everything else alone.
Why?
GeometryState
andMotionState
are currently pulling double duty: they track state and they compute state from other storesGeometryView
but noGeometryState
norGeometryStore
; ditto for motionChanges
LabwareState
intoLabwareState
dataclass andLabwareView
state selector interfacedeck_definition
state fromGeometryStore
toLabwareStore
GeometryState
toLabwareView
(could move more, but decided to punt)Review requests
Risk assessment
Very low. Might break some protocol engine stuff, but it's not being used yet so whatever