Skip to content

Commit

Permalink
fix(api): Fix malformed PAPI error message (#16065)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxColoring authored Aug 20, 2024
1 parent d7ced75 commit d05dc2e
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 53 deletions.
8 changes: 7 additions & 1 deletion api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2064,6 +2064,8 @@ def configure_nozzle_layout(
NozzleLayout.QUADRANT,
]
if style in disabled_layouts:
# todo(mm, 2024-08-20): UnsupportedAPIError boils down to an API_REMOVED
# error code, which is not correct here.
raise UnsupportedAPIError(
message=f"Nozzle layout configuration of style {style.value} is currently unsupported."
)
Expand All @@ -2074,7 +2076,11 @@ def configure_nozzle_layout(
< _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN
) and (style not in original_enabled_layouts):
raise APIVersionError(
f"Nozzle layout configuration of style {style.value} is unsupported in API Versions lower than {_PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN}."
api_element=f"Nozzle layout configuration of style {style.value}",
until_version=str(
_PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN
),
current_version=str(self._api_version),
)

front_right_resolved = front_right
Expand Down
8 changes: 4 additions & 4 deletions api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ def set_calibration(self, delta: Point) -> None:
api_element="Labware.set_calibration()",
since_version=f"{ENGINE_CORE_API_VERSION}",
current_version=f"{self._api_version}",
message=" Try using the Opentrons App's Labware Position Check.",
extra_message="Try using the Opentrons App's Labware Position Check.",
)
self._core.set_calibration(delta)

Expand Down Expand Up @@ -632,7 +632,7 @@ def set_offset(self, x: float, y: float, z: float) -> None:
api_element="Labware.set_offset()",
until_version=f"{SET_OFFSET_RESTORED_API_VERSION}",
current_version=f"{self._api_version}",
message=" This feature not available in versions 2.14 thorugh 2.17. You can also use the Opentrons App's Labware Position Check.",
extra_message="This feature not available in versions 2.14 thorugh 2.17. You can also use the Opentrons App's Labware Position Check.",
)
else:
self._core.set_calibration(Point(x=x, y=y, z=z))
Expand Down Expand Up @@ -974,7 +974,7 @@ def use_tips(self, start_well: Well, num_channels: int = 1) -> None:
api_element="Labware.use_tips",
since_version=f"{ENGINE_CORE_API_VERSION}",
current_version=f"{self._api_version}",
message=" To modify tip state, use Labware.reset.",
extra_message="To modify tip state, use Labware.reset.",
)

assert num_channels > 0, "Bad call to use_tips: num_channels<=0"
Expand Down Expand Up @@ -1064,7 +1064,7 @@ def return_tips(self, start_well: Well, num_channels: int = 1) -> None:
api_element="Labware.return_tips()",
since_version=f"{ENGINE_CORE_API_VERSION}",
current_version=f"{self._api_version}",
message=" Use Labware.reset() instead.",
extra_message="Use Labware.reset() instead.",
)

# This logic is the inverse of :py:meth:`use_tips`
Expand Down
6 changes: 3 additions & 3 deletions api/src/opentrons/protocol_api/module_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def load_labware_object(self, labware: Labware) -> Labware:
raise UnsupportedAPIError(
api_element="`ModuleContext.load_labware_object`",
since_version="2.14",
message=" Use `ModuleContext.load_labware` or `load_labware_by_definition` instead.",
extra_message="Use `ModuleContext.load_labware` or `load_labware_by_definition` instead.",
)

_log.warning(
Expand Down Expand Up @@ -305,7 +305,7 @@ def geometry(self) -> LegacyModuleGeometry:
raise UnsupportedAPIError(
api_element="`ModuleContext.geometry`",
since_version="2.14",
message=" Use properties of the `ModuleContext` itself.",
extra_message="Use properties of the `ModuleContext` itself.",
)

def __repr__(self) -> str:
Expand Down Expand Up @@ -482,7 +482,7 @@ def engage(
api_element="The height parameter of MagneticModuleContext.engage()",
since_version=f"{_MAGNETIC_MODULE_HEIGHT_PARAM_REMOVED_IN}",
current_version=f"{self._api_version}",
message=" Use offset or height_from_base.",
extra_message="Use offset or height_from_base.",
)
self._core.engage(height_from_home=height)

Expand Down
10 changes: 5 additions & 5 deletions api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def max_speeds(self) -> AxisMaxSpeeds:
api_element="ProtocolContext.max_speeds",
since_version=f"{ENGINE_CORE_API_VERSION}",
current_version=f"{self._api_version}",
message=" Set speeds using InstrumentContext.default_speed or the per-method 'speed' argument.",
extra_message="Set speeds using InstrumentContext.default_speed or the per-method 'speed' argument.",
)

return self._core.get_max_speeds()
Expand Down Expand Up @@ -1048,7 +1048,7 @@ def resume(self) -> None:
api_element="A Python Protocol safely resuming itself after a pause",
since_version=f"{ENGINE_CORE_API_VERSION}",
current_version=f"{self._api_version}",
message=" To wait automatically for a period of time, use ProtocolContext.delay().",
extra_message="To wait automatically for a period of time, use ProtocolContext.delay().",
)

# TODO(mc, 2023-02-13): this assert should be enough for mypy
Expand Down Expand Up @@ -1169,7 +1169,7 @@ def fixed_trash(self) -> Union[Labware, TrashBin]:
api_element="Fixed Trash",
since_version="2.16",
current_version=f"{self._api_version}",
message=" Fixed trash is no longer supported on Flex protocols.",
extra_message="Fixed trash is no longer supported on Flex protocols.",
)
disposal_locations = self._core.get_disposal_locations()
if len(disposal_locations) == 0:
Expand Down Expand Up @@ -1242,7 +1242,7 @@ def define_liquid(
api_element="Calling `define_liquid()` without a `description`",
current_version=str(self._api_version),
until_version=str(desc_and_display_color_omittable_since),
message="Use a newer API version or explicitly supply `description=None`.",
extra_message="Use a newer API version or explicitly supply `description=None`.",
)
else:
description = None
Expand All @@ -1252,7 +1252,7 @@ def define_liquid(
api_element="Calling `define_liquid()` without a `display_color`",
current_version=str(self._api_version),
until_version=str(desc_and_display_color_omittable_since),
message="Use a newer API version or explicitly supply `display_color=None`.",
extra_message="Use a newer API version or explicitly supply `display_color=None`.",
)
else:
display_color = None
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def ensure_and_convert_deck_slot(
api_element=f"Specifying a deck slot like '{deck_slot}'",
until_version=f"{_COORDINATE_DECK_LABEL_VERSION_GATE}",
current_version=f"{api_version}",
message=f"Increase your protocol's apiLevel, or use slot '{alternative}' instead.",
extra_message=f"Increase your protocol's apiLevel, or use slot '{alternative}' instead.",
)

return parsed_slot.to_equivalent_for_robot_type(robot_type)
Expand Down
6 changes: 3 additions & 3 deletions robot-server/robot_server/service/legacy/routers/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ async def post_serial_command(
if requested_version >= 3:
raise LegacyErrorResponse.from_exc(
APIRemoved(
"/modules/{serial}",
"3",
"This endpoint has been removed. Use POST /commands instead.",
api_element="/modules/{serial}",
since_version="3",
extra_message="This endpoint has been removed. Use POST /commands instead.",
),
).as_error(status.HTTP_410_GONE)

Expand Down
131 changes: 95 additions & 36 deletions shared-data/python/opentrons_shared_data/errors/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Exception hierarchy for error codes."""
from typing import Dict, Any, Optional, List, Iterator, Union, Sequence
from typing import Dict, Any, Optional, List, Iterator, Union, Sequence, overload
from logging import getLogger
from traceback import format_exception_only, format_tb
import inspect
Expand Down Expand Up @@ -900,12 +900,37 @@ def __init__(
class APIRemoved(GeneralError):
"""An error indicating that a specific API is no longer available."""

def __init__(
@overload
def __init__( # noqa: D107
self,
*,
api_element: Optional[str] = None,
since_version: Optional[str] = None,
current_version: Optional[str] = None,
extra_message: Optional[str] = None,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
pass

@overload
def __init__( # noqa: D107
self,
message: Optional[str] = None,
*,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
pass

def __init__(
self,
message: Optional[str] = None,
*,
api_element: Optional[str] = None,
since_version: Optional[str] = None,
current_version: Optional[str] = None,
extra_message: Optional[str] = None,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
Expand All @@ -914,23 +939,27 @@ def __init__(
checked_detail["identifier"] = api_element
checked_detail["since_version"] = since_version
checked_detail["current_version"] = current_version
checked_message = ""
if api_element and since_version and current_version:
checked_message = f"{api_element} is not available after API version {since_version}. You are currently using API version {current_version}."
elif api_element and since_version:
checked_message = (
f"{api_element} is not available after API version {since_version}."
)
elif api_element:
checked_message = (
f"{api_element} is no longer available in the API version in use."
)
if message:
checked_message = checked_message + message
checked_message = (
checked_message
or "This feature is no longer available in the API version in use."
)

checked_api_element = api_element if api_element is not None else "This feature"

if message is not None:
checked_message = message
else:
if since_version is not None and current_version is not None:
checked_message = (
f"{checked_api_element} is not available after API version {since_version}."
f" You are currently using API version {current_version}."
)
elif since_version is not None and current_version is None:
checked_message = f"{checked_api_element} is not available after API version {since_version}."
elif since_version is None and current_version is not None:
checked_message = f"{checked_api_element} is not available in API version {current_version}."
else:
checked_message = f"{checked_api_element} is no longer available in the API version in use."

if extra_message is not None:
checked_message += " " + extra_message

super().__init__(
ErrorCodes.API_REMOVED, checked_message, checked_detail, wrapping
)
Expand All @@ -939,12 +968,37 @@ def __init__(
class IncorrectAPIVersion(GeneralError):
"""An error indicating that a command was issued that is not supported by the API version in use."""

def __init__(
@overload
def __init__( # noqa: D107
self,
*,
api_element: Optional[str] = None,
until_version: Optional[str] = None,
current_version: Optional[str] = None,
extra_message: Optional[str] = None,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
pass

@overload
def __init__( # noqa: D107
self,
message: Optional[str] = None,
*,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
pass

def __init__(
self,
message: Optional[str] = None,
*,
api_element: Optional[str] = None,
until_version: Optional[str] = None,
current_version: Optional[str] = None,
extra_message: Optional[str] = None,
detail: Optional[Dict[str, str]] = None,
wrapping: Optional[Sequence[EnumeratedError]] = None,
) -> None:
Expand All @@ -953,22 +1007,27 @@ def __init__(
checked_detail["identifier"] = api_element
checked_detail["until_version"] = until_version
checked_detail["current_version"] = current_version
if api_element and until_version and current_version:
checked_message = f"{api_element} is not available until API version {until_version}. You are currently using API version {current_version}."
elif api_element and until_version:
checked_message = (
f"{api_element} is not available until API version {until_version}."
)
elif api_element:
checked_message = (
f"{api_element} is not yet available in the API version in use."
)
if message:
checked_message = checked_message + " " + message
checked_message = (
checked_message
or "This feature is not yet available in the API version in use."
)

checked_api_element = api_element if api_element is not None else "This feature"

if message is not None:
checked_message = message
else:
if until_version is not None and current_version is not None:
checked_message = (
f"{checked_api_element} is not available until API version {until_version}."
f" You are currently using API version {current_version}."
)
elif until_version is not None and current_version is None:
checked_message = f"{checked_api_element} is not available until API version {until_version}."
elif until_version is None and current_version is not None:
checked_message = f"{checked_api_element} is not available in API version {current_version}."
else:
checked_message = f"{checked_api_element} is not yet available in the API version in use."

if extra_message is not None:
checked_message += " " + extra_message

super().__init__(
ErrorCodes.INCORRECT_API_VERSION, checked_message, checked_detail, wrapping
)
Expand Down

0 comments on commit d05dc2e

Please sign in to comment.