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(protocol-engine): Implement CommandState.get_next_request() #7936

Merged
merged 14 commits into from
Jun 15, 2021

Conversation

SyntaxColoring
Copy link
Contributor

@SyntaxColoring SyntaxColoring commented Jun 11, 2021

Overview

Document, test, and implement opentrons.protocol_engine.state.commands.get_next_request(), which was formerly a NotImplementedError, in service towards #7808.

Review requests

Mostly on testing.

Risk assessment

None. Not production code, and this wasn't implemented before.

@codecov
Copy link

codecov bot commented Jun 11, 2021

Codecov Report

Merging #7936 (8dfdebf) into edge (8bdd223) will increase coverage by 0.14%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##             edge    #7936      +/-   ##
==========================================
+ Coverage   83.78%   83.93%   +0.14%     
==========================================
  Files         352      355       +3     
  Lines       21627    21895     +268     
==========================================
+ Hits        18120    18377     +257     
- Misses       3507     3518      +11     
Impacted Files Coverage Δ
robot-server/robot_server/sessions/dependencies.py 55.55% <0.00%> (-8.74%) ⬇️
robot-server/robot_server/service/dependencies.py 91.57% <0.00%> (-1.15%) ⬇️
robot-server/robot_server/sessions/router.py 100.00% <0.00%> (ø)
...bot-server/robot_server/sessions/session_models.py 100.00% <0.00%> (ø)
robot-server/robot_server/sessions/engine_store.py 97.36% <0.00%> (ø)
robot-server/robot_server/sessions/session_view.py 95.65% <0.00%> (ø)
...obot-server/robot_server/sessions/action_models.py 100.00% <0.00%> (ø)
...ot-server/robot_server/protocols/protocol_store.py 94.28% <0.00%> (+0.43%) ⬆️
opentrons/protocol_engine/protocol_engine.py 98.50% <0.00%> (+1.00%) ⬆️
...obot-server/robot_server/sessions/session_store.py 89.36% <0.00%> (+10.19%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8bdd223...8dfdebf. Read the comment docs.

@SyntaxColoring SyntaxColoring added robot-svcs Falls under the purview of the Robot Services squad (formerly CPX, Core Platform Experience). protocol-engine Ticket related to the Protocol Engine project and associated HTTP APIs labels Jun 11, 2021
Copy link
Contributor

@mcous mcous left a comment

Choose a reason for hiding this comment

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

Feedback on tests, mostly

created_at=now,
request=r
)
for r in _make_unique_requests(3)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this fixture function might be doing a little too much. Is this easier to read as an unrolled loop, without using actual commands?

command_a = PendingCommand(
  created_at=datetime(year=2021, month=1, day=1),
  request=BaseModel()
)
command_b = PendingCommand(
  created_at=datetime(year=2022, month=2, day=2),
  request=BaseModel()
)
command_c = PendingCommand(
  created_at=datetime(year=2023, month=3, day=3),
  request=BaseModel()
)

store.handle_command(command_a, "command-id-1")  # type: ignore[arg-type]
store.handle_command(command_b, "command-id-2")  # type: ignore[arg-type]

assert store.commands.get_all_commands() == [
  ("command-id-1", command_a),
  ("command-id-2", command_b),
]

store.handle_command(command_c, "command-id-1")  # type: ignore[arg-type]

assert store.commands.get_all_commands() == [
  ("command-id-1", command_c),
  ("command-id-2", command_b),
]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I like rolling the loop into a function because it lets us name which trait in this triplet of commands is important. For example, unrolling the loop means we have to write PendingCommand (I think?), which feels overly specific. We care that they're unique, and nothing else.

Also, at the very least, I should simplify the usage to something like:

[command_a, command_b, command_c] = _make_unique_commands(3)

Copy link
Contributor

Choose a reason for hiding this comment

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

What if you used the other value fixtures?

def test_command_state_preserves_handle_order(
    pending_command: PendingCommand,
    running_command: RunningCommand,
    completed_command: CompletedCommand,
) -> None:
    store.handle_command(running_command, "command-id-1")
    store.handle_command(pending_command, "command-id-2")

    assert store.commands.get_all_commands() == [
        ("command-id-1", running_command),
        ("command-id-2", pending_command),
    ]

    store.handle_command(completed_command, "command-id-1")

    assert store.commands.get_all_commands() == [
        ("command-id-1", completed_command),
        ("command-id-2", pending_command),
    ]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely better, but ultimately I think I have the same objection. pending_command, running_command, and completed_command seem lower than the most meaningful level of abstraction for this particular test.

For now, I'm moving forward with your suggestion, plus a comment pointing out that the commands are just an arbitrary triplet that compares != to each other.

@@ -27,3 +54,110 @@ def test_state_store_handles_command(store: StateStore, now: datetime) -> None:
store.handle_command(cmd, command_id="unique-id")

assert store.commands.get_command_by_id("unique-id") == cmd


def test_command_state_preserves_handle_order( # noqa:D103
Copy link
Contributor Author

@SyntaxColoring SyntaxColoring Jun 14, 2021

Choose a reason for hiding this comment

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

I'm omitting docstrings and #noqa:D103ing these test functions because the function names are what show up in the PyTest summary, so it seems like I should invest all my descriptive energy into making those clear and readable.

If we think this is weird and inconsistent, I'm happy to add docstrings now and reserve this discussion for another day.

Copy link
Contributor

Choose a reason for hiding this comment

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

I like the docstrings because I much prefer BDD-style describe and it API of mocha (also jasmine, rspec, etc.) as opposed to the flatter test_xyz style pytest uses. I've been using the docstrings as a way to write the it("should behave this way", () => { message that my brain craves

Copy link
Contributor Author

@SyntaxColoring SyntaxColoring Jun 14, 2021

Choose a reason for hiding this comment

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

Yeah, I like that too.

When I try that, I feel like I frequently collide with the unfortunate combination of:

  • The first docstring sentence must fit on a single line.
  • A single line must be less than 80 (I think?) characters.

Like, I want the "it should..." sentence to be an elaboration of whatever the function name test_command_state_preserves_handle_order means...but there's not enough room for that, so it becomes a restatement instead.

Maybe I need to bite the verbosity bullet and do a cascade like:

def test_command_state_preserves_handle_order():
    """It should preserve handle order.                                     <-- [This bit bugs me.]

    It should return commands in the order they were initially added.
    Replacing the command at an existing ID should keep the original
    command's place in the ordering.
    """

Copy link
Contributor

Choose a reason for hiding this comment

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

With that I think the first sentence of your description is better, and this would be enough for me:

def test_command_state_preserves_order() -> None:
   """It should return commands in the order they are added."""
    # ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving forward with docstrings similar to that latest suggestion. Though it still feels a little unsatisfying to me, since it's not "semantically different" enough from the function name, if that makes sense.

@SyntaxColoring SyntaxColoring marked this pull request as ready for review June 14, 2021 20:14
@SyntaxColoring SyntaxColoring requested a review from a team as a code owner June 14, 2021 20:14
@SyntaxColoring SyntaxColoring requested a review from sanni-t June 14, 2021 20:18
@SyntaxColoring SyntaxColoring merged commit 61136a1 into edge Jun 15, 2021
@SyntaxColoring SyntaxColoring deleted the next_command branch June 15, 2021 14:25
Copy link
Member

@sanni-t sanni-t left a comment

Choose a reason for hiding this comment

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

👀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
protocol-engine Ticket related to the Protocol Engine project and associated HTTP APIs robot-svcs Falls under the purview of the Robot Services squad (formerly CPX, Core Platform Experience).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants