Skip to content

Commit

Permalink
feat(api): support an "experimental" api version
Browse files Browse the repository at this point in the history
Add the ability to specify a protocol's API level as "experimental", and
to specify API calls as experimental.

The experimental API level is greater than any named API level. That
means that
- An api method that is experimental can only be used if the protocol
requests the experimental api level
- This will remain true as the max supported api version changes
  • Loading branch information
sfoster1 committed Mar 4, 2025
1 parent e583471 commit fe7b74b
Showing 3 changed files with 90 additions and 23 deletions.
30 changes: 12 additions & 18 deletions api/src/opentrons/protocols/api_support/definitions.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
from .types import APIVersion

MAX_SUPPORTED_VERSION = APIVersion(2, 23)
"""The maximum supported protocol API version in this release."""

MIN_SUPPORTED_VERSION = APIVersion(2, 0)
"""The minimum supported protocol API version in this release, across all robot types."""

MIN_SUPPORTED_VERSION_FOR_FLEX = APIVersion(2, 15)
"""The minimum protocol API version supported by the Opentrons Flex.
It's an infrastructural requirement for this to be at least newer than 2.14. Before then,
the protocol API is backed by the legacy non-Protocol-engine backend, which is not prepared to
handle anything but OT-2s.
The additional bump to 2.15 is because that's what we tested on, and because it adds all the
Flex-specific features.
"""
from .types import (
MAX_SUPPORTED_VERSION,
MIN_SUPPORTED_VERSION,
MIN_SUPPORTED_VERSION_FOR_FLEX,
)


__all__ = [
"MAX_SUPPORTED_VERSION",
"MIN_SUPPORTED_VERSION",
"MIN_SUPPORTED_VERSION_FOR_FLEX",
]
55 changes: 53 additions & 2 deletions api/src/opentrons/protocols/api_support/types.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,55 @@
from __future__ import annotations
from typing import NamedTuple, TypedDict
from functools import total_ordering


@total_ordering
class APIVersion(NamedTuple):
major: int
minor: int
experimental: bool = False

@classmethod
def from_string(cls, inp: str) -> APIVersion:
if inp == "experimental":
return cls.as_experimental()
parts = inp.split(".")
if len(parts) != 2:
raise ValueError(inp)
intparts = [int(p) for p in parts]

return cls(major=intparts[0], minor=intparts[1])
return cls(major=intparts[0], minor=intparts[1], experimental=False)

@classmethod
def as_experimental(cls) -> APIVersion:
return cls(
major=MAX_SUPPORTED_VERSION.major,
minor=MAX_SUPPORTED_VERSION.minor,
experimental=True,
)

def __str__(self) -> str:
return f"{self.major}.{self.minor}"
return f"{self.major}.{self.minor}" if not self.experimental else "experimental"

def __le__(self, other: tuple[int, int, bool] | tuple[int, int]) -> bool: # type: ignore[override]
# this __le__ supports calling it against a version raw tuple or an API version;
# the type ignore happens because NamedTuple says its parent is tuple[int...] in this
# case, not tuple[int, int, bool]
b_maj = other[0]
b_min = other[1]
b_exp = other[2] if len(other) > 2 else False
# when you do a <= b, interpreter calls a.__le__(b)
if self.experimental and b_exp:
# both a and b are experimental: same version
return True
elif self.experimental:
# a is experimental, and b is not: a > b, a !<= b
return False
elif b_exp:
# a is not experimental, and b is: a < b, a <= b
return True
# both are not experimental, standard version compare
return (self.major, self.minor) < (b_maj, b_min)


class ThermocyclerStepBase(TypedDict):
@@ -30,3 +63,21 @@ class ThermocyclerStep(ThermocyclerStepBase, total=False):

hold_time_seconds: float
hold_time_minutes: float


MAX_SUPPORTED_VERSION = APIVersion(2, 23)
"""The maximum supported protocol API version in this release."""

MIN_SUPPORTED_VERSION = APIVersion(2, 0)
"""The minimum supported protocol API version in this release, across all robot types."""

MIN_SUPPORTED_VERSION_FOR_FLEX = APIVersion(2, 15)
"""The minimum protocol API version supported by the Opentrons Flex.
It's an infrastructural requirement for this to be at least newer than 2.14. Before then,
the protocol API is backed by the legacy non-Protocol-engine backend, which is not prepared to
handle anything but OT-2s.
The additional bump to 2.15 is because that's what we tested on, and because it adds all the
Flex-specific features.
"""
28 changes: 25 additions & 3 deletions api/src/opentrons/protocols/api_support/util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
""" Utility functions and classes for the protocol api """
"""Utility functions and classes for the protocol api"""

from __future__ import annotations

from collections import UserDict
@@ -18,8 +19,11 @@
cast,
KeysView,
ItemsView,
overload,
)

from typing_extensions import Literal

from opentrons import types as top_types
from opentrons.protocols.api_support.types import APIVersion
from opentrons.hardware_control.types import Axis
@@ -303,7 +307,6 @@ def _verify_key(key: object) -> Axis:
return checked_key

def __setitem__(self, key: object, value: object) -> None:

checked_key = AxisMaxSpeeds._verify_key(key)
if value is None:
del self[checked_key]
@@ -353,13 +356,32 @@ def clamp_value(
FuncT = TypeVar("FuncT", bound=Callable[..., Any])


@overload
def requires_version(
major: Literal["experimental"],
) -> Callable[[FuncT], FuncT]:
...


@overload
def requires_version(major: int, minor: int) -> Callable[[FuncT], FuncT]:
...


def requires_version(
major: int | Literal["experimental"], minor: int | None = None
) -> Callable[[FuncT], FuncT]:
"""Decorator. Apply to Protocol API methods or attributes to indicate
the first version in which the method or attribute was present.
"""

def _set_version(decorated_obj: FuncT) -> FuncT:
added_version = APIVersion(major, minor)
if major == "experimental":
added_version = APIVersion.as_experimental()
else:
assert major is not None
assert minor is not None
added_version = APIVersion(major, minor, False)
setattr(decorated_obj, "__opentrons_version_added", added_version)
if hasattr(decorated_obj, "__doc__"):
# Add the versionadded stanza to everything decorated if we can

0 comments on commit fe7b74b

Please sign in to comment.