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(robot-server): update run creation endpoint to accept runtime parameter values #14776

Merged
merged 6 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
19 changes: 11 additions & 8 deletions api/src/opentrons/protocols/parameters/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,17 @@ def ensure_value_type(

This does not guarantee that the value will be the correct type for the given parameter, only that any data coming
in is in the format that we expect. For now, the only transformation it is doing is converting integers represented
as floating points to integers. If something is labelled as an int but is not actually an integer, that will be
caught when it is attempted to be set as the parameter value and will raise the appropriate error there.
as floating points to integers, and bools represented as 1.0/0.0 to True/False.

If something is labelled as a type but does not get converted here, that will be caught when it is attempted to be
set as the parameter value and will raise the appropriate error there.
"""
validated_value: AllowedTypes
if isinstance(value, float) and parameter_type is int and value.is_integer():
validated_value = int(value)
else:
validated_value = value
validated_value: AllowedTypes = value
if isinstance(value, float):
if parameter_type is bool and (value == 0 or value == 1):
validated_value = bool(value)
elif parameter_type is int and value.is_integer():
validated_value = int(value)
return validated_value


Expand Down Expand Up @@ -163,7 +166,7 @@ def validate_type(value: ParamType, parameter_type: type) -> None:
"""Validate parameter value is the correct type."""
if not isinstance(value, parameter_type):
raise ParameterValueError(
f"Parameter value has type {type(value)} must match type {parameter_type}."
f"Parameter value {value} has type {type(value)}, must match type {parameter_type}."
)


Expand Down
3 changes: 3 additions & 0 deletions api/tests/opentrons/protocols/parameters/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ def test_validate_options_raises_name_error() -> None:
(2.0, float, 2.0),
(2.2, float, 2.2),
("3.0", str, "3.0"),
(0.0, bool, False),
(1, bool, True),
(3.0, bool, 3.0),
(True, bool, True),
],
)
Expand Down
12 changes: 9 additions & 3 deletions robot-server/robot_server/runs/engine_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
)

from robot_server.protocols.protocol_store import ProtocolResource
from opentrons.protocol_engine.types import DeckConfigurationType
from opentrons.protocol_engine.types import (
DeckConfigurationType,
RunTimeParamValuesType,
)


class EngineConflictError(RuntimeError):
Expand Down Expand Up @@ -154,14 +157,17 @@ async def create(
deck_configuration: DeckConfigurationType,
notify_publishers: Callable[[], None],
protocol: Optional[ProtocolResource],
run_time_param_values: Optional[RunTimeParamValuesType] = None,
) -> StateSummary:
"""Create and store a ProtocolRunner and ProtocolEngine for a given Run.

Args:
run_id: The run resource the engine is assigned to.
labware_offsets: Labware offsets to create the engine with.
protocol: The protocol to load the runner with, if any.
deck_configuration: A mapping of fixtures to cutout fixtures the deck will be loaded with.
notify_publishers: Utilized by the engine to notify publishers of state changes.
protocol: The protocol to load the runner with, if any.
run_time_param_values: Any runtime parameter values to set.

Returns:
The initial equipment and status summary of the engine.
Expand Down Expand Up @@ -217,7 +223,7 @@ async def create(
# was uploaded before we added stricter validation, and that
# doesn't conform to the new rules.
python_parse_mode=PythonParseMode.ALLOW_LEGACY_METADATA_AND_REQUIREMENTS,
run_time_param_values=None,
run_time_param_values=run_time_param_values,
)
elif isinstance(runner, JsonRunner):
assert (
Expand Down
4 changes: 4 additions & 0 deletions robot-server/robot_server/runs/router/base_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ async def create_run(
"""
protocol_id = request_body.data.protocolId if request_body is not None else None
offsets = request_body.data.labwareOffsets if request_body is not None else []
rtp_values = (
request_body.data.runTimeParameterValues if request_body is not None else None
)
protocol_resource = None

deck_configuration = await deck_configuration_store.get_deck_configuration()
Expand All @@ -185,6 +188,7 @@ async def create_run(
created_at=created_at,
labware_offsets=offsets,
deck_configuration=deck_configuration,
run_time_param_values=rtp_values,
protocol=protocol_resource,
notify_publishers=notify_publishers,
)
Expand Down
6 changes: 6 additions & 0 deletions robot-server/robot_server/runs/run_data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CurrentCommand,
Command,
)
from opentrons.protocol_engine.types import RunTimeParamValuesType

from robot_server.protocols.protocol_store import ProtocolResource
from robot_server.service.task_runner import TaskRunner
Expand Down Expand Up @@ -142,6 +143,7 @@ async def create(
created_at: datetime,
labware_offsets: List[LabwareOffsetCreate],
deck_configuration: DeckConfigurationType,
run_time_param_values: Optional[RunTimeParamValuesType],
notify_publishers: Callable[[], None],
protocol: Optional[ProtocolResource],
) -> Union[Run, BadRun]:
Expand All @@ -151,7 +153,10 @@ async def create(
run_id: Identifier to assign the new run.
created_at: Creation datetime.
labware_offsets: Labware offsets to initialize the engine with.
deck_configuration: A mapping of fixtures to cutout fixtures the deck will be loaded with.
notify_publishers: Utilized by the engine to notify publishers of state changes.
run_time_param_values: Any runtime parameter values to set.
protocol: The protocol to load the runner with, if any.

Returns:
The run resource.
Expand All @@ -173,6 +178,7 @@ async def create(
labware_offsets=labware_offsets,
deck_configuration=deck_configuration,
protocol=protocol,
run_time_param_values=run_time_param_values,
notify_publishers=notify_publishers,
)
run_resource = self._run_store.insert(
Expand Down
7 changes: 7 additions & 0 deletions robot-server/robot_server/runs/run_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Liquid,
CommandNote,
)
from opentrons.protocol_engine.types import RunTimeParamValuesType
from opentrons_shared_data.errors import GeneralError
from robot_server.service.json_api import ResourceModel
from robot_server.errors.error_responses import ErrorDetails
Expand Down Expand Up @@ -212,6 +213,12 @@ class RunCreate(BaseModel):
default_factory=list,
description="Labware offsets to apply as labware are loaded.",
)
runTimeParameterValues: Optional[RunTimeParamValuesType] = Field(
None,
description="Key-value pairs of run-time parameters defined in a protocol."
" Also, if this data is included in the request, the server will"
" always trigger an analysis (for now).",
jbleon95 marked this conversation as resolved.
Show resolved Hide resolved
)


class RunUpdate(BaseModel):
Expand Down
9 changes: 8 additions & 1 deletion robot-server/tests/runs/router/test_base_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ async def test_create_run(
labware_offsets=[labware_offset_create],
deck_configuration=[],
protocol=None,
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)
).then_return(expected_response)
Expand Down Expand Up @@ -169,12 +170,17 @@ async def test_create_protocol_run(
labware_offsets=[],
deck_configuration=[],
protocol=protocol_resource,
run_time_param_values={"foo": "bar"},
notify_publishers=mock_notify_publishers,
)
).then_return(expected_response)

result = await create_run(
request_body=RequestModel(data=RunCreate(protocolId="protocol-id")),
request_body=RequestModel(
data=RunCreate(
protocolId="protocol-id", runTimeParameterValues={"foo": "bar"}
)
),
protocol_store=mock_protocol_store,
run_data_manager=mock_run_data_manager,
run_id=run_id,
Expand Down Expand Up @@ -232,6 +238,7 @@ async def test_create_run_conflict(
labware_offsets=[],
deck_configuration=[],
protocol=None,
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)
).then_raise(EngineConflictError("oh no"))
Expand Down
10 changes: 9 additions & 1 deletion robot-server/tests/runs/test_run_data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ async def test_create(
labware_offsets=[],
protocol=None,
deck_configuration=[],
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)
).then_return(engine_state_summary)
Expand All @@ -160,6 +161,7 @@ async def test_create(
labware_offsets=[],
protocol=None,
deck_configuration=[],
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)

Expand Down Expand Up @@ -187,7 +189,7 @@ async def test_create_with_options(
engine_state_summary: StateSummary,
run_resource: RunResource,
) -> None:
"""It should handle creation with a protocol and labware offsets."""
"""It should handle creation with a protocol, labware offsets and parameters."""
run_id = "hello world"
created_at = datetime(year=2021, month=1, day=1)

Expand All @@ -210,6 +212,7 @@ async def test_create_with_options(
labware_offsets=[labware_offset],
protocol=protocol,
deck_configuration=[],
run_time_param_values={"foo": "bar"},
notify_publishers=mock_notify_publishers,
)
).then_return(engine_state_summary)
Expand All @@ -228,6 +231,7 @@ async def test_create_with_options(
labware_offsets=[labware_offset],
protocol=protocol,
deck_configuration=[],
run_time_param_values={"foo": "bar"},
notify_publishers=mock_notify_publishers,
)

Expand Down Expand Up @@ -263,6 +267,7 @@ async def test_create_engine_error(
labware_offsets=[],
protocol=None,
deck_configuration=[],
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)
).then_raise(EngineConflictError("oh no"))
Expand All @@ -274,6 +279,7 @@ async def test_create_engine_error(
labware_offsets=[],
protocol=None,
deck_configuration=[],
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)

Expand Down Expand Up @@ -651,6 +657,7 @@ async def test_create_archives_existing(
labware_offsets=[],
protocol=None,
deck_configuration=[],
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)
).then_return(engine_state_summary)
Expand All @@ -669,6 +676,7 @@ async def test_create_archives_existing(
labware_offsets=[],
protocol=None,
deck_configuration=[],
run_time_param_values=None,
notify_publishers=mock_notify_publishers,
)

Expand Down
Loading