From 66ddd3569e34985aabee17aa3f7e1b5af128660b Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Tue, 25 Jun 2024 15:27:52 -0400 Subject: [PATCH 1/6] initial implementation --- .../opentrons/protocols/api_support/util.py | 8 +++++-- .../opentrons_shared_data/errors/codes.py | 1 + .../errors/exceptions.py | 24 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/api/src/opentrons/protocols/api_support/util.py b/api/src/opentrons/protocols/api_support/util.py index dbe9e16f7de..3b19f167657 100644 --- a/api/src/opentrons/protocols/api_support/util.py +++ b/api/src/opentrons/protocols/api_support/util.py @@ -22,6 +22,10 @@ from opentrons.hardware_control.types import Axis from opentrons.hardware_control.util import ot2_axis_to_string from opentrons_shared_data.robot.dev_types import RobotType +from opentrons_shared_data.errors.exceptions import ( + APIRemoved, + IncorrectAPIVersion, +) if TYPE_CHECKING: from opentrons.protocol_api.labware import Well, Labware @@ -37,7 +41,7 @@ MODULE_LOG = logging.getLogger(__name__) -class APIVersionError(Exception): +class APIVersionError(IncorrectAPIVersion): """ Error raised when a protocol attempts to access behavior not implemented """ @@ -45,7 +49,7 @@ class APIVersionError(Exception): pass -class UnsupportedAPIError(Exception): +class UnsupportedAPIError(APIRemoved): """Error raised when a protocol attempts to use unsupported API.""" diff --git a/shared-data/python/opentrons_shared_data/errors/codes.py b/shared-data/python/opentrons_shared_data/errors/codes.py index bfd0ecbdbbd..9b767d09733 100644 --- a/shared-data/python/opentrons_shared_data/errors/codes.py +++ b/shared-data/python/opentrons_shared_data/errors/codes.py @@ -92,6 +92,7 @@ class ErrorCodes(Enum): INVALID_STORED_DATA = _code_from_dict_entry("4008") MISSING_CONFIGURATION_DATA = _code_from_dict_entry("4009") RUNTIME_PARAMETER_VALUE_REQUIRED = _code_from_dict_entry("4010") + INCORRECT_API_VERSION = _code_from_dict_entry("4011") @classmethod @lru_cache(25) diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index 43d94e11a0b..9885fe55c46 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -921,6 +921,30 @@ 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( + self, + api_element: str, + until_version: str, + message: Optional[str] = None, + detail: Optional[Dict[str, str]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an IncorrectAPIVersion error.""" + checked_detail: Dict[str, Any] = detail or {} + checked_detail["identifier"] = api_element + checked_detail["until_version"] = until_version + checked_message = ( + message + or f"{api_element} is not available until version {until_version}." + ) + super().__init__( + ErrorCodes.INCORRECT_API_VERSION, checked_message, checked_detail, wrapping + ) + + class CommandPreconditionViolated(GeneralError): """An error indicating that a command was issued in a robot state incompatible with it.""" From 85c5d409466344663628767a99aa99caefbd90a1 Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Thu, 27 Jun 2024 08:55:43 -0400 Subject: [PATCH 2/6] updated IncorrectAPIVersion exception and some but not all APIVersionError instances --- .../core/legacy/legacy_well_core.py | 2 +- api/src/opentrons/protocol_api/deck.py | 5 ++-- .../protocol_api/instrument_context.py | 4 +++- .../opentrons/protocols/api_support/util.py | 7 +++--- shared-data/errors/definitions/1/errors.json | 4 ++++ .../errors/exceptions.py | 23 +++++++++++++------ 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py index 81780f1006a..a88dd2eee80 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py @@ -112,7 +112,7 @@ def load_liquid( volume: float, ) -> None: """Load liquid into a well.""" - raise APIVersionError("Loading a liquid is not supported in this API version.") + raise APIVersionError(api_element="Loading a liquid") def from_center_cartesian(self, x: float, y: float, z: float) -> Point: """Gets point in deck coordinates based on percentage of the radius of each axis.""" diff --git a/api/src/opentrons/protocol_api/deck.py b/api/src/opentrons/protocol_api/deck.py index 338b1bbecf6..b4ebe8ae766 100644 --- a/api/src/opentrons/protocol_api/deck.py +++ b/api/src/opentrons/protocol_api/deck.py @@ -107,8 +107,9 @@ def __delitem__(self, key: DeckLocation) -> None: # * PAPIv2.14 (Protocol Engine): No # * PAPIv2.15 (Protocol Engine): Yes raise APIVersionError( - f"Deleting deck elements is not supported with apiLevel {self._api_version}." - f" Try increasing your apiLevel to {APIVersion(2, 15)}." + api_element="Deleting deck elements", + until_version="2.15", + current_version=f"{self._api_version}", ) slot_name = _get_slot_name( diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 9dd0c482ded..a27cf816de3 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -368,7 +368,9 @@ def dispense( # noqa: C901 """ if self.api_version < APIVersion(2, 15) and push_out: raise APIVersionError( - "Unsupported parameter push_out. Change your API version to 2.15 or above to use this parameter." + api_element="Parameter push_out", + until_version="2.15", + current_version=f"{self.api_version}", ) _log.debug( "dispense {} from {} at {}".format( diff --git a/api/src/opentrons/protocols/api_support/util.py b/api/src/opentrons/protocols/api_support/util.py index b79221ee1fc..e1eb1195b12 100644 --- a/api/src/opentrons/protocols/api_support/util.py +++ b/api/src/opentrons/protocols/api_support/util.py @@ -386,10 +386,9 @@ def _check_version_wrapper(*args: Any, **kwargs: Any) -> Any: name = getattr(decorated_obj, "__qualname__", str(decorated_obj)) raise APIVersionError( - f"{name} was added in {added_in}, but your " - f"protocol requested version {current_version}. You " - f"must increase your API version to {added_in} to " - "use this functionality." + api_element=name, + until_version=added_in, + current_version=current_version, ) return decorated_obj(*args, **kwargs) diff --git a/shared-data/errors/definitions/1/errors.json b/shared-data/errors/definitions/1/errors.json index 1aca5ccf495..cff28ddd064 100644 --- a/shared-data/errors/definitions/1/errors.json +++ b/shared-data/errors/definitions/1/errors.json @@ -249,6 +249,10 @@ "4010": { "detail": "Runtime Parameter Value Required", "category": "generalError" + }, + "4011": { + "detail": "Incorrect API Version", + "category": "generalError" } } } diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index 9885fe55c46..9cc9f7c2928 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -924,10 +924,11 @@ 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( + def __init__( self, - api_element: str, - until_version: str, + api_element: Optional[str] = None, + until_version: Optional[str] = None, + current_version: Optional[str] = None, message: Optional[str] = None, detail: Optional[Dict[str, str]] = None, wrapping: Optional[Sequence[EnumeratedError]] = None, @@ -936,10 +937,18 @@ def __init( checked_detail: Dict[str, Any] = detail or {} checked_detail["identifier"] = api_element checked_detail["until_version"] = until_version - checked_message = ( - message - or f"{api_element} is not available until version {until_version}." - ) + checked_detail["current_version"] = current_version + # make this cleaner?! + 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 available in the API version in use." + elif message: + checked_message = message + else: + checked_message = f"This feature is not available in the API version in use." super().__init__( ErrorCodes.INCORRECT_API_VERSION, checked_message, checked_detail, wrapping ) From 8a7b0fce398f92b5607025fb6bcf9b27b3b11216 Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Mon, 8 Jul 2024 14:23:17 -0400 Subject: [PATCH 3/6] updated APIVersionError instances and some failing tests --- .../protocol_api/core/engine/well.py | 6 +- .../core/legacy/legacy_instrument_core.py | 18 ++---- .../core/legacy/legacy_protocol_core.py | 25 ++++---- .../legacy_instrument_core.py | 16 ++--- .../protocol_api/instrument_context.py | 30 +++++---- api/src/opentrons/protocol_api/labware.py | 64 +++++++++++++------ .../opentrons/protocol_api/module_contexts.py | 46 ++++++++----- .../protocol_api/protocol_context.py | 52 ++++++++++----- api/src/opentrons/protocol_api/validation.py | 11 ++-- .../protocol_api/test_instrument_context.py | 5 +- .../opentrons/protocol_api/test_labware.py | 6 +- .../errors/exceptions.py | 31 +++++---- 12 files changed, 182 insertions(+), 128 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/well.py b/api/src/opentrons/protocol_api/core/engine/well.py index bbf4bc95d3e..6743a8a39c5 100644 --- a/api/src/opentrons/protocol_api/core/engine/well.py +++ b/api/src/opentrons/protocol_api/core/engine/well.py @@ -6,7 +6,7 @@ from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine.clients import SyncClient as EngineClient -from opentrons.protocols.api_support.util import APIVersionError +from opentrons.protocols.api_support.util import UnsupportedAPIError from opentrons.types import Point from . import point_calculations @@ -69,8 +69,8 @@ def has_tip(self) -> bool: def set_has_tip(self, value: bool) -> None: """Set the well as containing or not containing a tip.""" - raise APIVersionError( - "Manually setting the tip state of a well in a tip rack has been deprecated." + raise UnsupportedAPIError( + api_element="Manually setting the tip state of a well in a tip rack", ) def get_display_name(self) -> str: diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index 3755b093e78..24a1731a59e 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -133,10 +133,10 @@ def dispense( """ if isinstance(location, (TrashBin, WasteChute)): raise APIVersionError( - "Dispense in Moveable Trash or Waste Chute are not supported in this API Version." + api_element="Dispense in Moveable Trash or Waste Chute" ) if push_out: - raise APIVersionError("push_out is not supported in this API version.") + raise APIVersionError(api_element="push_out") if not in_place: self.move_to(location=location) @@ -157,7 +157,7 @@ def blow_out( """ if isinstance(location, (TrashBin, WasteChute)): raise APIVersionError( - "Blow Out in Moveable Trash or Waste Chute are not supported in this API Version." + api_element="Blow Out in Moveable Trash or Waste Chute" ) if not in_place: @@ -247,9 +247,7 @@ def drop_tip( home_after: Whether to home the pipette after the tip is dropped. """ if alternate_drop_location: - raise APIVersionError( - "Tip drop randomization is not supported in this API version." - ) + raise APIVersionError(api_element="Tip drop randomization") labware_core = well_core.geometry.parent if location is None: @@ -300,9 +298,7 @@ def drop_tip_in_disposal_location( home_after: Optional[bool], alternate_tip_drop: bool = False, ) -> None: - raise APIVersionError( - "Dropping tips in a trash bin or waste chute is not supported in this API Version." - ) + raise APIVersionError(api_element="Dropping tips in a trash bin or waste chute") def home(self) -> None: """Home the mount""" @@ -340,9 +336,7 @@ def move_to( the computed safe travel height. """ if isinstance(location, (TrashBin, WasteChute)): - raise APIVersionError( - "Move To Trash Bin and Waste Chute are not supported in this API Version." - ) + raise APIVersionError(api_element="Move To Trash Bin and Waste Chute") self.flag_unsafe_move(location) # prevent direct movement bugs in PAPI version >= 2.10 diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py index 9e8912c5629..ae2f7f032ac 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py @@ -138,7 +138,7 @@ def append_disposal_location( ) -> None: if isinstance(disposal_location, (TrashBin, WasteChute)): raise APIVersionError( - "Trash Bin and Waste Chute Disposal locations are not supported in this API Version." + api_element="Trash Bin and Waste Chute Disposal locations" ) self._disposal_locations.append(disposal_location) @@ -173,14 +173,17 @@ def load_labware( """Load a labware using its identifying parameters.""" if isinstance(location, OffDeckType): raise APIVersionError( - "Loading a labware off deck is only supported with apiLevel 2.15 and newer." + api_element="Loading a labware off deck", until_version="2.15" ) elif isinstance(location, LegacyLabwareCore): raise APIVersionError( - "Loading a labware onto another labware or adapter is only supported with api version 2.15 and above" + api_element="Loading a labware onto another labware or adapter", + until_version="2.15", ) elif isinstance(location, StagingSlotName): - raise APIVersionError("Using a staging deck slot requires apiLevel 2.16.") + raise APIVersionError( + api_element="Using a staging deck slot", until_version="2.16" + ) deck_slot = ( location if isinstance(location, DeckSlotName) else location.get_deck_slot() @@ -261,7 +264,7 @@ def load_adapter( version: Optional[int], ) -> LegacyLabwareCore: """Load an adapter using its identifying parameters""" - raise APIVersionError("Loading adapter is not supported in this API version.") + raise APIVersionError(api_element="Loading adapter") # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237 def move_labware( @@ -281,7 +284,7 @@ def move_labware( drop_offset: Optional[Tuple[float, float, float]], ) -> None: """Move labware to new location.""" - raise APIVersionError("Labware movement is not supported in this API version") + raise APIVersionError(api_element="Labware movement") def load_module( self, @@ -382,19 +385,15 @@ def load_instrument( return new_instr def load_trash_bin(self, slot_name: DeckSlotName, area_name: str) -> TrashBin: - raise APIVersionError( - "Loading deck configured trash bin is not supported in this API version." - ) + raise APIVersionError(api_element="Loading deck configured trash bin") def load_ot2_fixed_trash_bin(self) -> None: raise APIVersionError( - "Loading deck configured OT-2 fixed trash bin is not supported in this API version." + api_element="Loading deck configured OT-2 fixed trash bin" ) def load_waste_chute(self) -> WasteChute: - raise APIVersionError( - "Loading waste chute is not supported in this API version." - ) + raise APIVersionError(api_element="Loading waste chute") def get_loaded_instruments( self, diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index ffcdda5019c..bd62dfa8aff 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -133,7 +133,7 @@ def dispense( ) -> None: if isinstance(location, (TrashBin, WasteChute)): raise APIVersionError( - "Dispense in Moveable Trash or Waste Chute are not supported in this API Version." + api_element="Dispense in Moveable Trash or Waste Chute" ) if not in_place: self.move_to(location=location, well_core=well_core) @@ -148,7 +148,7 @@ def blow_out( ) -> None: if isinstance(location, (TrashBin, WasteChute)): raise APIVersionError( - "Blow Out in Moveable Trash or Waste Chute are not supported in this API Version." + api_element="Blow Out in Moveable Trash or Waste Chute" ) if not in_place: self.move_to(location=location, well_core=well_core) @@ -213,9 +213,7 @@ def drop_tip( alternate_drop_location: Optional[bool] = False, ) -> None: if alternate_drop_location: - raise APIVersionError( - "Tip drop alternation is not supported in this API version." - ) + raise APIVersionError(api_element="Tip drop alternation") labware_core = well_core.geometry.parent if location is None: @@ -268,9 +266,7 @@ def drop_tip_in_disposal_location( home_after: Optional[bool], alternate_tip_drop: bool = False, ) -> None: - raise APIVersionError( - "Dropping tips in a trash bin or waste chute is not supported in this API Version." - ) + raise APIVersionError(api_element="Dropping tips in a trash bin or waste chute") def home(self) -> None: self._protocol_interface.set_last_location(None) @@ -288,9 +284,7 @@ def move_to( ) -> None: """Simulation of only the motion planning portion of move_to.""" if isinstance(location, (TrashBin, WasteChute)): - raise APIVersionError( - "Move To Trash Bin and Waste Chute are not supported in this API Version." - ) + raise APIVersionError(api_element="Move To Trash Bin and Waste Chute") self.flag_unsafe_move(location) diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a27cf816de3..c3fdc59a18e 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -26,6 +26,7 @@ clamp_value, requires_version, APIVersionError, + UnsupportedAPIError, ) from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType @@ -889,21 +890,24 @@ def pick_up_tip( # noqa: C901 """ if presses is not None and self._api_version >= _PRESSES_INCREMENT_REMOVED_IN: - raise APIVersionError( - f"presses is only available in API versions lower than {_PRESSES_INCREMENT_REMOVED_IN}," - f" but you are using API {self._api_version}." + raise UnsupportedAPIError( + api_element="presses", + since_version=f"{_PRESSES_INCREMENT_REMOVED_IN}", + current_version=f"{self._api_version}", ) if increment is not None and self._api_version >= _PRESSES_INCREMENT_REMOVED_IN: - raise APIVersionError( - f"increment is only available in API versions lower than {_PRESSES_INCREMENT_REMOVED_IN}," - f" but you are using API {self._api_version}." + raise UnsupportedAPIError( + api_element="increment", + since_version=f"{_PRESSES_INCREMENT_REMOVED_IN}", + current_version=f"{self._api_version}", ) if prep_after is not None and self._api_version < _PREP_AFTER_ADDED_IN: raise APIVersionError( - f"prep_after is only available in API {_PREP_AFTER_ADDED_IN} and newer," - f" but you are using API {self._api_version}." + api_element="prep_after", + until_version=f"{_PREP_AFTER_ADDED_IN}", + current_version=f"{self._api_version}", ) well: labware.Well @@ -1495,9 +1499,8 @@ def delay(self, *args: Any, **kwargs: Any) -> None: # would get a TypeError if they tried to call it like delay(minutes=10). # Without changing the ultimate behavior that such a call fails the # protocol, we can provide a more descriptive message as a courtesy. - raise APIVersionError( - "InstrumentContext.delay() is not supported in Python Protocol API v2." - " Use ProtocolContext.delay() instead." + raise UnsupportedAPIError( + message="InstrumentContext.delay() is not supported in Python Protocol API v2. Use ProtocolContext.delay() instead." ) else: # Former implementations of this method, when called without any args, @@ -1617,9 +1620,8 @@ def speed(self) -> "PlungerSpeeds": :py:attr:`.flow_rate` instead. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "InstrumentContext.speed has been removed." - " Use InstrumentContext.flow_rate, instead." + raise UnsupportedAPIError( + message="InstrumentContext.speed has been removed. Use InstrumentContext.flow_rate, instead." ) # TODO(mc, 2023-02-13): this assert should be enough for mypy diff --git a/api/src/opentrons/protocol_api/labware.py b/api/src/opentrons/protocol_api/labware.py index be6cc442782..6e6932e0cf6 100644 --- a/api/src/opentrons/protocol_api/labware.py +++ b/api/src/opentrons/protocol_api/labware.py @@ -19,7 +19,11 @@ from opentrons.types import Location, Point from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import requires_version, APIVersionError +from opentrons.protocols.api_support.util import ( + requires_version, + APIVersionError, + UnsupportedAPIError, +) from opentrons.hardware_control.nozzle_manager import NozzleMap # TODO(mc, 2022-09-02): re-exports provided for backwards compatibility @@ -126,7 +130,7 @@ def max_volume(self) -> float: def geometry(self) -> WellGeometry: if isinstance(self._core, LegacyWellCore): return self._core.geometry - raise APIVersionError("Well.geometry has been deprecated.") + raise UnsupportedAPIError(api_element="Well.geometry") @property @requires_version(2, 0) @@ -354,7 +358,11 @@ def __init__( @property def separate_calibration(self) -> bool: if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError("Labware.separate_calibration has been removed") + raise UnsupportedAPIError( + api_element="Labware.separate_calibration", + since_version=f"{ENGINE_CORE_API_VERSION}", + current_version=f"{self._api_version}", + ) _log.warning( "Labware.separate_calibrations is a deprecated internal property." @@ -440,7 +448,11 @@ def name(self, new_name: str) -> None: Set the name of labware in `load_labware` instead. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError("Labware.name setter has been deprecated") + raise UnsupportedAPIError( + api_element="Labware.name setter", + since_version=f"{ENGINE_CORE_API_VERSION}", + current_version=f"{self._api_version}", + ) # TODO(mc, 2023-02-06): this assert should be enough for mypy # investigate if upgrading mypy allows the `cast` to be removed @@ -565,10 +577,11 @@ def set_calibration(self, delta: Point) -> None: .. deprecated:: 2.14 """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "Labware.set_calibration() is not supported when apiLevel is 2.14 or higher." - " Use a lower apiLevel" - " or use the Opentrons App's Labware Position Check." + raise UnsupportedAPIError( + 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.", ) self._core.set_calibration(delta) @@ -616,9 +629,10 @@ def set_offset(self, x: float, y: float, z: float) -> None: and self._api_version < SET_OFFSET_RESTORED_API_VERSION ): raise APIVersionError( - "Labware.set_offset() is not supported when apiLevel is 2.14, 2.15, 2.16, or 2.17." - " Use apilevel 2.13 or below, or 2.18 or above to set offset," - " or use the Opentrons App's Labware Position Check." + 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.", ) else: self._core.set_calibration(Point(x=x, y=y, z=z)) @@ -889,7 +903,11 @@ def tip_length(self, length: float) -> None: and/or use the Opentrons App's tip length calibration feature. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError("Labware.tip_length setter has been deprecated") + raise UnsupportedAPIError( + api_element="Labware.tip_length setter", + since_version=f"{ENGINE_CORE_API_VERSION}", + current_version=f"{self._api_version}", + ) # TODO(mc, 2023-02-06): this assert should be enough for mypy # investigate if upgrading mypy allows the `cast` to be removed @@ -952,9 +970,11 @@ def use_tips(self, start_well: Well, num_channels: int = 1) -> None: Modification of tip tracking state outside :py:meth:`.reset` has been deprecated. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "Labware.use_tips has been deprecated." - " To modify tip state, use Labware.reset" + raise UnsupportedAPIError( + 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.", ) assert num_channels > 0, "Bad call to use_tips: num_channels<=0" @@ -996,8 +1016,10 @@ def previous_tip(self, num_tips: int = 1) -> Optional[Well]: This method has been removed. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "Labware.previous_tip is unsupported in this API version." + raise UnsupportedAPIError( + api_element="Labware.previous_tip", + since_version=f"{ENGINE_CORE_API_VERSION}", + current_version=f"{self._api_version}", ) # This logic is the inverse of :py:meth:`next_tip` @@ -1038,9 +1060,11 @@ def return_tips(self, start_well: Well, num_channels: int = 1) -> None: This method has been removed. Use :py:meth:`.reset` instead. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "Labware.return_tips() is unsupported in this API version." - " Use Labware.reset() instead." + raise UnsupportedAPIError( + api_element="Labware.return_tips()", + since_version=f"{ENGINE_CORE_API_VERSION}", + current_version=f"{self._api_version}", + message=" Use Labware.reset() instead.", ) # This logic is the inverse of :py:meth:`use_tips` diff --git a/api/src/opentrons/protocol_api/module_contexts.py b/api/src/opentrons/protocol_api/module_contexts.py index e1a2fce844b..c8947049a96 100644 --- a/api/src/opentrons/protocol_api/module_contexts.py +++ b/api/src/opentrons/protocol_api/module_contexts.py @@ -11,7 +11,11 @@ from opentrons.legacy_commands import module_commands as cmds from opentrons.legacy_commands.publisher import CommandPublisher, publish from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import APIVersionError, requires_version +from opentrons.protocols.api_support.util import ( + APIVersionError, + requires_version, + UnsupportedAPIError, +) from .core.common import ( ProtocolCore, @@ -95,15 +99,16 @@ def load_labware_object(self, labware: Labware) -> Labware: .. deprecated:: 2.14 Use :py:meth:`load_labware` or :py:meth:`load_labware_by_definition`. """ - deprecation_message = ( - "`ModuleContext.load_labware_object` is an internal, deprecated method." - " Use `ModuleContext.load_labware` or `load_labware_by_definition` instead." - ) - if not isinstance(self._core, LegacyModuleCore): - raise APIVersionError(deprecation_message) + raise UnsupportedAPIError( + api_element="`ModuleContext.load_labware_object`", + since_version="2.14", + message=" Use `ModuleContext.load_labware` or `load_labware_by_definition` instead.", + ) - _log.warning(deprecation_message) + _log.warning( + "`ModuleContext.load_labware_object` is an internal, deprecated method. Use `ModuleContext.load_labware` or `load_labware_by_definition` instead." + ) assert ( labware.parent == self._core.geometry @@ -143,7 +148,9 @@ def load_labware( if adapter is not None: if self._api_version < APIVersion(2, 15): raise APIVersionError( - "Loading a labware on an adapter requires apiLevel 2.15 or higher." + api_element="Loading a labware on an adapter", + until_version="2.15", + current_version=f"{self._api_version}", ) loaded_adapter = self.load_adapter( name=adapter, @@ -295,9 +302,10 @@ def geometry(self) -> LegacyModuleGeometry: if isinstance(self._core, LegacyModuleCore): return self._core.geometry - raise APIVersionError( - "`ModuleContext.geometry` has been deprecated;" - " use properties of the `ModuleContext` itself, instead." + raise UnsupportedAPIError( + api_element="`ModuleContext.geometry`", + since_version="2.14", + message=" Use properties of the `ModuleContext` itself.", ) def __repr__(self) -> str: @@ -426,7 +434,10 @@ def calibrate(self) -> None: ) self._core._sync_module_hardware.calibrate() # type: ignore[attr-defined] else: - raise APIVersionError("`MagneticModuleContext.calibrate` has been removed.") + raise UnsupportedAPIError( + api_element="`MagneticModuleContext.calibrate`", + since_version="2.14", + ) @publish(command=cmds.magdeck_engage) @requires_version(2, 0) @@ -467,10 +478,11 @@ def engage( """ if height is not None: if self._api_version >= _MAGNETIC_MODULE_HEIGHT_PARAM_REMOVED_IN: - raise APIVersionError( - f"The height parameter of MagneticModuleContext.engage() was removed" - f" in {_MAGNETIC_MODULE_HEIGHT_PARAM_REMOVED_IN}." - f" Use offset or height_from_base instead." + raise UnsupportedAPIError( + 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.", ) self._core.engage(height_from_home=height) diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 82d5f73fbd6..f80951a139e 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -39,6 +39,7 @@ requires_version, APIVersionError, RobotTypeError, + UnsupportedAPIError, ) from ._types import OffDeckType @@ -271,10 +272,11 @@ def max_speeds(self) -> AxisMaxSpeeds: if self._api_version >= ENGINE_CORE_API_VERSION: # TODO(mc, 2023-02-23): per-axis max speeds not yet supported on the engine # See https://opentrons.atlassian.net/browse/RCORE-373 - raise APIVersionError( - "ProtocolContext.max_speeds is not supported at apiLevel 2.14 or higher." - " Use a lower apiLevel or set speeds using InstrumentContext.default_speed" - " or the per-method 'speed' argument." + raise UnsupportedAPIError( + 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.", ) return self._core.get_max_speeds() @@ -421,7 +423,9 @@ def load_labware( """ if isinstance(location, OffDeckType) and self._api_version < APIVersion(2, 15): raise APIVersionError( - "Loading a labware off-deck requires apiLevel 2.15 or higher." + api_element="Loading a labware off-deck", + until_version="2.15", + current_version=f"{self._api_version}", ) load_name = validation.ensure_lowercase_name(load_name) @@ -429,7 +433,9 @@ def load_labware( if adapter is not None: if self._api_version < APIVersion(2, 15): raise APIVersionError( - "Loading a labware on an adapter requires apiLevel 2.15 or higher." + api_element="Loading a labware on an adapter", + until_version="2.15", + current_version=f"{self._api_version}", ) loaded_adapter = self.load_adapter( load_name=adapter, @@ -798,12 +804,15 @@ def load_module( if configuration: if self._api_version < APIVersion(2, 4): raise APIVersionError( - f"You have specified API {self._api_version}, but you are" - "using Thermocycler parameters only available in 2.4" + api_element="Thermocycler parameters", + until_version="2.4", + current_version=f"{self._api_version}", ) if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "The configuration parameter of load_module has been removed." + raise UnsupportedAPIError( + api_element="The configuration parameter of load_module", + since_version=f"{ENGINE_CORE_API_VERSION}", + current_version=f"{self._api_version}", ) requested_model = validation.ensure_module_model(module_name) @@ -811,7 +820,9 @@ def load_module( requested_model, MagneticBlockModel ) and self._api_version < APIVersion(2, 15): raise APIVersionError( - f"Module of type {module_name} is only available in versions 2.15 and above." + api_element=f"Module of type {module_name}", + until_version="2.15", + current_version=f"{self._api_version}", ) deck_slot = ( @@ -936,7 +947,9 @@ def load_instrument( and liquid_presence_detection is not None ): raise APIVersionError( - "Liquid Presence Detection is only supported in API Version 2.20 and above." + api_element="Liquid Presence Detection", + until_version="2.20", + current_version=f"{self._api_version}", ) if ( self._core.robot_type != "OT-3 Standard" @@ -1026,9 +1039,11 @@ def resume(self) -> None: after a period of time, use :py:meth:`delay`. """ if self._api_version >= ENGINE_CORE_API_VERSION: - raise APIVersionError( - "A Python Protocol cannot safely resume itself after a pause." - " To wait automatically for a period of time, use ProtocolContext.delay()." + raise UnsupportedAPIError( + 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().", ) # TODO(mc, 2023-02-13): this assert should be enough for mypy @@ -1145,8 +1160,11 @@ def fixed_trash(self) -> Union[Labware, TrashBin]: """ if self._api_version >= APIVersion(2, 16): if self._core.robot_type == "OT-3 Standard": - raise APIVersionError( - "Fixed Trash is not supported on Flex protocols in API Version 2.16 and above." + raise UnsupportedAPIError( + api_element="Fixed Trash", + since_version="2.16", + current_version=f"{self._api_version}", + message=" Fixed trash is no longer supported on Flex protocols.", ) disposal_locations = self._core.get_disposal_locations() if len(disposal_locations) == 0: diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index e1c1902023f..62e5ecc3dc1 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -189,7 +189,9 @@ def ensure_and_convert_deck_slot( if str(deck_slot).upper() in {"A4", "B4", "C4", "D4"}: if api_version < APIVersion(2, 16): raise APIVersionError( - f"Using a staging deck slot requires apiLevel {_STAGING_DECK_SLOT_VERSION_GATE}." + api_element="Using a staging deck slot", + until_version=f"{_STAGING_DECK_SLOT_VERSION_GATE}", + current_version=f"{api_version}", ) # Don't need a try/except since we're already pre-validating this parsed_staging_slot = StagingSlotName.from_primitive(str(deck_slot)) @@ -203,9 +205,10 @@ def ensure_and_convert_deck_slot( if not is_ot2_style and api_version < _COORDINATE_DECK_LABEL_VERSION_GATE: alternative = parsed_slot.to_ot2_equivalent().id raise APIVersionError( - f'Specifying a deck slot like "{deck_slot}" requires apiLevel' - f" {_COORDINATE_DECK_LABEL_VERSION_GATE}." - f' Increase your protocol\'s apiLevel, or use slot "{alternative}" instead.' + 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.", ) return parsed_slot.to_equivalent_for_robot_type(robot_type) diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 43fbd62e09d..9d27555590d 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -14,6 +14,7 @@ from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import ( APIVersionError, + UnsupportedAPIError, FlowRates, PlungerSpeeds, ) @@ -265,7 +266,7 @@ def test_pick_up_from_well_deprecated_args( """It should pick up a specific tip.""" mock_well = decoy.mock(cls=Well) - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.pick_up_tip(mock_well, presses=1, increment=2.0, prep_after=False) @@ -1081,7 +1082,7 @@ def test_plunger_speed( @pytest.mark.parametrize("api_version", [APIVersion(2, 14)]) def test_plunger_speed_removed(subject: InstrumentContext) -> None: """It should raise an error on PAPI >= v2.14.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.speed diff --git a/api/tests/opentrons/protocol_api/test_labware.py b/api/tests/opentrons/protocol_api/test_labware.py index b9b008e77a1..dcf39fe300e 100644 --- a/api/tests/opentrons/protocol_api/test_labware.py +++ b/api/tests/opentrons/protocol_api/test_labware.py @@ -8,7 +8,7 @@ from opentrons_shared_data.labware.dev_types import LabwareDefinition as LabwareDefDict from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import APIVersionError +from opentrons.protocols.api_support.util import UnsupportedAPIError from opentrons.protocol_api import MAX_SUPPORTED_VERSION, Labware, Well from opentrons.protocol_api.core import well_grid from opentrons.protocol_api.core.common import ( @@ -342,7 +342,7 @@ def test_set_offset_raises_on_intermediate_api_version( mock_labware_core: LabwareCore, ) -> None: """It should raise an error, on high API versions.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.set_offset(1, 2, 3) @@ -362,5 +362,5 @@ def test_separate_calibration_raises_on_high_api_version( mock_labware_core: LabwareCore, ) -> None: """It should raise an error, on high API versions.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.separate_calibration diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index 9cc9f7c2928..8161150a491 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -902,8 +902,9 @@ class APIRemoved(GeneralError): def __init__( self, - api_element: str, - since_version: str, + api_element: Optional[str] = None, + since_version: Optional[str] = None, + current_version: Optional[str] = None, message: Optional[str] = None, detail: Optional[Dict[str, str]] = None, wrapping: Optional[Sequence[EnumeratedError]] = None, @@ -912,15 +913,23 @@ def __init__( checked_detail: Dict[str, Any] = detail or {} checked_detail["identifier"] = api_element checked_detail["since_version"] = since_version - checked_message = ( - message - or f"{api_element} is no longer available 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 f"This feature is no longer available in the API version in use." super().__init__( ErrorCodes.API_REMOVED, checked_message, checked_detail, wrapping ) +# write tests class IncorrectAPIVersion(GeneralError): """An error indicating that a command was issued that is not supported by the API version in use.""" @@ -938,17 +947,15 @@ def __init__( checked_detail["identifier"] = api_element checked_detail["until_version"] = until_version checked_detail["current_version"] = current_version - # make this cleaner?! 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 available in the API version in use." - elif message: - checked_message = message - else: - checked_message = f"This feature is not available in the API version in use." + 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 f"This feature is not yet available in the API version in use." super().__init__( ErrorCodes.INCORRECT_API_VERSION, checked_message, checked_detail, wrapping ) From 98f9ca04d811f73bf34cff7201ea805f70cdfaca Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Tue, 9 Jul 2024 13:29:14 -0400 Subject: [PATCH 4/6] test fixes --- .../protocol_api/protocol_context.py | 2 +- .../core/engine/test_well_core.py | 4 ++-- .../opentrons/protocol_api/test_labware.py | 4 ++-- .../test_magnetic_module_context.py | 8 ++++---- .../protocol_api/test_protocol_context.py | 10 +++++++--- .../opentrons/protocol_api/test_validation.py | 5 ++++- .../protocol_api_integration/test_trashes.py | 20 +++++++++++++------ 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index f80951a139e..ad96e0c3156 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -974,7 +974,7 @@ def load_instrument( trash: Optional[Union[Labware, TrashBin]] try: trash = self.fixed_trash - except (NoTrashDefinedError, APIVersionError): + except (NoTrashDefinedError, UnsupportedAPIError): trash = None instrument = InstrumentContext( diff --git a/api/tests/opentrons/protocol_api/core/engine/test_well_core.py b/api/tests/opentrons/protocol_api/core/engine/test_well_core.py index 5d2d5f4914b..31b562f7e81 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_well_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_well_core.py @@ -11,7 +11,7 @@ from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import APIVersionError +from opentrons.protocols.api_support.util import UnsupportedAPIError from opentrons.types import Point from opentrons.protocol_api._liquid import Liquid @@ -164,7 +164,7 @@ def test_has_tip( def test_set_has_tip(subject: WellCore) -> None: """Trying to set the has tip state should raise an error.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.set_has_tip(True) diff --git a/api/tests/opentrons/protocol_api/test_labware.py b/api/tests/opentrons/protocol_api/test_labware.py index dcf39fe300e..bfbbb7b33a7 100644 --- a/api/tests/opentrons/protocol_api/test_labware.py +++ b/api/tests/opentrons/protocol_api/test_labware.py @@ -8,7 +8,7 @@ from opentrons_shared_data.labware.dev_types import LabwareDefinition as LabwareDefDict from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import UnsupportedAPIError +from opentrons.protocols.api_support.util import APIVersionError, UnsupportedAPIError from opentrons.protocol_api import MAX_SUPPORTED_VERSION, Labware, Well from opentrons.protocol_api.core import well_grid from opentrons.protocol_api.core.common import ( @@ -342,7 +342,7 @@ def test_set_offset_raises_on_intermediate_api_version( mock_labware_core: LabwareCore, ) -> None: """It should raise an error, on high API versions.""" - with pytest.raises(UnsupportedAPIError): + with pytest.raises(APIVersionError): subject.set_offset(1, 2, 3) diff --git a/api/tests/opentrons/protocol_api/test_magnetic_module_context.py b/api/tests/opentrons/protocol_api/test_magnetic_module_context.py index 6435d5d8787..db980dced99 100644 --- a/api/tests/opentrons/protocol_api/test_magnetic_module_context.py +++ b/api/tests/opentrons/protocol_api/test_magnetic_module_context.py @@ -5,7 +5,7 @@ from opentrons.legacy_broker import LegacyBroker from opentrons.hardware_control.modules import MagneticStatus from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import APIVersionError +from opentrons.protocols.api_support.util import UnsupportedAPIError from opentrons.protocol_api import MAX_SUPPORTED_VERSION, MagneticModuleContext from opentrons.protocol_api.core.common import ProtocolCore, MagneticModuleCore from opentrons.protocol_api.core.core_map import LoadedCoreMap @@ -117,9 +117,9 @@ def test_engage_height_from_home_raises_on_high_version( subject: MagneticModuleContext, ) -> None: """It should error if given a raw motor height and the apiLevel is high.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.engage(height=42.0) - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.engage(42.0) @@ -130,7 +130,7 @@ def test_calibrate_raises_on_high_version( subject: MagneticModuleContext, ) -> None: """It should raise a deprecation error.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.calibrate() diff --git a/api/tests/opentrons/protocol_api/test_protocol_context.py b/api/tests/opentrons/protocol_api/test_protocol_context.py index 7f00bfb9ee8..a72ed7f8856 100644 --- a/api/tests/opentrons/protocol_api/test_protocol_context.py +++ b/api/tests/opentrons/protocol_api/test_protocol_context.py @@ -14,7 +14,11 @@ from opentrons.hardware_control.modules.types import ModuleType, TemperatureModuleModel from opentrons.protocols.api_support import instrument as mock_instrument_support from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import APIVersionError, RobotTypeError +from opentrons.protocols.api_support.util import ( + APIVersionError, + RobotTypeError, + UnsupportedAPIError, +) from opentrons.protocol_api import ( MAX_SUPPORTED_VERSION, ProtocolContext, @@ -1070,7 +1074,7 @@ def test_load_module_default_location( @pytest.mark.parametrize("api_version", [APIVersion(2, 14)]) def test_load_module_with_configuration(subject: ProtocolContext) -> None: """It should raise an APIVersionError if the deprecated `configuration` argument is used.""" - with pytest.raises(APIVersionError, match="removed"): + with pytest.raises(UnsupportedAPIError): subject.load_module( module_name="spline reticulator", location=42, @@ -1081,7 +1085,7 @@ def test_load_module_with_configuration(subject: ProtocolContext) -> None: @pytest.mark.parametrize("api_version", [APIVersion(2, 14)]) def test_load_module_with_mag_block_raises(subject: ProtocolContext) -> None: """It should raise an APIVersionError if loading a magnetic block.""" - with pytest.raises(APIVersionError): + with pytest.raises(UnsupportedAPIError): subject.load_module( module_name="magneticBlockV1", location=42, diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 667349f0f8d..b06e28e0785 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -4,6 +4,7 @@ from decoy import Decoy import pytest +import re from opentrons_shared_data.labware.labware_definition import ( LabwareRole, @@ -188,7 +189,9 @@ def test_ensure_and_convert_deck_slot( "A1", APIVersion(2, 0), APIVersionError, - '"A1" requires apiLevel 2.15. Increase your protocol\'s apiLevel, or use slot "10" instead.', + re.escape( + "Error 4011 INCORRECT_API_VERSION (APIVersionError): Specifying a deck slot like 'A1' is not available until API version 2.15. You are currently using API version 2.0. Increase your protocol's apiLevel, or use slot '10' instead." + ), ), ("A4", APIVersion(2, 15), APIVersionError, "Using a staging deck slot"), ], diff --git a/api/tests/opentrons/protocol_api_integration/test_trashes.py b/api/tests/opentrons/protocol_api_integration/test_trashes.py index 1c8250fe44e..18dfa62170d 100644 --- a/api/tests/opentrons/protocol_api_integration/test_trashes.py +++ b/api/tests/opentrons/protocol_api_integration/test_trashes.py @@ -3,10 +3,12 @@ from opentrons import protocol_api, simulate from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocols.api_support.util import UnsupportedAPIError import contextlib from typing import ContextManager, Optional, Type from typing_extensions import Literal +import re import pytest @@ -55,8 +57,10 @@ def test_fixed_trash_presence( if expected_trash_class is None: with pytest.raises( - Exception, - match="Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.", + UnsupportedAPIError, + match=re.escape( + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + ), ): protocol.fixed_trash with pytest.raises(Exception, match="No trash container has been defined"): @@ -75,8 +79,10 @@ def test_trash_search() -> None: # By default, there should be no trash. with pytest.raises( - Exception, - match="Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.", + UnsupportedAPIError, + match=re.escape( + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + ), ): protocol.fixed_trash with pytest.raises(Exception, match="No trash container has been defined"): @@ -87,8 +93,10 @@ def test_trash_search() -> None: # After loading some trashes, there should still be no protocol.fixed_trash... with pytest.raises( - Exception, - match="Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.", + UnsupportedAPIError, + match=re.escape( + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + ), ): protocol.fixed_trash # ...but instrument.trash_container should automatically update to point to From 731450da5578722903d85626ad1f3d79542df9a5 Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Tue, 9 Jul 2024 13:59:18 -0400 Subject: [PATCH 5/6] lint fix --- .../errors/exceptions.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index 8161150a491..ee7d8857270 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -903,7 +903,7 @@ class APIRemoved(GeneralError): def __init__( self, api_element: Optional[str] = None, - since_version: Optional[str] = None, + since_version: Optional[str] = None, current_version: Optional[str] = None, message: Optional[str] = None, detail: Optional[Dict[str, str]] = None, @@ -918,12 +918,19 @@ def __init__( 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}." + 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." + 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 f"This feature is no longer available in the API version in use." + checked_message = ( + checked_message + or "This feature is no longer available in the API version in use." + ) super().__init__( ErrorCodes.API_REMOVED, checked_message, checked_detail, wrapping ) @@ -950,12 +957,19 @@ def __init__( 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}." + 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." + 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 f"This feature is not yet available in the API version in use." + checked_message = ( + checked_message + or "This feature is not yet available in the API version in use." + ) super().__init__( ErrorCodes.INCORRECT_API_VERSION, checked_message, checked_detail, wrapping ) From f582b1e45eade180f99bc124dd6a85f364f55169 Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Tue, 9 Jul 2024 14:12:19 -0400 Subject: [PATCH 6/6] cleaned up comment --- shared-data/python/opentrons_shared_data/errors/exceptions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index ee7d8857270..888dc7f6763 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -936,7 +936,6 @@ def __init__( ) -# write tests class IncorrectAPIVersion(GeneralError): """An error indicating that a command was issued that is not supported by the API version in use."""