Skip to content
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): command executors can add notes #14652

Merged
merged 6 commits into from
Mar 14, 2024

Conversation

sfoster1
Copy link
Member

@sfoster1 sfoster1 commented Mar 13, 2024

This PR adds an interface for command executors to add notes to the command that they're executing, succeed or fail, and uses that interface to implement an example note warning of aspirate volume rounding.

The interface is done by callbacks from the command implementations to the command executor that is executing each command. Implementations, or the functions they call, are responsible for creating the CommandNote instances. The executor just gathers a list of commands and then issues an UpdateCommandAction with the new list. If there are already notes on the command before execution, the new ones are appended. While this doesn't seem necessary right now, we'll want this behavior generically when we have notes also coming from hardware.

One thing that's a little annoying about this interface is that the callbacks have to be threaded through all the way to whatever wants to add a note. For instance, in the example aspirate-rounded note, we have to pass the callback all the way into the pipetting executor. An alternative would be to have an interface that can add notes on one of the state instances that can be called from anything that can access the state instance. The thing is, that function won't have the context to know when commands are currently executing or are done executing without having some sort of state machine that only looks at UpdateCommandActions from elsewhere. That code would be pretty ugly. The alternative there would be to issue a new UpdateCommandAction for each note; this would have race condition issues if it provided new values for the notes list. The alternative there would be a new action called like AddCommandNoteAction that carries the attention of adding to the notes list along with it. Of course, that command is not idempotent.

This interface works well enough for now that I think we should roll with it, and feel free to change it later.

Still todo

  • add tests for actually submitting a new command note

testing (once todos are todone)

  • in simulation, request an aspirate that is 0.0000001 ul higher than the pipette max and see the note in the output
  • on an executing flex, do the same
    - [ ] on an executing ot-2, do the same will test this in followups

Closes EXEC-291

This PR adds an interface for command executors to add notes to the
command that they're executing, succeed or fail, and uses that interface
to implement an example note warning of aspirate volume rounding.

The interface is done by callbacks from the command implementations to
the command executor that is executing each command. Implementations, or
the functions they call, are responsible for creating the CommandNote
instances. The executor just gathers a list of commands and then issues
an UpdateCommandAction with the new list. If there are already notes on
the command before execution, the new ones are appended. While this
doesn't seem necessary right now, we'll want this behavior generically
when we have notes also coming from hardware.

One thing that's a little annoying about this interface is that the
callbacks have to be threaded through all the way to whatever wants to
add a note. For instance, in the example aspirate-rounded note, we have
to pass the callback all the way into the pipetting executor. An
alternative would be to have an interface that can add notes on one of
the state instances that can be called from anything that can access the
state instance. The thing is, that function won't have the context to
know when commands are currently executing or are done executing without
having some sort of state machine that only looks at
UpdateCommandActions from elsewhere. That code would be pretty ugly. The
alternative there would be to issue a new UpdateCommandAction for each
note; this would have race condition issues if it provided new values
for the notes list. The alternative _there_ would be a new action called
like AddCommandNoteAction that carries the attention of adding to the
notes list along with it. Of course, that command is not idempotent.

This interface works well enough for now that I think we should roll
with it, and feel free to change it later.

Closes EXEC-291
@sfoster1 sfoster1 requested a review from a team March 13, 2024 18:30
@sfoster1 sfoster1 requested a review from a team as a code owner March 13, 2024 18:30
@sfoster1 sfoster1 marked this pull request as draft March 13, 2024 18:32
Copy link

codecov bot commented Mar 13, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 67.29%. Comparing base (051b50d) to head (db728bf).
Report is 20 commits behind head on edge.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             edge   #14652   +/-   ##
=======================================
  Coverage   67.29%   67.29%           
=======================================
  Files        2485     2485           
  Lines       71496    71496           
  Branches     9085     9085           
=======================================
  Hits        48116    48116           
  Misses      21224    21224           
  Partials     2156     2156           
Flag Coverage Δ
g-code-testing 92.43% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
api/src/opentrons/protocol_engine/__init__.py 100.00% <ø> (ø)
...src/opentrons/protocol_engine/commands/__init__.py 100.00% <ø> (ø)
...src/opentrons/protocol_engine/commands/aspirate.py 100.00% <ø> (ø)
.../src/opentrons/protocol_engine/commands/command.py 94.11% <ø> (ø)
...rons/protocol_engine/execution/command_executor.py 100.00% <ø> (ø)
...c/opentrons/protocol_engine/execution/pipetting.py 100.00% <ø> (ø)

@SyntaxColoring
Copy link
Contributor

Yeah, this fundamental interface looks good to me.

I agree that we don't want command executors to add notes by emitting UpdateCommandActions or AddCommandNoteActions. In addition to the reasons you mentioned, it's nice if we can continue to treat a command's execution as one atomic step that's committed with one UpdateCommandAction.

Re the annoyance of having to pass the callback all the way into the pipetting executor: it does seem doable to me to have something "globally" accessible that knows what the current note target is. It can delegate to CommandView.get_current(); it doesn't have to be a whole new independent state machine that monitors UpdateCommandActions from scratch. We could even wire that up to the standard Python warnings system, maybe.

I agree that this interface is good enough that we should roll with it.

Copy link
Contributor

@DerekMaggio DerekMaggio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface looks good to me.
I do like Max's idea of having something global so you don't have to pass the callback so much.

@sfoster1 sfoster1 marked this pull request as ready for review March 14, 2024 14:01
@sfoster1
Copy link
Member Author

Yeah I honestly don't love the idea of a global callback like that because it gets pretty nonlocal

@TamarZanzouri
Copy link
Contributor

TamarZanzouri commented Mar 14, 2024

Spoke privately with Seth about this, I kind of prefer we use AddNoteAction instead of UpdateCommandAction. I feel like there is too much conditional logic for UpdateCommandAction and we know exactly what needs to be done when we dispatch a more specific action. UpdateCommandAction feels like its more in charge of the command status and timestamps.

Copy link
Contributor

@TamarZanzouri TamarZanzouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke again to Seth about this, might make more sense to use UpdateCommandAction to append all notes at one time. This logic makes sense to me.

Copy link
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks.

One thing in the tests that might matter, and various small suggestions.

Comment on lines +41 to +63
class CommandNoteTrackerProvider(Protocol):
"""The correct shape for a function that provides a CommandNoteTracker.

This function will be called by the executor once for each call to execute().
It is mostly useful for testing harnesses.
"""

def __call__(self) -> CommandNoteTracker:
"""Provide a new CommandNoteTracker."""
...


class _NoteTracker(CommandNoteTracker):
def __init__(self) -> None:
self._notes: List[CommandNote] = []

def __call__(self, note: CommandNote) -> None:
self._notes.append(note)

def get_notes(self) -> List[CommandNote]:
return self._notes


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity, I'd be down for CommandExecutor to just construct each CommandNoteTracker directly, in a way that can't be overridden by CommandExecutor's unit tests.

The downside is we couldn't test that it instantiates a separate tracker for each command, and I'm fine with that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that's a good idea, will reduce the diff

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SyntaxColoring mind if we put this off actually? the way those tests are currently implemented, we'd have to change a bunch of stuff to get access to the test command constructor arguments

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, NP.

Copy link
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM if tests and linting are passing.

@sfoster1 sfoster1 merged commit 2ac7dcb into edge Mar 14, 2024
22 checks passed
@sfoster1 sfoster1 deleted the exec-291-executors-can-add-notes branch March 14, 2024 19:41
Carlos-fernandez pushed a commit that referenced this pull request May 20, 2024
This PR adds an interface for command executors to add notes to the
command that they're executing, succeed or fail, and uses that interface
to implement an example note warning of aspirate volume rounding.

The interface is done by callbacks from the command implementations to
the command executor that is executing each command. Implementations, or
the functions they call, are responsible for creating the CommandNote
instances. The executor just gathers a list of commands and then issues
an UpdateCommandAction with the new list. If there are already notes on
the command before execution, the new ones are appended. While this
doesn't seem necessary right now, we'll want this behavior generically
when we have notes also coming from hardware.

One thing that's a little annoying about this interface is that the
callbacks have to be threaded through all the way to whatever wants to
add a note. For instance, in the example aspirate-rounded note, we have
to pass the callback all the way into the pipetting executor. An
alternative would be to have an interface that can add notes on one of
the state instances that can be called from anything that can access the
state instance. The thing is, that function won't have the context to
know when commands are currently executing or are done executing without
having some sort of state machine that only looks at
UpdateCommandActions from elsewhere. That code would be pretty ugly. The
alternative there would be to issue a new UpdateCommandAction for each
note; this would have race condition issues if it provided new values
for the notes list. The alternative _there_ would be a new action called
like AddCommandNoteAction that carries the attention of adding to the
notes list along with it. Of course, that command is not idempotent.

This interface works well enough for now that I think we should roll
with it, and feel free to change it later.

Closes EXEC-291

---------

Co-authored-by: Max Marrone <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants