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

refactor(api): Split UpdateCommandAction #14726

Merged
merged 24 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3d57263
Split UpdateCommandAction into RunCommandAction and SucceedCommandAct…
SyntaxColoring Mar 20, 2024
43f50d3
Update CommandStore.
SyntaxColoring Mar 20, 2024
fad2416
Trivially update remaining state stores.
SyntaxColoring Mar 20, 2024
d186f1f
Update legacy equipment loads.
SyntaxColoring Mar 22, 2024
c755485
WIP: CommandExecutor.
SyntaxColoring Mar 20, 2024
4127d7e
WIP: legacy
SyntaxColoring Mar 22, 2024
6df0bcc
WIP: Legacy
SyntaxColoring Mar 22, 2024
f02ec73
Dispatch actions atomically from LegacyContextPlugin.
SyntaxColoring Mar 22, 2024
815297e
WIP: legacy tests
SyntaxColoring Mar 22, 2024
10b3372
Fix race condition in legacy Python protocols' initial home command.
SyntaxColoring Mar 22, 2024
b5c54af
Leave a todo for removing the old ThreadAsyncQueue.
SyntaxColoring Mar 22, 2024
cfa9053
Less ambiguous docstrings.
SyntaxColoring Mar 26, 2024
e0de9dd
Let FailCommandAction set command notes.
SyntaxColoring Mar 26, 2024
dd05fd8
Import fixup.
SyntaxColoring Mar 26, 2024
098fc2e
Update various things to supply notes.
SyntaxColoring Mar 26, 2024
e012b78
Leave todo comment for maintenance_run arg.
SyntaxColoring Mar 26, 2024
308c5bc
Forgot my keys.
SyntaxColoring Mar 26, 2024
f173f7b
Set notes=[] for succeeded commands in LegacyCommandMapper.
SyntaxColoring Mar 26, 2024
cb3d655
Update integration tests to expect `notes: []` on completed commands.
SyntaxColoring Mar 26, 2024
7c48aca
I'm sorry, Mike.
SyntaxColoring Mar 26, 2024
32543f8
Update test_legacy_context_plugin.py.
SyntaxColoring Mar 26, 2024
ec58e46
Update legacy command mapper unit tests.
SyntaxColoring Mar 26, 2024
8c6f65d
Lint.
SyntaxColoring Mar 27, 2024
33e39e0
Flow variables from queued->running->completed.
SyntaxColoring Mar 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions api/src/opentrons/protocol_engine/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
FinishAction,
HardwareStoppedAction,
QueueCommandAction,
UpdateCommandAction,
RunCommandAction,
SucceedCommandAction,
FailCommandAction,
AddLabwareOffsetAction,
AddLabwareDefinitionAction,
Expand All @@ -40,7 +41,8 @@
"FinishAction",
"HardwareStoppedAction",
"QueueCommandAction",
"UpdateCommandAction",
"RunCommandAction",
"SucceedCommandAction",
"FailCommandAction",
"AddLabwareOffsetAction",
"AddLabwareDefinitionAction",
Expand Down
28 changes: 24 additions & 4 deletions api/src/opentrons/protocol_engine/actions/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,35 @@ class QueueCommandAction:


@dataclass(frozen=True)
class UpdateCommandAction:
"""Update a given command."""
class RunCommandAction:
"""Mark a given command as running.

The command must be queued immediately prior to this action.
"""

command_id: str
started_at: datetime


@dataclass(frozen=True)
class SucceedCommandAction:
"""Mark a given command as succeeded.

The command must be running immediately prior to this action.
"""

command: Command
Copy link
Member

Choose a reason for hiding this comment

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

when UpdateCommandAction was the everything-doer, having the command embedded made sense, but now having it in SucceedCommandAction looks a little weirder amongst all its siblings that only reference the id. Does it maybe make sense to have SucceedCommandAction carry notes: List[CommandNote], result: CommandResult, private_result: CommandPrivateResult independently?

Copy link
Contributor

@TamarZanzouri TamarZanzouri Mar 27, 2024

Choose a reason for hiding this comment

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

I agree with @sfoster1 we might want to add these to the SucceedCommandAction. I agree UpdateCommandAction had too many responsibilities

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that makes sense.

I’m pretty sure some of the states stores are reading command.params, too. So to replace that, we’d either need to add params to CompleteCommandAction, or give every state store access to the CommandView so they can retrieve the params themselves.

"""The command in its new succeeded state."""

private_result: CommandPrivateResult


@dataclass(frozen=True)
class FailCommandAction:
"""Mark a given command as failed."""
"""Mark a given command as failed.

The command must be running immediately prior to this action.
"""

command_id: str
Copy link
Member

Choose a reason for hiding this comment

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

if we don't want to make SucceedCommandAction carry everything independently, maybe we should make FailCommandAction embed the command too. Both of these actions are going to cause pretty equivalent modifications to the command dataclass anyway.

error_id: str
Expand Down Expand Up @@ -211,7 +230,8 @@ class SetPipetteMovementSpeedAction:
HardwareStoppedAction,
DoorChangeAction,
QueueCommandAction,
UpdateCommandAction,
RunCommandAction,
SucceedCommandAction,
FailCommandAction,
AddLabwareOffsetAction,
AddLabwareDefinitionAction,
Expand Down
26 changes: 17 additions & 9 deletions api/src/opentrons/protocol_engine/execution/command_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
CommandPrivateResult,
Command,
)
from ..actions import ActionDispatcher, UpdateCommandAction, FailCommandAction
from ..actions import (
ActionDispatcher,
RunCommandAction,
SucceedCommandAction,
FailCommandAction,
)
from ..errors import RunStoppedError
from ..errors.exceptions import EStopActivatedError as PE_EStopActivatedError
from ..notes import CommandNote, CommandNoteTracker
Expand Down Expand Up @@ -140,7 +145,7 @@ async def execute(self, command_id: str) -> None:
)

self._action_dispatcher.dispatch(
UpdateCommandAction(command=running_command, private_result=None)
RunCommandAction(command_id=command.id, started_at=started_at)
)

try:
Expand Down Expand Up @@ -168,12 +173,15 @@ async def execute(self, command_id: str) -> None:
)

if notes_update:
command_with_new_notes = running_command.copy(update=notes_update)
self._action_dispatcher.dispatch(
UpdateCommandAction(
command=command_with_new_notes, private_result=None
)
)
# FIX BEFORE MERGE
pass
# command_with_new_notes = running_command.copy(update=notes_update)
# self._action_dispatcher.dispatch(
# # FIX BEFORE MERGE
# SucceedCommandAction(
# command=command_with_new_notes, private_result=None
# )
# )

self._action_dispatcher.dispatch(
FailCommandAction(
Expand Down Expand Up @@ -203,7 +211,7 @@ async def execute(self, command_id: str) -> None:
}
completed_command = running_command.copy(update=update)
self._action_dispatcher.dispatch(
UpdateCommandAction(
SucceedCommandAction(
command=completed_command, private_result=private_result
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
DeckConfigurationType,
Dimensions,
)
from ..actions import Action, UpdateCommandAction, PlayAction, AddAddressableAreaAction
from ..actions import Action, SucceedCommandAction, PlayAction, AddAddressableAreaAction
from .config import Config
from .abstract_store import HasState, HandlesActions

Expand Down Expand Up @@ -182,7 +182,7 @@ def __init__(

def handle_action(self, action: Action) -> None:
"""Modify state in reaction to an action."""
if isinstance(action, UpdateCommandAction):
if isinstance(action, SucceedCommandAction):
self._handle_command(action.command)
elif isinstance(action, AddAddressableAreaAction):
self._check_location_is_addressable_area(action.addressable_area)
Expand Down
73 changes: 45 additions & 28 deletions api/src/opentrons/protocol_engine/state/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
from opentrons.ordered_set import OrderedSet

from opentrons.hardware_control.types import DoorState
from opentrons.protocol_engine.actions.actions import ResumeFromRecoveryAction
from opentrons.protocol_engine.actions.actions import (
ResumeFromRecoveryAction,
RunCommandAction,
)
from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType

from ..actions import (
Action,
QueueCommandAction,
UpdateCommandAction,
SucceedCommandAction,
FailCommandAction,
PlayAction,
PauseAction,
Expand Down Expand Up @@ -261,41 +264,55 @@ def handle_action(self, action: Action) -> None: # noqa: C901
if action.request_hash is not None:
self._state.latest_command_hash = action.request_hash

# TODO(mc, 2021-12-28): replace "UpdateCommandAction" with explicit
# state change actions (e.g. RunCommandAction, SucceedCommandAction)
# to make a command's queue transition logic easier to follow
elif isinstance(action, UpdateCommandAction):
command = action.command
prev_entry = self._state.commands_by_id.get(command.id)

if prev_entry is None:
index = len(self._state.all_command_ids)
self._state.all_command_ids.append(command.id)
self._state.commands_by_id[command.id] = CommandEntry(
index=index,
command=command,
)
else:
self._state.commands_by_id[command.id] = CommandEntry(
index=prev_entry.index,
command=command,
)
elif isinstance(action, RunCommandAction):
prev_entry = self._state.commands_by_id[action.command_id]
assert prev_entry.command.status == CommandStatus.QUEUED

self._state.queued_command_ids.discard(command.id)
self._state.queued_setup_command_ids.discard(command.id)
running_command = prev_entry.command.copy(
update={
"status": CommandStatus.RUNNING,
"startedAt": action.started_at,
}
)

if command.status == CommandStatus.RUNNING:
self._state.running_command_id = command.id
elif self._state.running_command_id == command.id:
self._state.running_command_id = None
self._state.commands_by_id[action.command_id] = CommandEntry(
index=prev_entry.index, command=running_command
)

assert self._state.running_command_id is None
self._state.running_command_id = action.command_id

self._state.queued_command_ids.discard(action.command_id)
self._state.queued_setup_command_ids.discard(action.command_id)

elif isinstance(action, SucceedCommandAction):
prev_entry = self._state.commands_by_id[action.command.id]
assert prev_entry.command.status == CommandStatus.RUNNING

succeeded_command = action.command
assert succeeded_command.status == CommandStatus.SUCCEEDED

self._state.commands_by_id[action.command.id] = CommandEntry(
index=prev_entry.index,
command=succeeded_command,
)

assert self._state.running_command_id == action.command.id
self._state.running_command_id = None

self._state.queued_command_ids.discard(succeeded_command.id)
self._state.queued_setup_command_ids.discard(succeeded_command.id)

elif isinstance(action, FailCommandAction):
prev_entry = self._state.commands_by_id[action.command_id]
assert prev_entry.command.status == CommandStatus.RUNNING

error_occurrence = ErrorOccurrence.from_failed(
id=action.error_id,
createdAt=action.failed_at,
error=action.error,
)
prev_entry = self._state.commands_by_id[action.command_id]

# TODO(mc, 2022-06-06): add new "cancelled" status or similar
self._update_to_failed(
command_id=action.command_id,
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_engine/state/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
)
from ..actions import (
Action,
UpdateCommandAction,
SucceedCommandAction,
AddLabwareOffsetAction,
AddLabwareDefinitionAction,
)
Expand Down Expand Up @@ -152,7 +152,7 @@ def __init__(

def handle_action(self, action: Action) -> None:
"""Modify state in reaction to an action."""
if isinstance(action, UpdateCommandAction):
if isinstance(action, SucceedCommandAction):
self._handle_command(action.command)

elif isinstance(action, AddLabwareOffsetAction):
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_engine/state/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
temperature_module,
thermocycler,
)
from ..actions import Action, UpdateCommandAction, AddModuleAction
from ..actions import Action, SucceedCommandAction, AddModuleAction
from .abstract_store import HasState, HandlesActions
from .module_substates import (
MagneticModuleSubState,
Expand Down Expand Up @@ -195,7 +195,7 @@ def __init__(

def handle_action(self, action: Action) -> None:
"""Modify state in reaction to an action."""
if isinstance(action, UpdateCommandAction):
if isinstance(action, SucceedCommandAction):
self._handle_command(action.command)

elif isinstance(action, AddModuleAction):
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_engine/state/pipettes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from ..actions import (
Action,
SetPipetteMovementSpeedAction,
UpdateCommandAction,
SucceedCommandAction,
)
from .abstract_store import HasState, HandlesActions

Expand Down Expand Up @@ -150,7 +150,7 @@ def __init__(self) -> None:

def handle_action(self, action: Action) -> None:
"""Modify state in reaction to an action."""
if isinstance(action, UpdateCommandAction):
if isinstance(action, SucceedCommandAction):
self._handle_command(action.command, action.private_result)
elif isinstance(action, SetPipetteMovementSpeedAction):
self._state.movement_speed_by_id[action.pipette_id] = action.speed
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_engine/state/tips.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .abstract_store import HasState, HandlesActions
from ..actions import (
Action,
UpdateCommandAction,
SucceedCommandAction,
ResetTipsAction,
)
from ..commands import (
Expand Down Expand Up @@ -64,7 +64,7 @@ def __init__(self) -> None:

def handle_action(self, action: Action) -> None:
"""Modify state in reaction to an action."""
if isinstance(action, UpdateCommandAction):
if isinstance(action, SucceedCommandAction):
if isinstance(action.private_result, PipetteConfigUpdateResultMixin):
pipette_id = action.private_result.pipette_id
config = action.private_result.config
Expand Down
Loading
Loading