Skip to content

Commit

Permalink
load runner before running, show RTPs in pending analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
sanni-t committed May 20, 2024
1 parent 29ba5b8 commit 19e5c5b
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 56 deletions.
10 changes: 10 additions & 0 deletions robot-server/robot_server/protocols/analysis_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ class PendingAnalysis(BaseModel):
AnalysisStatus.PENDING,
description="Status marking the analysis as pending",
)
runTimeParameters: List[RunTimeParameter] = Field(
default_factory=list,
description=(
"Run time parameters used during analysis."
" These are the parameters that are defined in the protocol, with values"
" specified either in the protocol creation request or reanalysis request"
" (whichever started this analysis), or default values from the protocol"
" if none are specified in the request."
),
)


class CompletedAnalysis(BaseModel):
Expand Down
24 changes: 20 additions & 4 deletions robot-server/robot_server/protocols/analysis_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ def __init__(
current_analyzer_version=_CURRENT_ANALYZER_VERSION,
)

def add_pending(self, protocol_id: str, analysis_id: str) -> AnalysisSummary:
def add_pending(
self,
protocol_id: str,
analysis_id: str,
run_time_parameters: Optional[List[RunTimeParameter]] = None,
) -> AnalysisSummary:
"""Add a new pending analysis to the store.
Args:
Expand All @@ -125,12 +130,16 @@ def add_pending(self, protocol_id: str, analysis_id: str) -> AnalysisSummary:
a pending analysis.
analysis_id: The ID of the new analysis.
Must be unique across *all* protocols, not just this one.
run_time_parameters: Run time parameters defined in the protocol, along with
override values for the run.
Returns:
A summary of the just-added analysis.
"""
new_pending_analysis = self._pending_store.add(
protocol_id=protocol_id, analysis_id=analysis_id
protocol_id=protocol_id,
analysis_id=analysis_id,
run_time_parameters=run_time_parameters or [],
)
return _summarize_pending(pending_analysis=new_pending_analysis)

Expand Down Expand Up @@ -371,13 +380,20 @@ def __init__(self) -> None:
self._analysis_ids_by_protocol_id: Dict[str, str] = {}
self._protocol_ids_by_analysis_id: Dict[str, str] = {}

def add(self, protocol_id: str, analysis_id: str) -> PendingAnalysis:
def add(
self,
protocol_id: str,
analysis_id: str,
run_time_parameters: List[RunTimeParameter],
) -> PendingAnalysis:
"""Add a new pending analysis and associate it with the given protocol."""
assert (
protocol_id not in self._analysis_ids_by_protocol_id
), "Protocol must not already have a pending analysis."

new_pending_analysis = PendingAnalysis.construct(id=analysis_id)
new_pending_analysis = PendingAnalysis.construct(
id=analysis_id, runTimeParameters=run_time_parameters
)

self._analyses_by_id[analysis_id] = new_pending_analysis
self._analysis_ids_by_protocol_id[protocol_id] = analysis_id
Expand Down
93 changes: 63 additions & 30 deletions robot-server/robot_server/protocols/protocol_analyzer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Protocol analysis module."""
import logging
from typing import Optional
from typing import Optional, List

from opentrons_shared_data.robot.dev_types import RobotType

from opentrons import protocol_runner
from opentrons.protocol_engine.errors import ErrorOccurrence
from opentrons.protocol_engine.types import RunTimeParamValuesType
from opentrons.protocol_engine.types import RunTimeParamValuesType, RunTimeParameter
import opentrons.util.helpers as datetime_helper
from opentrons.protocol_runner import AbstractRunner, PythonAndLegacyRunner, JsonRunner
from opentrons.protocols.parse import PythonParseMode

import robot_server.errors.error_mappers as em

Expand All @@ -24,46 +28,46 @@ def __init__(
) -> None:
"""Initialize the analyzer and its dependencies."""
self._analysis_store = analysis_store
self._runner: Optional[AbstractRunner] = None

async def analyze(
async def load_runner(
self,
protocol_resource: ProtocolResource,
analysis_id: str,
run_time_param_values: Optional[RunTimeParamValuesType],
) -> None:
"""Analyze a given protocol, storing the analysis when complete."""
runner = await protocol_runner.create_simulating_runner(
) -> List[RunTimeParameter]:
"""Load the runner with the protocl and run time parameters."""
self._runner = await protocol_runner.create_simulating_runner(
robot_type=protocol_resource.source.robot_type,
protocol_config=protocol_resource.source.config,
)
try:
result = await runner.run(
if isinstance(self._runner, PythonAndLegacyRunner):
await self._runner.load(
protocol_source=protocol_resource.source,
deck_configuration=[],
python_parse_mode=PythonParseMode.NORMAL,
run_time_param_values=run_time_param_values,
)
elif isinstance(self._runner, JsonRunner):
await self._runner.load(protocol_source=protocol_resource.source)
return self._runner.run_time_parameters

async def analyze(
self,
protocol_resource: ProtocolResource,
analysis_id: str,
run_time_parameters: Optional[List[RunTimeParameter]] = None,
) -> None:
"""Analyze a given protocol, storing the analysis when complete."""
assert self._runner is not None
try:
result = await self._runner.run(
deck_configuration=[],
)
except BaseException as error:
internal_error = em.map_unexpected_error(error=error)
await self._analysis_store.update(
await self.update_to_failed_analysis(
analysis_id=analysis_id,
robot_type=protocol_resource.source.robot_type,
# TODO (spp, 2024-03-12): populate the RTP field if we decide to have
# parameter parsing and validation in protocol reader itself.
run_time_parameters=[],
commands=[],
labware=[],
modules=[],
pipettes=[],
errors=[
ErrorOccurrence.from_failed(
# TODO(tz, 2-15-24): replace with a different error type
# when we are able to support different errors.
id="internal-error",
createdAt=datetime_helper.utc_now(),
error=internal_error,
)
],
liquids=[],
protocol_robot_type=protocol_resource.source.robot_type,
error=error,
run_time_parameters=run_time_parameters or [],
)
return

Expand All @@ -80,3 +84,32 @@ async def analyze(
errors=result.state_summary.errors,
liquids=result.state_summary.liquids,
)

async def update_to_failed_analysis(
self,
analysis_id: str,
protocol_robot_type: RobotType,
error: BaseException,
run_time_parameters: List[RunTimeParameter],
) -> None:
"""Update analysis store with analysis failure."""
internal_error = em.map_unexpected_error(error=error)
await self._analysis_store.update(
analysis_id=analysis_id,
robot_type=protocol_robot_type,
run_time_parameters=run_time_parameters,
commands=[],
labware=[],
modules=[],
pipettes=[],
errors=[
ErrorOccurrence.from_failed(
# TODO(tz, 2-15-24): replace with a different error type
# when we are able to support different errors.
id="internal-error",
createdAt=datetime_helper.utc_now(),
error=internal_error,
)
],
liquids=[],
)
78 changes: 58 additions & 20 deletions robot-server/robot_server/protocols/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,25 +311,43 @@ async def create_protocol(

protocol_auto_deleter.make_room_for_new_protocol()
protocol_store.insert(protocol_resource)

task_runner.run(
protocol_analyzer.analyze,
protocol_resource=protocol_resource,
analysis_id=analysis_id,
run_time_param_values=parsed_rtp,
)
pending_analysis = analysis_store.add_pending(
protocol_id=protocol_id,
analysis_id=analysis_id,
)
try:
run_time_parameters = await protocol_analyzer.load_runner(
protocol_resource=protocol_resource,
run_time_param_values=parsed_rtp,
)
except BaseException as error:
analysis_store.add_pending(
protocol_id=protocol_id,
analysis_id=analysis_id,
run_time_parameters=[],
)
await protocol_analyzer.update_to_failed_analysis(
analysis_id=analysis_id,
protocol_robot_type=protocol_resource.source.robot_type,
error=error,
run_time_parameters=[],
)
else:
analysis_store.add_pending(
protocol_id=protocol_id,
analysis_id=analysis_id,
run_time_parameters=run_time_parameters,
)
task_runner.run(
protocol_analyzer.analyze,
protocol_resource=protocol_resource,
analysis_id=analysis_id,
run_time_parameters=run_time_parameters,
)

data = Protocol(
id=protocol_id,
createdAt=created_at,
protocolType=source.config.protocol_type,
robotType=source.robot_type,
metadata=Metadata.parse_obj(source.metadata),
analysisSummaries=[pending_analysis],
analysisSummaries=analysis_store.get_summaries_by_protocol(protocol_id),
key=key,
files=[ProtocolFile(name=f.path.name, role=f.role) for f in source.files],
)
Expand Down Expand Up @@ -373,19 +391,39 @@ async def _start_new_analysis_if_necessary(
analysis_summary=analyses[-1], new_rtp_values=rtp_values
)
):
task_runner.run(
protocol_analyzer.analyze,
protocol_resource=resource,
analysis_id=analysis_id,
run_time_param_values=rtp_values,
)
started_new_analysis = True
analyses.append(
try:
run_time_parameters = await protocol_analyzer.load_runner(
protocol_resource=resource,
run_time_param_values=rtp_values,
)
except BaseException as error:
analysis_store.add_pending(
protocol_id=protocol_id,
analysis_id=analysis_id,
run_time_parameters=[],
)
)
await protocol_analyzer.update_to_failed_analysis(
analysis_id=analysis_id,
protocol_robot_type=resource.source.robot_type,
error=error,
run_time_parameters=[],
)
else:
analyses.append(
analysis_store.add_pending(
protocol_id=protocol_id,
analysis_id=analysis_id,
run_time_parameters=run_time_parameters,
)
)
task_runner.run(
protocol_analyzer.analyze,
protocol_resource=resource,
analysis_id=analysis_id,
run_time_parameters=run_time_parameters,
)

return analyses, started_new_analysis


Expand Down
2 changes: 0 additions & 2 deletions robot-server/tests/protocols/test_protocol_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ async def test_analyze(
await subject.analyze(
protocol_resource=protocol_resource,
analysis_id="analysis-id",
run_time_param_values=None,
)

decoy.verify(
Expand Down Expand Up @@ -246,7 +245,6 @@ async def test_analyze_updates_pending_on_error(
await subject.analyze(
protocol_resource=protocol_resource,
analysis_id="analysis-id",
run_time_param_values=None,
)

decoy.verify(
Expand Down

0 comments on commit 19e5c5b

Please sign in to comment.