Skip to content

Commit

Permalink
fix(api): properly handle non-labware jsons in sim
Browse files Browse the repository at this point in the history
opentrons_simulate tries to see if any json file it can reach is a
labware and load it if so. We recently changed the exceptions we raise
in verify_labware_definitions if that call fails, so now instead of
ignoring the file on exception opentrons_simulate raises the error
through and terminates. No good!

Change the exception to something a little saner and make sure we can
actually ignore invalid labware files in sim.

Closes RQA-3820
  • Loading branch information
sfoster1 committed Jan 7, 2025
1 parent c45938d commit 0d95008
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 14 deletions.
45 changes: 37 additions & 8 deletions api/src/opentrons/protocols/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import os
from pathlib import Path
from typing import Any, AnyStr, Dict, Optional, Union, List
from typing import Any, AnyStr, Dict, Optional, Union, List, Sequence, Literal

import jsonschema # type: ignore

Expand All @@ -17,10 +17,30 @@
USER_DEFS_PATH,
)
from opentrons_shared_data.labware.types import LabwareDefinition
from opentrons_shared_data.errors.exceptions import InvalidProtocolData


MODULE_LOG = logging.getLogger(__name__)

LabwareProblem = Literal[
"no-schema-id", "bad-schema-id", "schema-mismatch", "invalid-json"
]


class NotALabwareError(InvalidProtocolData):
def __init__(
self, problem: LabwareProblem, wrapping: Sequence[BaseException]
) -> None:
messages: dict[LabwareProblem, str] = {
"no-schema-id": "No schema ID present in file",
"bad-schema-id": "Bad schema ID in file",
"invalid-json": "File does not contain valid JSON",
"schema-mismatch": "File does not match labware schema",
}
super().__init__(
message=messages[problem], detail={"kind": problem}, wrapping=wrapping
)


def get_labware_definition(
load_name: str,
Expand Down Expand Up @@ -126,7 +146,7 @@ def save_definition(
json.dump(labware_def, f)


def verify_definition(
def verify_definition( # noqa: C901
contents: Union[AnyStr, LabwareDefinition, Dict[str, Any]]
) -> LabwareDefinition:
"""Verify that an input string is a labware definition and return it.
Expand All @@ -146,15 +166,24 @@ def verify_definition(
if isinstance(contents, dict):
to_return = contents
else:
to_return = json.loads(contents)
try:
to_return = json.loads(contents)
except json.JSONDecodeError as e:
raise NotALabwareError("invalid-json", [e])
try:
schema_version = to_return["schemaVersion"]
except KeyError as e:
raise NotALabwareError("no-schema-id", [e])

try:
schema = schemata_by_version[schema_version]
except KeyError:
raise RuntimeError(
f'Invalid or unknown labware schema version {to_return.get("schemaVersion", None)}'
)
jsonschema.validate(to_return, schema)
except KeyError as e:
raise NotALabwareError("bad-schema-id", [e])

try:
jsonschema.validate(to_return, schema)
except jsonschema.ValidationError as e:
raise NotALabwareError("schema-mismatch", [e])

# we can type ignore this because if it passes the jsonschema it has
# the correct structure
Expand Down
7 changes: 2 additions & 5 deletions api/src/opentrons/util/entrypoint_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from dataclasses import dataclass
import json
import logging
from json import JSONDecodeError
import pathlib
import subprocess
import sys
Expand All @@ -21,8 +20,6 @@
TYPE_CHECKING,
)

from jsonschema import ValidationError # type: ignore

from opentrons.calibration_storage.deck_configuration import (
deserialize_deck_configuration,
)
Expand All @@ -32,7 +29,7 @@
JUPYTER_NOTEBOOK_LABWARE_DIR,
SystemArchitecture,
)
from opentrons.protocol_api import labware
from opentrons.protocols import labware
from opentrons.calibration_storage import helpers
from opentrons.protocol_engine.errors.error_occurrence import (
ErrorOccurrence as ProtocolEngineErrorOccurrence,
Expand Down Expand Up @@ -79,7 +76,7 @@ def labware_from_paths(
if child.is_file() and child.suffix.endswith("json"):
try:
defn = labware.verify_definition(child.read_bytes())
except (ValidationError, JSONDecodeError):
except labware.NotALabwareError:
log.info(f"{child}: invalid labware, ignoring")
log.debug(
f"{child}: labware invalid because of this exception.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Exception hierarchy for error codes."""

from typing import Dict, Any, Optional, List, Iterator, Union, Sequence, overload
from logging import getLogger
from traceback import format_exception_only, format_tb
Expand Down Expand Up @@ -1099,7 +1100,7 @@ def __init__(
self,
message: Optional[str] = None,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
wrapping: Optional[Sequence[Union[EnumeratedError, BaseException]]] = None,
) -> None:
"""Build an InvalidProtocolData."""
super().__init__(ErrorCodes.INVALID_PROTOCOL_DATA, message, detail, wrapping)
Expand Down

0 comments on commit 0d95008

Please sign in to comment.