opentrons 8.7.0a7__py3-none-any.whl → 8.7.0a9__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/asyncio/communication/serial_connection.py +55 -129
- opentrons/drivers/flex_stacker/driver.py +6 -1
- opentrons/drivers/heater_shaker/abstract.py +0 -5
- opentrons/drivers/heater_shaker/driver.py +0 -10
- opentrons/drivers/heater_shaker/simulator.py +0 -4
- opentrons/drivers/thermocycler/abstract.py +0 -6
- opentrons/drivers/thermocycler/driver.py +10 -61
- opentrons/drivers/thermocycler/simulator.py +0 -6
- opentrons/hardware_control/api.py +5 -24
- opentrons/hardware_control/backends/controller.py +2 -8
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +3 -3
- opentrons/hardware_control/backends/ot3simulator.py +2 -2
- opentrons/hardware_control/backends/simulator.py +1 -2
- opentrons/hardware_control/backends/subsystem_manager.py +2 -5
- opentrons/hardware_control/emulation/abstract_emulator.py +4 -6
- opentrons/hardware_control/emulation/connection_handler.py +5 -8
- opentrons/hardware_control/emulation/heater_shaker.py +3 -12
- opentrons/hardware_control/emulation/settings.py +1 -1
- opentrons/hardware_control/emulation/thermocycler.py +15 -67
- 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 -42
- 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 +11 -68
- 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 +7 -26
- opentrons/hardware_control/poller.py +8 -22
- opentrons/hardware_control/protocols/gripper_controller.py +1 -0
- opentrons/hardware_control/scripts/update_module_fw.py +0 -5
- opentrons/hardware_control/types.py +2 -31
- opentrons/legacy_commands/module_commands.py +0 -23
- opentrons/legacy_commands/protocol_commands.py +0 -20
- opentrons/legacy_commands/types.py +0 -80
- opentrons/motion_planning/deck_conflict.py +12 -17
- opentrons/motion_planning/waypoints.py +29 -15
- opentrons/protocol_api/__init__.py +1 -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 +8 -75
- opentrons/protocol_api/core/engine/protocol.py +1 -18
- opentrons/protocol_api/core/engine/well.py +0 -8
- opentrons/protocol_api/core/legacy/legacy_module_core.py +4 -24
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -11
- opentrons/protocol_api/core/legacy/legacy_well_core.py +0 -4
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +2 -14
- opentrons/protocol_api/core/module.py +4 -37
- opentrons/protocol_api/core/protocol.py +2 -11
- opentrons/protocol_api/core/well.py +0 -4
- opentrons/protocol_api/labware.py +0 -5
- opentrons/protocol_api/module_contexts.py +61 -122
- 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 -42
- 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 -49
- opentrons/protocol_engine/commands/dispense.py +0 -1
- opentrons/protocol_engine/commands/drop_tip.py +8 -32
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +0 -14
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +4 -5
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +5 -31
- opentrons/protocol_engine/commands/movement_common.py +0 -2
- opentrons/protocol_engine/commands/pick_up_tip.py +11 -21
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +7 -38
- opentrons/protocol_engine/commands/thermocycler/__init__.py +0 -16
- 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 +6 -40
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +5 -29
- 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 +21 -10
- 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 +34 -75
- 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 +11 -14
- opentrons/protocol_engine/state/geometry.py +374 -213
- 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.0a7.dist-info → opentrons-8.7.0a9.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a7.dist-info → opentrons-8.7.0a9.dist-info}/RECORD +130 -146
- opentrons/protocol_api/core/engine/tasks.py +0 -48
- 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/heater_shaker/common.py +0 -20
- opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +0 -136
- opentrons/protocol_engine/commands/set_tip_state.py +0 -97
- opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +0 -191
- 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.0a7.dist-info → opentrons-8.7.0a9.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a7.dist-info → opentrons-8.7.0a9.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a7.dist-info → opentrons-8.7.0a9.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
"""StartRunProfile command request, result, and implementation models."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
5
|
-
from typing import List, Optional, TYPE_CHECKING, overload, Union, Any
|
|
6
|
-
from typing_extensions import Literal, Type
|
|
7
|
-
from pydantic.json_schema import SkipJsonSchema
|
|
8
|
-
|
|
9
|
-
from opentrons.hardware_control.modules.types import ThermocyclerStep, ThermocyclerCycle
|
|
10
|
-
|
|
11
|
-
from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
|
|
12
|
-
from ...errors.error_occurrence import ErrorOccurrence
|
|
13
|
-
from .run_extended_profile import ProfileStep, ProfileCycle
|
|
14
|
-
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from opentrons.protocol_engine.state.state import StateView
|
|
17
|
-
from opentrons.protocol_engine.execution import (
|
|
18
|
-
TaskHandler,
|
|
19
|
-
EquipmentHandler,
|
|
20
|
-
)
|
|
21
|
-
from opentrons.protocol_engine.state.module_substates.thermocycler_module_substate import (
|
|
22
|
-
ThermocyclerModuleSubState,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
StartRunExtendedProfileCommandType = Literal["thermocycler/startRunExtendedProfile"]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _remove_default(s: dict[str, Any]) -> None:
|
|
29
|
-
s.pop("default", None)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class StartRunExtendedProfileStepParams(BaseModel):
|
|
33
|
-
"""Input parameters for an individual Thermocycler profile step."""
|
|
34
|
-
|
|
35
|
-
celsius: float = Field(..., description="Target temperature in °C.")
|
|
36
|
-
holdSeconds: float = Field(
|
|
37
|
-
..., description="Time to hold target temperature at in seconds."
|
|
38
|
-
)
|
|
39
|
-
rampRate: float | SkipJsonSchema[None] = Field(
|
|
40
|
-
None,
|
|
41
|
-
description="How quickly to change temperature in °C/second.",
|
|
42
|
-
json_schema_extra=_remove_default,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class StartRunExtendedProfileParams(BaseModel):
|
|
47
|
-
"""Input parameters to run a Thermocycler profile."""
|
|
48
|
-
|
|
49
|
-
moduleId: str = Field(..., description="Unique ID of the Thermocycler.")
|
|
50
|
-
profileElements: List[Union[ProfileStep, ProfileCycle]] = Field(
|
|
51
|
-
...,
|
|
52
|
-
description="Elements of the profile. Each can be either a step or a cycle.",
|
|
53
|
-
)
|
|
54
|
-
blockMaxVolumeUl: float | SkipJsonSchema[None] = Field(
|
|
55
|
-
None,
|
|
56
|
-
description="Amount of liquid in uL of the most-full well"
|
|
57
|
-
" in labware loaded onto the thermocycler.",
|
|
58
|
-
json_schema_extra=_remove_default,
|
|
59
|
-
)
|
|
60
|
-
taskId: str | None = Field(None, description="The id of the profile task")
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class StartRunExtendedProfileResult(BaseModel):
|
|
64
|
-
"""Result data from running a Thermocycler profile."""
|
|
65
|
-
|
|
66
|
-
taskId: str = Field(..., description="The id of the profile task")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _transform_profile_step(
|
|
70
|
-
step: ProfileStep, thermocycler_state: ThermocyclerModuleSubState
|
|
71
|
-
) -> ThermocyclerStep:
|
|
72
|
-
return ThermocyclerStep(
|
|
73
|
-
temperature=thermocycler_state.validate_target_block_temperature(step.celsius),
|
|
74
|
-
hold_time_seconds=step.holdSeconds,
|
|
75
|
-
ramp_rate=thermocycler_state.validate_ramp_rate(step.rampRate, step.celsius),
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@overload
|
|
80
|
-
def _transform_profile_element(
|
|
81
|
-
element: ProfileStep, thermocycler_state: ThermocyclerModuleSubState
|
|
82
|
-
) -> ThermocyclerStep:
|
|
83
|
-
...
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@overload
|
|
87
|
-
def _transform_profile_element(
|
|
88
|
-
element: ProfileCycle, thermocycler_state: ThermocyclerModuleSubState
|
|
89
|
-
) -> ThermocyclerCycle:
|
|
90
|
-
...
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _transform_profile_element(
|
|
94
|
-
element: Union[ProfileStep, ProfileCycle],
|
|
95
|
-
thermocycler_state: ThermocyclerModuleSubState,
|
|
96
|
-
) -> Union[ThermocyclerStep, ThermocyclerCycle]:
|
|
97
|
-
if isinstance(element, ProfileStep):
|
|
98
|
-
return _transform_profile_step(element, thermocycler_state)
|
|
99
|
-
else:
|
|
100
|
-
return ThermocyclerCycle(
|
|
101
|
-
steps=[
|
|
102
|
-
_transform_profile_step(step, thermocycler_state)
|
|
103
|
-
for step in element.steps
|
|
104
|
-
],
|
|
105
|
-
repetitions=element.repetitions,
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
class StartRunExtendedProfileImpl(
|
|
110
|
-
AbstractCommandImpl[
|
|
111
|
-
StartRunExtendedProfileParams, SuccessData[StartRunExtendedProfileResult]
|
|
112
|
-
]
|
|
113
|
-
):
|
|
114
|
-
"""Execution implementation of a Thermocycler's run profile command."""
|
|
115
|
-
|
|
116
|
-
def __init__(
|
|
117
|
-
self,
|
|
118
|
-
state_view: StateView,
|
|
119
|
-
equipment: EquipmentHandler,
|
|
120
|
-
task_handler: TaskHandler,
|
|
121
|
-
**unused_dependencies: object,
|
|
122
|
-
) -> None:
|
|
123
|
-
self._state_view = state_view
|
|
124
|
-
self._equipment = equipment
|
|
125
|
-
self._task_handler = task_handler
|
|
126
|
-
|
|
127
|
-
async def execute(
|
|
128
|
-
self, params: StartRunExtendedProfileParams
|
|
129
|
-
) -> SuccessData[StartRunExtendedProfileResult]:
|
|
130
|
-
"""Run a Thermocycler profile."""
|
|
131
|
-
thermocycler_state = self._state_view.modules.get_thermocycler_module_substate(
|
|
132
|
-
params.moduleId
|
|
133
|
-
)
|
|
134
|
-
thermocycler_hardware = self._equipment.get_module_hardware_api(
|
|
135
|
-
thermocycler_state.module_id
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
profile = [
|
|
139
|
-
_transform_profile_element(element, thermocycler_state)
|
|
140
|
-
for element in params.profileElements
|
|
141
|
-
]
|
|
142
|
-
target_volume: Optional[float]
|
|
143
|
-
if params.blockMaxVolumeUl is not None:
|
|
144
|
-
target_volume = thermocycler_state.validate_max_block_volume(
|
|
145
|
-
params.blockMaxVolumeUl
|
|
146
|
-
)
|
|
147
|
-
else:
|
|
148
|
-
target_volume = None
|
|
149
|
-
|
|
150
|
-
async def start_run_profile(task_handler: TaskHandler) -> None:
|
|
151
|
-
if thermocycler_hardware is not None:
|
|
152
|
-
async with task_handler.synchronize_cancel_latest(
|
|
153
|
-
thermocycler_state.module_id
|
|
154
|
-
):
|
|
155
|
-
await thermocycler_hardware.execute_profile(
|
|
156
|
-
profile=profile, volume=target_volume
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
task = await self._task_handler.create_task(
|
|
160
|
-
task_function=start_run_profile, id=params.taskId
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
return SuccessData(
|
|
164
|
-
public=StartRunExtendedProfileResult(taskId=task.id),
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class StartRunExtendedProfile(
|
|
169
|
-
BaseCommand[
|
|
170
|
-
StartRunExtendedProfileParams, StartRunExtendedProfileResult, ErrorOccurrence
|
|
171
|
-
]
|
|
172
|
-
):
|
|
173
|
-
"""A command to start the execution of a Thermocycler profile run without waiting for its completion."""
|
|
174
|
-
|
|
175
|
-
commandType: StartRunExtendedProfileCommandType = (
|
|
176
|
-
"thermocycler/startRunExtendedProfile"
|
|
177
|
-
)
|
|
178
|
-
params: StartRunExtendedProfileParams
|
|
179
|
-
|
|
180
|
-
_ImplementationCls: Type[StartRunExtendedProfileImpl] = StartRunExtendedProfileImpl
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class StartRunExtendedProfileCreate(BaseCommandCreate[StartRunExtendedProfileParams]):
|
|
184
|
-
"""A request to execute a Thermocycler profile run."""
|
|
185
|
-
|
|
186
|
-
commandType: StartRunExtendedProfileCommandType = (
|
|
187
|
-
"thermocycler/startRunExtendedProfile"
|
|
188
|
-
)
|
|
189
|
-
params: StartRunExtendedProfileParams
|
|
190
|
-
|
|
191
|
-
_CommandCls: Type[StartRunExtendedProfile] = StartRunExtendedProfile
|
|
@@ -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)
|