diff --git a/pixi.lock b/pixi.lock index 6648249..bf529f7 100644 --- a/pixi.lock +++ b/pixi.lock @@ -788,7 +788,7 @@ packages: depends: - python input: - hash: 32013f31bef9004d6bef71b20df05282e87aba75a66432bfae782e261b1e3b7b + hash: 2c6b85c2c0ca83e1a3e746d3c620e8bcb5ff1f4e41d4f2788da63105720f310d globs: - pyproject.toml - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.0.0-pyha770c72_1.conda diff --git a/pyproject.toml b/pyproject.toml index 9a61a9c..e663f35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ python_version = "3.13" warn_unused_configs = true strict = true enable_error_code = ["ignore-without-code", "truthy-bool"] +disable_error_code = ["explicit-any", "decorated-any"] [tool.basedpyright] diff --git a/src/metrology_apis/__init__.py b/src/metrology_apis/__init__.py index e4edd38..722b553 100644 --- a/src/metrology_apis/__init__.py +++ b/src/metrology_apis/__init__.py @@ -1,6 +1,6 @@ """Metrology APIs.""" -from typing import Final, Protocol, Self, override, runtime_checkable +from typing import Any, Final, Generic, Protocol, Self, TypeVar, override, runtime_checkable import optype as op @@ -8,8 +8,51 @@ __all__ = ["__version__", "Dimension", "Quantity", "Unit"] +VT = TypeVar('VT') +DT = TypeVar('DT', bound='Dimension') +UT = TypeVar('UT', bound='Unit[DT]') + +@runtime_checkable +class MetrologyNamespace[Q: Quantity[VT, UT, DT], V, U: Unit[DT], D: Dimension](Protocol): + + @staticmethod + def asdimension(obj: str | D) -> D: ... + + @staticmethod + def asunit(obj: str | U) -> U: ... + + @staticmethod + def asquantity(obj: Q | V, *, unit: U) -> Q: ... + + @runtime_checkable class Dimension(Protocol): + def __metrology_namespace__[Q: Quantity[VT, UT, DT], V, U: Unit[DT]]( + self, /, *, api_version: str | None = None + ) -> MetrologyNamespace[Q, V, U, Self]: + """ + Returns an object that has all the metrology API functions on it. + + Parameters + ---------- + api_version: str or None + string representing the version of the metrology API + specification to be returned. If it is `None`, it should + return the namespace corresponding to latest version of the + metrology API specification. If the given version is invalid + or not implemented for the given module, an error should be + raised. Default: `None`. + + Returns + ------- + out: Any + An object representing the metrology API namespace. It + should have every top-level function defined in the + specification as an attribute. It may contain other public + names as well, but it is recommended to only include those + names that are part of the specification. + """ + def __mul__(self, other: Self, /) -> Self: ... def __truediv__(self, other: Self, /) -> Self: ... def __pow__(self, other: int, /) -> Self: ... @@ -19,9 +62,35 @@ def __rtruediv__(self, other: Self, /) -> Self: ... @runtime_checkable -class Unit(Protocol): +class Unit[D: Dimension](Protocol): + def __metrology_namespace__[Q: Quantity[VT, UT, DT], V]( + self, /, *, api_version: str | None = None + ) -> MetrologyNamespace[Q, V, Self, D]: + """ + Returns an object that has all the metrology API functions on it. + + Parameters + ---------- + api_version: str or None + string representing the version of the metrology API + specification to be returned. If it is `None`, it should + return the namespace corresponding to latest version of the + metrology API specification. If the given version is invalid + or not implemented for the given module, an error should be + raised. Default: `None`. + + Returns + ------- + out: Any + an object representing the metrology API namespace. It should + have every top-level function defined in the specification as + an attribute. It may contain other public names as well, but + it is recommended to only include those names that are part + of the specification. + """ + @property - def dimension(self) -> Dimension: ... + def dimension(self) -> D: ... def __mul__(self, other: Self, /) -> Self: ... def __truediv__(self, other: Self, /) -> Self: ... @@ -31,8 +100,35 @@ def __rmul__(self, other: Self, /) -> Self: ... def __rtruediv__(self, other: Self, /) -> Self: ... def __rpow__(self, other: int | float, /) -> Self: ... + @runtime_checkable -class Quantity[V, U: Unit](Protocol): +class Quantity[V, U: Unit[DT], D: Dimension](Protocol): + def __metrology_namespace__( + self, /, *, api_version: str | None = None + ) -> MetrologyNamespace[Self, V, U, D]: + """ + Returns an object that has all the metrology API functions on it. + + Parameters + ---------- + api_version: str or None + string representing the version of the metrology API + specification to be returned. If it is `None`, it should + return the namespace corresponding to the latest version of + the metrology API specification. If the given version is + invalid or not implemented for the given module, an error + should be raised. Default: `None`. + + Returns + ------- + out: Any + an object representing the metrology API namespace. It should + have every top-level function defined in the specification as + an attribute. It may contain other public names as well, but it + is recommended to only include those names that are part of the + specification. + """ + @property def value(self) -> V: ... @property @@ -41,4 +137,4 @@ def unit(self) -> U: ... ### Dunder Methods @override - def __eq__[B](self: "Quantity[V, U]", other: "Quantity[op.CanEq[V, B], U]", /) -> B: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] + def __eq__[B](self: "Quantity[V, U, D]", other: "Quantity[op.CanEq[V, B], U, D]", /) -> B: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride]