opentrons 8.7.0a2__py3-none-any.whl → 8.7.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/_version.py +2 -2
- opentrons/drivers/thermocycler/abstract.py +0 -1
- opentrons/drivers/thermocycler/driver.py +4 -33
- opentrons/drivers/thermocycler/simulator.py +0 -2
- opentrons/hardware_control/api.py +5 -24
- opentrons/hardware_control/backends/controller.py +2 -8
- opentrons/hardware_control/backends/ot3controller.py +0 -3
- opentrons/hardware_control/backends/ot3simulator.py +1 -2
- opentrons/hardware_control/backends/simulator.py +1 -2
- opentrons/hardware_control/backends/subsystem_manager.py +2 -5
- opentrons/hardware_control/module_control.py +8 -82
- opentrons/hardware_control/modules/__init__.py +0 -3
- opentrons/hardware_control/modules/absorbance_reader.py +4 -11
- opentrons/hardware_control/modules/flex_stacker.py +9 -38
- opentrons/hardware_control/modules/heater_shaker.py +5 -30
- opentrons/hardware_control/modules/magdeck.py +4 -8
- opentrons/hardware_control/modules/mod_abc.py +5 -13
- opentrons/hardware_control/modules/tempdeck.py +5 -25
- opentrons/hardware_control/modules/thermocycler.py +10 -56
- opentrons/hardware_control/modules/types.py +1 -20
- opentrons/hardware_control/modules/utils.py +4 -11
- opentrons/hardware_control/nozzle_manager.py +0 -3
- opentrons/hardware_control/ot3api.py +5 -26
- opentrons/hardware_control/scripts/update_module_fw.py +0 -5
- opentrons/hardware_control/types.py +2 -31
- opentrons/legacy_commands/protocol_commands.py +0 -20
- opentrons/legacy_commands/types.py +0 -42
- opentrons/motion_planning/waypoints.py +29 -15
- opentrons/protocol_api/__init__.py +0 -5
- opentrons/protocol_api/_types.py +1 -6
- opentrons/protocol_api/core/common.py +1 -3
- opentrons/protocol_api/core/engine/_default_labware_versions.py +11 -32
- opentrons/protocol_api/core/engine/labware.py +1 -8
- opentrons/protocol_api/core/engine/module_core.py +0 -4
- opentrons/protocol_api/core/engine/protocol.py +43 -23
- opentrons/protocol_api/core/legacy/legacy_module_core.py +0 -2
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -11
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +2 -14
- opentrons/protocol_api/core/module.py +0 -1
- opentrons/protocol_api/core/protocol.py +2 -11
- opentrons/protocol_api/module_contexts.py +0 -1
- opentrons/protocol_api/protocol_context.py +4 -26
- opentrons/protocol_api/robot_context.py +21 -38
- opentrons/protocol_api/validation.py +1 -6
- opentrons/protocol_engine/actions/__init__.py +2 -4
- opentrons/protocol_engine/actions/actions.py +9 -22
- opentrons/protocol_engine/clients/sync_client.py +7 -6
- opentrons/protocol_engine/commands/__init__.py +0 -42
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +15 -2
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +15 -2
- opentrons/protocol_engine/commands/aspirate.py +0 -1
- opentrons/protocol_engine/commands/command.py +0 -1
- opentrons/protocol_engine/commands/command_unions.py +0 -39
- opentrons/protocol_engine/commands/dispense.py +0 -1
- opentrons/protocol_engine/commands/drop_tip.py +8 -32
- opentrons/protocol_engine/commands/movement_common.py +0 -2
- opentrons/protocol_engine/commands/pick_up_tip.py +11 -21
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +0 -6
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +0 -8
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +1 -17
- opentrons/protocol_engine/commands/touch_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +22 -6
- opentrons/protocol_engine/errors/__init__.py +0 -4
- opentrons/protocol_engine/errors/exceptions.py +0 -55
- opentrons/protocol_engine/execution/__init__.py +0 -2
- opentrons/protocol_engine/execution/command_executor.py +0 -8
- opentrons/protocol_engine/execution/create_queue_worker.py +1 -5
- opentrons/protocol_engine/execution/labware_movement.py +12 -9
- opentrons/protocol_engine/execution/movement.py +0 -2
- opentrons/protocol_engine/execution/queue_worker.py +0 -4
- opentrons/protocol_engine/execution/run_control.py +0 -8
- opentrons/protocol_engine/protocol_engine.py +33 -67
- opentrons/protocol_engine/resources/__init__.py +0 -2
- opentrons/protocol_engine/resources/deck_configuration_provider.py +0 -7
- opentrons/protocol_engine/resources/labware_validation.py +6 -10
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_well_math.py +18 -60
- opentrons/protocol_engine/state/addressable_areas.py +0 -2
- opentrons/protocol_engine/state/commands.py +7 -7
- opentrons/protocol_engine/state/geometry.py +374 -204
- opentrons/protocol_engine/state/labware.py +102 -52
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +0 -37
- opentrons/protocol_engine/state/modules.py +8 -21
- opentrons/protocol_engine/state/motion.py +0 -44
- opentrons/protocol_engine/state/state.py +0 -14
- opentrons/protocol_engine/state/state_summary.py +0 -2
- opentrons/protocol_engine/state/tips.py +258 -177
- opentrons/protocol_engine/state/update_types.py +9 -16
- opentrons/protocol_engine/types/__init__.py +3 -9
- opentrons/protocol_engine/types/deck_configuration.py +1 -5
- opentrons/protocol_engine/types/instrument.py +1 -8
- opentrons/protocol_engine/types/labware.py +13 -1
- opentrons/protocol_engine/types/module.py +0 -10
- opentrons/protocol_engine/types/tip.py +0 -9
- opentrons/protocol_runner/create_simulating_orchestrator.py +2 -29
- opentrons/protocol_runner/run_orchestrator.py +2 -18
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +1 -2
- opentrons/simulate.py +15 -48
- opentrons/system/camera.py +1 -1
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a4.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a4.dist-info}/RECORD +105 -118
- opentrons/protocol_api/core/engine/tasks.py +0 -35
- opentrons/protocol_api/core/legacy/tasks.py +0 -19
- opentrons/protocol_api/core/legacy_simulator/tasks.py +0 -19
- opentrons/protocol_api/core/tasks.py +0 -31
- opentrons/protocol_api/tasks.py +0 -48
- opentrons/protocol_engine/commands/create_timer.py +0 -83
- opentrons/protocol_engine/commands/set_tip_state.py +0 -97
- opentrons/protocol_engine/commands/wait_for_tasks.py +0 -98
- opentrons/protocol_engine/execution/task_handler.py +0 -157
- opentrons/protocol_engine/resources/concurrency_provider.py +0 -27
- opentrons/protocol_engine/state/labware_origin_math/errors.py +0 -94
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +0 -1331
- opentrons/protocol_engine/state/tasks.py +0 -139
- opentrons/protocol_engine/types/tasks.py +0 -38
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a4.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a4.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from abc import abstractmethod, ABC
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from typing import TypeVar
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class AbstractTaskCore(ABC):
|
|
7
|
-
@abstractmethod
|
|
8
|
-
def get_created_at_timestamp(self) -> datetime:
|
|
9
|
-
"""Get the createdAt timestamp of the task."""
|
|
10
|
-
...
|
|
11
|
-
|
|
12
|
-
@abstractmethod
|
|
13
|
-
def is_done(self) -> bool:
|
|
14
|
-
"""Returns ``True`` if the task is done."""
|
|
15
|
-
...
|
|
16
|
-
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def is_started(self) -> bool:
|
|
19
|
-
"""Returns ``True`` if the task has started."""
|
|
20
|
-
...
|
|
21
|
-
|
|
22
|
-
@abstractmethod
|
|
23
|
-
def get_finished_at_timestamp(self) -> datetime | None:
|
|
24
|
-
"""The timestamp of the when the task finished.
|
|
25
|
-
|
|
26
|
-
Returns ``None`` if the task hasn't finished yet.
|
|
27
|
-
"""
|
|
28
|
-
...
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
TaskCoreType = TypeVar("TaskCoreType", bound=AbstractTaskCore)
|
opentrons/protocol_api/tasks.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""Data for concurrent protocol tasks."""
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from opentrons.protocols.api_support.util import requires_version
|
|
5
|
-
from opentrons.protocols.api_support.types import APIVersion
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from .core.common import TaskCore
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Task:
|
|
12
|
-
"""A concurrent protocol task created by a protocol API function.
|
|
13
|
-
|
|
14
|
-
.. versionadded:: 2.27
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(self, core: "TaskCore", api_version: APIVersion) -> None:
|
|
18
|
-
"""Initialize a Task."""
|
|
19
|
-
self._core = core
|
|
20
|
-
self._api_version = api_version
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
@requires_version(2, 27)
|
|
24
|
-
def created_at(self) -> datetime:
|
|
25
|
-
"""The timestamp of when the task was created."""
|
|
26
|
-
return self._core.get_created_at_timestamp()
|
|
27
|
-
|
|
28
|
-
@property
|
|
29
|
-
@requires_version(2, 27)
|
|
30
|
-
def done(self) -> bool:
|
|
31
|
-
"""Returns ``True`` if the task is done."""
|
|
32
|
-
return self._core.is_done()
|
|
33
|
-
|
|
34
|
-
@property
|
|
35
|
-
@requires_version(2, 27)
|
|
36
|
-
def started(self) -> bool:
|
|
37
|
-
"""Returns ``True`` if the task has started."""
|
|
38
|
-
return self._core.is_started()
|
|
39
|
-
...
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
@requires_version(2, 27)
|
|
43
|
-
def finished_at(self) -> datetime | None:
|
|
44
|
-
"""The timestamp of the when the task finished.
|
|
45
|
-
|
|
46
|
-
Returns ``None`` if the task hasn't finished yet.
|
|
47
|
-
"""
|
|
48
|
-
return self._core.get_finished_at_timestamp()
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"""CreateTimer command request, result, and implementation models."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
5
|
-
from typing import Optional, Type, TYPE_CHECKING
|
|
6
|
-
from typing_extensions import Literal
|
|
7
|
-
|
|
8
|
-
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
9
|
-
from ..errors.error_occurrence import ErrorOccurrence
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from ..execution import TaskHandler, RunControlHandler
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
CreateTimerCommandType = Literal["createTimer"]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class CreateTimerParams(BaseModel):
|
|
19
|
-
"""Payload required to annotate execution with a CreateTimer."""
|
|
20
|
-
|
|
21
|
-
time: float = Field(
|
|
22
|
-
...,
|
|
23
|
-
description="The time before the timer should elapse in seconds. This is the minimum time before the timer elapses; it may in practice take longer than this.",
|
|
24
|
-
)
|
|
25
|
-
task_id: str | None = Field(
|
|
26
|
-
None,
|
|
27
|
-
description="The id of the timer task",
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class CreateTimerResult(BaseModel):
|
|
32
|
-
"""Result data from the execution of a CreateTimer command."""
|
|
33
|
-
|
|
34
|
-
task_id: str = Field(..., description="The id of the timer task")
|
|
35
|
-
time: float = Field(..., description="The same time as the parameter.")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class CreateTimerImplementation(
|
|
39
|
-
AbstractCommandImpl[CreateTimerParams, SuccessData[CreateTimerResult]]
|
|
40
|
-
):
|
|
41
|
-
"""CreateTimer command implementation."""
|
|
42
|
-
|
|
43
|
-
def __init__(
|
|
44
|
-
self,
|
|
45
|
-
task_handler: TaskHandler,
|
|
46
|
-
run_control: RunControlHandler,
|
|
47
|
-
**kwargs: object,
|
|
48
|
-
) -> None:
|
|
49
|
-
self._task_handler = task_handler
|
|
50
|
-
self._run_control = run_control
|
|
51
|
-
|
|
52
|
-
async def execute(
|
|
53
|
-
self, params: CreateTimerParams
|
|
54
|
-
) -> SuccessData[CreateTimerResult]:
|
|
55
|
-
"""No operation taken other than capturing message in command."""
|
|
56
|
-
|
|
57
|
-
async def timer(task_handler: TaskHandler) -> None:
|
|
58
|
-
async with task_handler.synchronize_concurrent("createTimer"):
|
|
59
|
-
await self._run_control.wait_for_duration(params.time)
|
|
60
|
-
|
|
61
|
-
task = await self._task_handler.create_task(timer, params.task_id)
|
|
62
|
-
return SuccessData(
|
|
63
|
-
public=CreateTimerResult(task_id=task.id, time=params.time),
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class CreateTimer(BaseCommand[CreateTimerParams, CreateTimerResult, ErrorOccurrence]):
|
|
68
|
-
"""CreateTimer command model."""
|
|
69
|
-
|
|
70
|
-
commandType: CreateTimerCommandType = "createTimer"
|
|
71
|
-
params: CreateTimerParams
|
|
72
|
-
result: Optional[CreateTimerResult] = None
|
|
73
|
-
|
|
74
|
-
_ImplementationCls: Type[CreateTimerImplementation] = CreateTimerImplementation
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class CreateTimerCreate(BaseCommandCreate[CreateTimerParams]):
|
|
78
|
-
"""CreateTimer command request model."""
|
|
79
|
-
|
|
80
|
-
commandType: CreateTimerCommandType = "createTimer"
|
|
81
|
-
params: CreateTimerParams
|
|
82
|
-
|
|
83
|
-
_CommandCls: Type[CreateTimer] = CreateTimer
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"""Set tip state command request, result, and implementation models."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
5
|
-
from typing import TYPE_CHECKING, Optional, Type, List
|
|
6
|
-
from typing_extensions import Literal
|
|
7
|
-
|
|
8
|
-
from ..errors.error_occurrence import ErrorOccurrence
|
|
9
|
-
from ..state.update_types import StateUpdate
|
|
10
|
-
from ..types import TipRackWellState
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from .command import (
|
|
14
|
-
AbstractCommandImpl,
|
|
15
|
-
BaseCommand,
|
|
16
|
-
BaseCommandCreate,
|
|
17
|
-
SuccessData,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from ..state.state import StateView
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
SetTipStateCommandType = Literal["setTipState"]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class SetTipStateParams(BaseModel):
|
|
28
|
-
"""Payload needed to set tip wells of a tip rack to the requested state."""
|
|
29
|
-
|
|
30
|
-
labwareId: str = Field(
|
|
31
|
-
..., description="Identifier of tip rack labware to set tip wells in."
|
|
32
|
-
)
|
|
33
|
-
wellNames: List[str] = Field(
|
|
34
|
-
..., description="Names of the well to set tip well state for."
|
|
35
|
-
)
|
|
36
|
-
tipWellState: TipRackWellState = Field(
|
|
37
|
-
..., description="State to set tip wells to."
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class SetTipStateResult(BaseModel):
|
|
42
|
-
"""Result data from the execution of a setTipState command."""
|
|
43
|
-
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SetTipStateImplementation(
|
|
48
|
-
AbstractCommandImpl[SetTipStateParams, SuccessData[SetTipStateResult]]
|
|
49
|
-
):
|
|
50
|
-
"""Set tip state command implementation."""
|
|
51
|
-
|
|
52
|
-
def __init__(
|
|
53
|
-
self,
|
|
54
|
-
state_view: StateView,
|
|
55
|
-
**kwargs: object,
|
|
56
|
-
) -> None:
|
|
57
|
-
self._state_view = state_view
|
|
58
|
-
|
|
59
|
-
async def execute(
|
|
60
|
-
self, params: SetTipStateParams
|
|
61
|
-
) -> SuccessData[SetTipStateResult]:
|
|
62
|
-
"""Set the tip rack wells to the requested state."""
|
|
63
|
-
labware_id = params.labwareId
|
|
64
|
-
well_names = params.wellNames
|
|
65
|
-
|
|
66
|
-
self._state_view.labware.raise_if_not_tip_rack(labware_id=labware_id)
|
|
67
|
-
self._state_view.labware.raise_if_wells_are_invalid(
|
|
68
|
-
labware_id=labware_id, well_names=well_names
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
return SuccessData(
|
|
72
|
-
public=SetTipStateResult(),
|
|
73
|
-
state_update=StateUpdate().update_tip_rack_well_state(
|
|
74
|
-
tip_state=params.tipWellState,
|
|
75
|
-
labware_id=labware_id,
|
|
76
|
-
well_names=well_names,
|
|
77
|
-
),
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class SetTipState(BaseCommand[SetTipStateParams, SetTipStateResult, ErrorOccurrence]):
|
|
82
|
-
"""Set tip state command model."""
|
|
83
|
-
|
|
84
|
-
commandType: SetTipStateCommandType = "setTipState"
|
|
85
|
-
params: SetTipStateParams
|
|
86
|
-
result: Optional[SetTipStateResult] = None
|
|
87
|
-
|
|
88
|
-
_ImplementationCls: Type[SetTipStateImplementation] = SetTipStateImplementation
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class SetTipStateCreate(BaseCommandCreate[SetTipStateParams]):
|
|
92
|
-
"""Set tip state command creation request model."""
|
|
93
|
-
|
|
94
|
-
commandType: SetTipStateCommandType = "setTipState"
|
|
95
|
-
params: SetTipStateParams
|
|
96
|
-
|
|
97
|
-
_CommandCls: Type[SetTipState] = SetTipState
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"""WaitForTasks command request, result, and implementation models."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
from typing import Optional, Type, TYPE_CHECKING
|
|
5
|
-
from typing_extensions import Literal
|
|
6
|
-
|
|
7
|
-
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
8
|
-
from ..errors.error_occurrence import ErrorOccurrence, ProtocolCommandFailedError
|
|
9
|
-
from ..errors.exceptions import TaskFailedError
|
|
10
|
-
|
|
11
|
-
if TYPE_CHECKING:
|
|
12
|
-
from ..execution import TaskHandler, RunControlHandler
|
|
13
|
-
from ..state.state import StateView
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
WaitForTasksCommandType = Literal["waitForTasks"]
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class WaitForTasksParams(BaseModel):
|
|
20
|
-
"""Payload required to annotate execution with a WaitForTasks."""
|
|
21
|
-
|
|
22
|
-
task_ids: list[str] = Field(
|
|
23
|
-
...,
|
|
24
|
-
description="The list of task ids to wait for.",
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class WaitForTasksResult(BaseModel):
|
|
29
|
-
"""Result data from the execution of a WaitForTasks command."""
|
|
30
|
-
|
|
31
|
-
task_ids: list[str] = Field(
|
|
32
|
-
...,
|
|
33
|
-
description="The list of completed task ids.",
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class WaitForTasksImplementation(
|
|
38
|
-
AbstractCommandImpl[WaitForTasksParams, SuccessData[WaitForTasksResult]]
|
|
39
|
-
):
|
|
40
|
-
"""WaitForTasks command implementation."""
|
|
41
|
-
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
task_handler: TaskHandler,
|
|
45
|
-
run_control: RunControlHandler,
|
|
46
|
-
state_view: StateView,
|
|
47
|
-
**kwargs: object,
|
|
48
|
-
) -> None:
|
|
49
|
-
self._task_handler = task_handler
|
|
50
|
-
self._run_control = run_control
|
|
51
|
-
self._state_view = state_view
|
|
52
|
-
|
|
53
|
-
async def execute(
|
|
54
|
-
self, params: WaitForTasksParams
|
|
55
|
-
) -> SuccessData[WaitForTasksResult]:
|
|
56
|
-
"""Checks for existance of task id and then asynchronously waits for the valid, specified tasks to finish."""
|
|
57
|
-
# Raises the exception if we don't have a valid task id.
|
|
58
|
-
for task_id in params.task_ids:
|
|
59
|
-
_ = self._state_view.tasks.get(task_id)
|
|
60
|
-
|
|
61
|
-
await self._run_control.wait_for_tasks(params.task_ids)
|
|
62
|
-
|
|
63
|
-
failed_tasks = self._state_view.tasks.get_failed_tasks(params.task_ids)
|
|
64
|
-
if failed_tasks:
|
|
65
|
-
raise TaskFailedError(
|
|
66
|
-
message=f"{len(failed_tasks)} tasks failed.",
|
|
67
|
-
details={"failed_task_ids": failed_tasks},
|
|
68
|
-
wrapping=[
|
|
69
|
-
ProtocolCommandFailedError(
|
|
70
|
-
original_error=self._state_view.tasks.get_finished(
|
|
71
|
-
task_id
|
|
72
|
-
).error
|
|
73
|
-
)
|
|
74
|
-
for task_id in failed_tasks
|
|
75
|
-
],
|
|
76
|
-
)
|
|
77
|
-
return SuccessData(public=WaitForTasksResult(task_ids=params.task_ids))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class WaitForTasks(
|
|
81
|
-
BaseCommand[WaitForTasksParams, WaitForTasksResult, ErrorOccurrence]
|
|
82
|
-
):
|
|
83
|
-
"""WaitForTasks command model."""
|
|
84
|
-
|
|
85
|
-
commandType: WaitForTasksCommandType = "waitForTasks"
|
|
86
|
-
params: WaitForTasksParams
|
|
87
|
-
result: Optional[WaitForTasksResult] = None
|
|
88
|
-
|
|
89
|
-
_ImplementationCls: Type[WaitForTasksImplementation] = WaitForTasksImplementation
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class WaitForTasksCreate(BaseCommandCreate[WaitForTasksParams]):
|
|
93
|
-
"""WaitForTasks command request model."""
|
|
94
|
-
|
|
95
|
-
commandType: WaitForTasksCommandType = "waitForTasks"
|
|
96
|
-
params: WaitForTasksParams
|
|
97
|
-
|
|
98
|
-
_CommandCls: Type[WaitForTasks] = WaitForTasks
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""Task handling."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
import logging
|
|
5
|
-
from typing import Protocol, AsyncIterator
|
|
6
|
-
from ..state.state import StateStore
|
|
7
|
-
from ..resources import ModelUtils, ConcurrencyProvider
|
|
8
|
-
from ..types import Task
|
|
9
|
-
import asyncio
|
|
10
|
-
import contextlib
|
|
11
|
-
from ..actions import ActionDispatcher, FinishTaskAction, StartTaskAction
|
|
12
|
-
from ..errors import ErrorOccurrence
|
|
13
|
-
from opentrons_shared_data.errors.exceptions import EnumeratedError, PythonException
|
|
14
|
-
|
|
15
|
-
log = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class TaskFunction(Protocol):
|
|
19
|
-
"""The function run inside a task protocol."""
|
|
20
|
-
|
|
21
|
-
async def __call__(self, task_handler: TaskHandler) -> None:
|
|
22
|
-
"""The function called inside a task."""
|
|
23
|
-
...
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class TaskHandler:
|
|
27
|
-
"""Implementation logic for fast concurrency."""
|
|
28
|
-
|
|
29
|
-
_state_store: StateStore
|
|
30
|
-
_model_utils: ModelUtils
|
|
31
|
-
_concurrency_provider: ConcurrencyProvider
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
state_store: StateStore,
|
|
36
|
-
action_dispatcher: ActionDispatcher,
|
|
37
|
-
model_utils: ModelUtils | None = None,
|
|
38
|
-
concurrency_provider: ConcurrencyProvider | None = None,
|
|
39
|
-
) -> None:
|
|
40
|
-
"""Initialize a TaskHandler instance."""
|
|
41
|
-
self._state_store = state_store
|
|
42
|
-
self._model_utils = model_utils or ModelUtils()
|
|
43
|
-
self._concurrency_provider = concurrency_provider or ConcurrencyProvider()
|
|
44
|
-
self._action_dispatcher = action_dispatcher
|
|
45
|
-
|
|
46
|
-
async def create_task(
|
|
47
|
-
self, task_function: TaskFunction, id: str | None = None
|
|
48
|
-
) -> Task:
|
|
49
|
-
"""Create a task and immediately schedules it."""
|
|
50
|
-
task_id = self._model_utils.ensure_id(id)
|
|
51
|
-
asyncio_task = asyncio.create_task(
|
|
52
|
-
task_function(task_handler=self), name=f"engine-task-{task_id}"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
def _done_callback(task: asyncio.Task[None]) -> None:
|
|
56
|
-
try:
|
|
57
|
-
maybe_exception = task.exception()
|
|
58
|
-
except asyncio.CancelledError as e:
|
|
59
|
-
maybe_exception = e
|
|
60
|
-
if isinstance(maybe_exception, EnumeratedError):
|
|
61
|
-
occurence: ErrorOccurrence | None = ErrorOccurrence.from_failed(
|
|
62
|
-
id=self._model_utils.generate_id(),
|
|
63
|
-
createdAt=self._model_utils.get_timestamp(),
|
|
64
|
-
error=maybe_exception,
|
|
65
|
-
)
|
|
66
|
-
elif isinstance(maybe_exception, BaseException):
|
|
67
|
-
occurence = ErrorOccurrence.from_failed(
|
|
68
|
-
id=self._model_utils.generate_id(),
|
|
69
|
-
createdAt=self._model_utils.get_timestamp(),
|
|
70
|
-
error=PythonException(maybe_exception),
|
|
71
|
-
)
|
|
72
|
-
else:
|
|
73
|
-
occurence = None
|
|
74
|
-
try:
|
|
75
|
-
self._action_dispatcher.dispatch(
|
|
76
|
-
FinishTaskAction(
|
|
77
|
-
task_id=task_id,
|
|
78
|
-
finished_at=self._model_utils.get_timestamp(),
|
|
79
|
-
error=occurence,
|
|
80
|
-
),
|
|
81
|
-
)
|
|
82
|
-
except BaseException:
|
|
83
|
-
log.exception("Exception in task finish dispatch.")
|
|
84
|
-
|
|
85
|
-
asyncio_task.add_done_callback(_done_callback)
|
|
86
|
-
task = Task(
|
|
87
|
-
id=task_id,
|
|
88
|
-
createdAt=self._model_utils.get_timestamp(),
|
|
89
|
-
asyncioTask=asyncio_task,
|
|
90
|
-
)
|
|
91
|
-
self._action_dispatcher.dispatch(StartTaskAction(task))
|
|
92
|
-
return task
|
|
93
|
-
|
|
94
|
-
@staticmethod
|
|
95
|
-
def _empty_queue(
|
|
96
|
-
queue: "asyncio.Queue[asyncio.Task[None]]", this_task: asyncio.Task[None]
|
|
97
|
-
) -> None:
|
|
98
|
-
"""Empties the queue."""
|
|
99
|
-
try:
|
|
100
|
-
while True:
|
|
101
|
-
task = queue.get_nowait()
|
|
102
|
-
if task is this_task:
|
|
103
|
-
break
|
|
104
|
-
except asyncio.QueueEmpty:
|
|
105
|
-
pass
|
|
106
|
-
|
|
107
|
-
@contextlib.asynccontextmanager
|
|
108
|
-
async def synchronize_cancel_latest(self, group_id: str) -> AsyncIterator[None]:
|
|
109
|
-
"""Cancel current task."""
|
|
110
|
-
lock = self._concurrency_provider.lock_for_group(group_id)
|
|
111
|
-
if lock.locked():
|
|
112
|
-
raise asyncio.CancelledError()
|
|
113
|
-
async with lock:
|
|
114
|
-
yield
|
|
115
|
-
|
|
116
|
-
@contextlib.asynccontextmanager
|
|
117
|
-
async def synchronize_cancel_previous(self, group_id: str) -> AsyncIterator[None]:
|
|
118
|
-
"""Cancel previous run."""
|
|
119
|
-
queue = self._concurrency_provider.queue_for_group(group_id)
|
|
120
|
-
while not queue.empty():
|
|
121
|
-
task = queue.get_nowait()
|
|
122
|
-
task.cancel()
|
|
123
|
-
this_task = asyncio.current_task()
|
|
124
|
-
assert this_task is not None
|
|
125
|
-
queue.put_nowait(this_task)
|
|
126
|
-
try:
|
|
127
|
-
yield
|
|
128
|
-
except asyncio.CancelledError:
|
|
129
|
-
raise
|
|
130
|
-
except BaseException:
|
|
131
|
-
self._empty_queue(queue, this_task)
|
|
132
|
-
raise
|
|
133
|
-
else:
|
|
134
|
-
self._empty_queue(queue, this_task)
|
|
135
|
-
|
|
136
|
-
@contextlib.asynccontextmanager
|
|
137
|
-
async def synchronize_sequential(self, group_id: str) -> AsyncIterator[None]:
|
|
138
|
-
"""Run tasks one after the other."""
|
|
139
|
-
lock = self._concurrency_provider.lock_for_group(group_id)
|
|
140
|
-
async with lock:
|
|
141
|
-
yield
|
|
142
|
-
|
|
143
|
-
@contextlib.asynccontextmanager
|
|
144
|
-
async def synchronize_concurrent(self, group_id: str) -> AsyncIterator[None]:
|
|
145
|
-
"""Run a list of tasks at the same time."""
|
|
146
|
-
yield
|
|
147
|
-
|
|
148
|
-
def cancel_all(self, message: str | None = None) -> None:
|
|
149
|
-
"""Cancel all asyncio tasks immediately.
|
|
150
|
-
|
|
151
|
-
Do not call this more than once synchronously because
|
|
152
|
-
that could lead to tasks cancelling more than once.
|
|
153
|
-
It can be called if there are no current tasks. In that case
|
|
154
|
-
nothing will happen.
|
|
155
|
-
"""
|
|
156
|
-
for task in self._state_store.tasks.get_all_current():
|
|
157
|
-
task.asyncioTask.cancel(msg=message)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Concurrency primitives providers."""
|
|
2
|
-
import asyncio
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ConcurrencyProvider:
|
|
6
|
-
"""Concurrency primitives for engine tasks."""
|
|
7
|
-
|
|
8
|
-
def __init__(self) -> None:
|
|
9
|
-
"""Build a concurrency provider."""
|
|
10
|
-
self._locks: dict[str, asyncio.Lock] = {}
|
|
11
|
-
self._queues: dict[str, "asyncio.Queue[asyncio.Task[None]]"] = {}
|
|
12
|
-
|
|
13
|
-
def lock_for_group(self, group_id: str) -> asyncio.Lock:
|
|
14
|
-
"""Returns the lock for specified group id."""
|
|
15
|
-
try:
|
|
16
|
-
return self._locks[group_id]
|
|
17
|
-
except KeyError:
|
|
18
|
-
self._locks[group_id] = asyncio.Lock()
|
|
19
|
-
return self._locks[group_id]
|
|
20
|
-
|
|
21
|
-
def queue_for_group(self, group_id: str) -> "asyncio.Queue[asyncio.Task[None]]":
|
|
22
|
-
"""Returns the queue for specified group id."""
|
|
23
|
-
try:
|
|
24
|
-
return self._queues[group_id]
|
|
25
|
-
except KeyError:
|
|
26
|
-
self._queues[group_id] = asyncio.Queue()
|
|
27
|
-
return self._queues[group_id]
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
"""Labware origin math errors."""
|
|
2
|
-
|
|
3
|
-
from typing import Any, Dict, Optional, Sequence
|
|
4
|
-
|
|
5
|
-
from opentrons.protocol_engine.errors import ProtocolEngineError
|
|
6
|
-
from opentrons_shared_data.errors import ErrorCodes
|
|
7
|
-
from opentrons_shared_data.errors.exceptions import EnumeratedError
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class LabwareLocatingFeatureError(ProtocolEngineError):
|
|
11
|
-
"""Base class for errors related to labware locating features."""
|
|
12
|
-
|
|
13
|
-
def __init__(
|
|
14
|
-
self,
|
|
15
|
-
message: Optional[str] = None,
|
|
16
|
-
details: Optional[Dict[str, Any]] = None,
|
|
17
|
-
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
18
|
-
) -> None:
|
|
19
|
-
"""Build a LabwareLocatingFeatureError."""
|
|
20
|
-
super().__init__(
|
|
21
|
-
ErrorCodes.LABWARE_LOCATING_FEATURE_ERROR, message, details, wrapping
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class MissingLocatingFeatureError(LabwareLocatingFeatureError):
|
|
26
|
-
"""Raised when a labware definition is missing a required locating feature."""
|
|
27
|
-
|
|
28
|
-
def __init__(
|
|
29
|
-
self,
|
|
30
|
-
labware_name: str,
|
|
31
|
-
required_feature: str,
|
|
32
|
-
message: Optional[str] = None,
|
|
33
|
-
details: Optional[Dict[str, Any]] = None,
|
|
34
|
-
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
35
|
-
) -> None:
|
|
36
|
-
"""Build a MissingLocatingFeatureError."""
|
|
37
|
-
if message is None:
|
|
38
|
-
message = f"Expected {labware_name} to have {required_feature} feature"
|
|
39
|
-
|
|
40
|
-
if details is None:
|
|
41
|
-
details = {
|
|
42
|
-
"labware_name": labware_name,
|
|
43
|
-
"required_feature": required_feature,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
super().__init__(message, details, wrapping)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class InvalidLabwarePlacementError(LabwareLocatingFeatureError):
|
|
50
|
-
"""Raised when a labware cannot be placed in the specified location due to locating feature constraints."""
|
|
51
|
-
|
|
52
|
-
def __init__(
|
|
53
|
-
self,
|
|
54
|
-
feature_name: str,
|
|
55
|
-
invalid_placement: str,
|
|
56
|
-
message: Optional[str] = None,
|
|
57
|
-
details: Optional[Dict[str, Any]] = None,
|
|
58
|
-
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
59
|
-
) -> None:
|
|
60
|
-
"""Build an InvalidLabwarePlacementError."""
|
|
61
|
-
if message is None:
|
|
62
|
-
message = f"{feature_name} feature does not support placement: {invalid_placement}"
|
|
63
|
-
|
|
64
|
-
if details is None:
|
|
65
|
-
details = {
|
|
66
|
-
"feature_name": feature_name,
|
|
67
|
-
"invalid_placement": invalid_placement,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
super().__init__(message, details, wrapping)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class IncompatibleLocatingFeatureError(LabwareLocatingFeatureError):
|
|
74
|
-
"""Raised when parent and child labware have incompatible locating features."""
|
|
75
|
-
|
|
76
|
-
def __init__(
|
|
77
|
-
self,
|
|
78
|
-
parent_feature: str,
|
|
79
|
-
child_feature: str,
|
|
80
|
-
message: Optional[str] = None,
|
|
81
|
-
details: Optional[Dict[str, Any]] = None,
|
|
82
|
-
wrapping: Optional[Sequence[EnumeratedError]] = None,
|
|
83
|
-
) -> None:
|
|
84
|
-
"""Build an IncompatibleLocatingFeatureError."""
|
|
85
|
-
if message is None:
|
|
86
|
-
message = f"Incompatible labware features: parent {parent_feature}, child {child_feature}"
|
|
87
|
-
|
|
88
|
-
if details is None:
|
|
89
|
-
details = {
|
|
90
|
-
"parent_feature": parent_feature,
|
|
91
|
-
"child_feature": child_feature,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
super().__init__(message, details, wrapping)
|