Skip to content

Commit

Permalink
Merge branch 'chore_release-8.0.0' into edge
Browse files Browse the repository at this point in the history
  • Loading branch information
mjhuff committed Aug 13, 2024
2 parents b62199a + ffd1c16 commit 40515e6
Show file tree
Hide file tree
Showing 112 changed files with 1,534 additions and 1,033 deletions.
2 changes: 1 addition & 1 deletion api-client/src/runs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export interface LegacyGoodRunData {
modules: LoadedModule[]
protocolId?: string
labwareOffsets?: LabwareOffset[]
runTimeParameters: RunTimeParameter[]
}

export interface KnownGoodRunData extends LegacyGoodRunData {
ok: true
runTimeParameters: RunTimeParameter[]
}

export interface KnownInvalidRunData extends LegacyGoodRunData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ async def _status_bar_software_error(self) -> None:
if self._enabled:
await self._controller.static_color(status_bar.YELLOW)

async def _status_bar_error_recovery(self) -> None:
self._status_bar_state = StatusBarState.ERROR_RECOVERY
if self._enabled:
await self._controller.pulse_color(status_bar.YELLOW)

async def _status_bar_confirm(self) -> None:
# Confirm should revert to IDLE
self._status_bar_state = StatusBarState.IDLE
Expand Down Expand Up @@ -163,6 +168,7 @@ async def set_status_bar_state(self, state: StatusBarState) -> None:
StatusBarState.PAUSED: self._status_bar_paused,
StatusBarState.HARDWARE_ERROR: self._status_bar_hardware_error,
StatusBarState.SOFTWARE_ERROR: self._status_bar_software_error,
StatusBarState.ERROR_RECOVERY: self._status_bar_error_recovery,
StatusBarState.CONFIRMATION: self._status_bar_confirm,
StatusBarState.RUN_COMPLETED: self._status_bar_run_complete,
StatusBarState.UPDATING: self._status_bar_updating,
Expand Down
15 changes: 12 additions & 3 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2629,7 +2629,7 @@ async def _liquid_probe_pass(
force_both_sensors: bool = False,
) -> float:
plunger_direction = -1 if probe_settings.aspirate_while_sensing else 1
await self._backend.liquid_probe(
end_z = await self._backend.liquid_probe(
mount,
p_travel,
probe_settings.mount_speed,
Expand All @@ -2641,8 +2641,17 @@ async def _liquid_probe_pass(
probe=probe,
force_both_sensors=force_both_sensors,
)
end_pos = await self.gantry_position(mount, refresh=True)
return end_pos.z
machine_pos = await self._backend.update_position()
machine_pos[Axis.by_mount(mount)] = end_z
deck_end_z = self._deck_from_machine(machine_pos)[Axis.by_mount(mount)]
offset = offset_for_mount(
mount,
top_types.Point(*self._config.left_mount_offset),
top_types.Point(*self._config.right_mount_offset),
top_types.Point(*self._config.gripper_mount_offset),
)
cp = self.critical_point_for(mount, None)
return deck_end_z + offset.z + cp.z

async def liquid_probe(
self,
Expand Down
28 changes: 17 additions & 11 deletions api/src/opentrons/hardware_control/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,17 +572,23 @@ class PauseType(enum.Enum):


class StatusBarState(enum.Enum):
IDLE = 0
RUNNING = 1
PAUSED = 2
HARDWARE_ERROR = 3
SOFTWARE_ERROR = 4
CONFIRMATION = 5
RUN_COMPLETED = 6
UPDATING = 7
ACTIVATION = 8
DISCO = 9
OFF = 10
"""Semantic status bar states.
These mostly correspond to cases listed out in the Flex manual.
"""

IDLE = enum.auto()
RUNNING = enum.auto()
PAUSED = enum.auto()
HARDWARE_ERROR = enum.auto()
SOFTWARE_ERROR = enum.auto()
ERROR_RECOVERY = enum.auto()
CONFIRMATION = enum.auto()
RUN_COMPLETED = enum.auto()
UPDATING = enum.auto()
ACTIVATION = enum.auto()
DISCO = enum.auto()
OFF = enum.auto()

def transient(self) -> bool:
return self.value in {
Expand Down
14 changes: 13 additions & 1 deletion api/src/opentrons/protocol_api/_parameter_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from opentrons.protocols.parameters.exceptions import (
ParameterDefinitionError,
ParameterValueError,
IncompatibleParameterError,
)
from opentrons.protocol_engine.types import (
RunTimeParameter,
Expand Down Expand Up @@ -183,6 +184,14 @@ def add_csv_file(
variable_name: The variable name the CSV parameter will be referred to in the run context.
description: A description of the parameter as it will show up on the frontend.
"""
if any(
isinstance(parameter, csv_parameter_definition.CSVParameterDefinition)
for parameter in self._parameters.values()
):
raise IncompatibleParameterError(
"Only one CSV File parameter can be defined per protocol."
)

validation.validate_variable_name_unique(variable_name, set(self._parameters))
parameter = csv_parameter_definition.create_csv_parameter(
display_name=display_name,
Expand Down Expand Up @@ -246,8 +255,11 @@ def initialize_csv_files(
file_id = file_path.parent.name
file_name = file_path.name

with file_path.open("rb") as fh:
contents = fh.read()

parameter.file_info = FileInfo(id=file_id, name=file_name)
parameter.value = file_path
parameter.value = contents

def export_parameters_for_analysis(self) -> List[RunTimeParameter]:
"""Exports all parameters into a protocol engine models for reporting in analysis.
Expand Down
10 changes: 2 additions & 8 deletions api/src/opentrons/protocols/execution/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from opentrons.protocols.api_support.types import APIVersion

from opentrons.protocols.parameters.csv_parameter_interface import CSVParameter
from opentrons.protocols.parameters.exceptions import RuntimeParameterRequired

MODULE_LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -51,13 +50,8 @@ def run_protocol(
finally:
if protocol.api_level >= APIVersion(2, 18):
for parameter in context.params.get_all().values():
if isinstance(parameter, CSVParameter):
try:
parameter.file.close()
# This will be raised if the csv file wasn't set, which means it was never opened,
# so we can safely skip this.
except RuntimeParameterRequired:
pass
if isinstance(parameter, CSVParameter) and parameter.file_opened:
parameter.file.close()
else:
if protocol.contents["schemaVersion"] == 3:
ins = execute_json_v3.load_pipettes_from_json(context, protocol.contents)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""CSV Parameter definition and associated classes/functions."""
from pathlib import Path
from typing import Optional

from opentrons.protocol_engine.types import (
Expand All @@ -14,7 +13,7 @@
from .csv_parameter_interface import CSVParameter


class CSVParameterDefinition(AbstractParameterDefinition[Optional[Path]]):
class CSVParameterDefinition(AbstractParameterDefinition[Optional[bytes]]):
"""The definition for a user defined CSV file parameter."""

def __init__(
Expand All @@ -30,7 +29,7 @@ def __init__(
self._display_name = validation.ensure_display_name(display_name)
self._variable_name = validation.ensure_variable_name(variable_name)
self._description = validation.ensure_description(description)
self._value: Optional[Path] = None
self._value: Optional[bytes] = None
self._file_info: Optional[FileInfo] = None

@property
Expand All @@ -39,13 +38,13 @@ def variable_name(self) -> str:
return self._variable_name

@property
def value(self) -> Optional[Path]:
def value(self) -> Optional[bytes]:
"""The current set file for the CSV parameter. Defaults to None on definition creation."""
return self._value

@value.setter
def value(self, new_path: Path) -> None:
self._value = new_path
def value(self, contents: bytes) -> None:
self._value = contents

@property
def file_info(self) -> Optional[FileInfo]:
Expand All @@ -56,7 +55,7 @@ def file_info(self, file_info: FileInfo) -> None:
self._file_info = file_info

def as_csv_parameter_interface(self, api_version: APIVersion) -> CSVParameter:
return CSVParameter(csv_path=self._value, api_version=api_version)
return CSVParameter(contents=self._value, api_version=api_version)

def as_protocol_engine_type(self) -> RunTimeParameter:
"""Returns CSV parameter as a Protocol Engine type to send to client."""
Expand Down
48 changes: 28 additions & 20 deletions api/src/opentrons/protocols/parameters/csv_parameter_interface.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
import csv
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Optional, TextIO, Any, List

from opentrons.protocols.api_support.types import APIVersion

from . import parameter_file_reader
from .exceptions import ParameterValueError
from .exceptions import ParameterValueError, RuntimeParameterRequired


# TODO(jbl 2024-08-02) This is a public facing class and as such should be moved to the protocol_api folder
class CSVParameter:
def __init__(self, csv_path: Optional[Path], api_version: APIVersion) -> None:
self._path = csv_path
self._file: Optional[TextIO] = None
self._contents: Optional[str] = None
def __init__(self, contents: Optional[bytes], api_version: APIVersion) -> None:
self._contents = contents
self._api_version = api_version
self._file: Optional[TextIO] = None

@property
def file(self) -> TextIO:
"""Returns the file handler for the CSV file."""
if self._file is None:
self._file = parameter_file_reader.open_file_path(self._path)
text = self.contents
temporary_file = NamedTemporaryFile("r+")
temporary_file.write(text)
temporary_file.flush()

# Open a new file handler for the temporary file with read-only permissions and close the other
self._file = open(temporary_file.name, "r")
temporary_file.close()
return self._file

@property
def file_opened(self) -> bool:
"""Return if a file handler has been opened for the CSV parameter."""
return self._file is not None

@property
def contents(self) -> str:
"""Returns the full contents of the CSV file as a single string."""
if self._contents is None:
self.file.seek(0)
self._contents = self.file.read()
return self._contents
raise RuntimeParameterRequired(
"CSV parameter needs to be set to a file for full analysis or run."
)
return self._contents.decode("utf-8")

def parse_as_csv(
self, detect_dialect: bool = True, **kwargs: Any
Expand All @@ -42,23 +53,20 @@ def parse_as_csv(
rows: List[List[str]] = []
if detect_dialect:
try:
self.file.seek(0)
dialect = csv.Sniffer().sniff(self.file.read(1024))
self.file.seek(0)
reader = csv.reader(self.file, dialect, **kwargs)
dialect = csv.Sniffer().sniff(self.contents[:1024])
reader = csv.reader(self.contents.split("\n"), dialect, **kwargs)
except (UnicodeDecodeError, csv.Error):
raise ParameterValueError(
"Cannot parse dialect or contents from provided CSV file."
"Cannot parse dialect or contents from provided CSV contents."
)
else:
try:
reader = csv.reader(self.file, **kwargs)
reader = csv.reader(self.contents.split("\n"), **kwargs)
except (UnicodeDecodeError, csv.Error):
raise ParameterValueError("Cannot parse provided CSV file.")
raise ParameterValueError("Cannot parse provided CSV contents.")
try:
for row in reader:
rows.append(row)
except (UnicodeDecodeError, csv.Error):
raise ParameterValueError("Cannot parse provided CSV file.")
self.file.seek(0)
raise ParameterValueError("Cannot parse provided CSV contents.")
return rows
4 changes: 4 additions & 0 deletions api/src/opentrons/protocols/parameters/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ class ParameterDefinitionError(ValueError):

class ParameterNameError(ValueError):
"""An error raised when a parameter name or description is not valid."""


class IncompatibleParameterError(ValueError):
"""An error raised when a parameter conflicts with another parameter."""
26 changes: 0 additions & 26 deletions api/src/opentrons/protocols/parameters/parameter_file_reader.py

This file was deleted.

3 changes: 1 addition & 2 deletions api/src/opentrons/protocols/parameters/types.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from pathlib import Path
from typing import TypeVar, Union, TypedDict

from .csv_parameter_interface import CSVParameter


PrimitiveAllowedTypes = Union[str, int, float, bool]
AllAllowedTypes = Union[str, int, float, bool, Path, None]
AllAllowedTypes = Union[str, int, float, bool, bytes, None]
UserFacingTypes = Union[str, int, float, bool, CSVParameter]

ParamType = TypeVar("ParamType", bound=AllAllowedTypes)
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocols/parameters/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,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 {value} has type '{type(value).__name__}',"
f"Parameter value {value!r} has type '{type(value).__name__}',"
f" but must be of type '{parameter_type.__name__}'."
)

Expand All @@ -252,7 +252,7 @@ def validate_options(
"""Validate default values and all possible constraints for a valid parameter definition."""
if not isinstance(default, parameter_type):
raise ParameterValueError(
f"Parameter default {default} has type '{type(default).__name__}',"
f"Parameter default {default!r} has type '{type(default).__name__}',"
f" but must be of type '{parameter_type.__name__}'."
)

Expand Down
Loading

0 comments on commit 40515e6

Please sign in to comment.