Repository: jorenham/optype (language: python)

Issue:
Array-API interfaces
https://data-apis.org/array-api/latest/API_specification/index.html

## goal

The goal is to provide `Protocols` that can be used to type e.g. [`scipy.fft.fftfreq`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.fftfreq.html#scipy.fft.fftfreq) (through its `xp` kwarg) and [`scipy.fft.fftshift`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.fft.fftshift.html) (via the passed array).

## the (big) problems

- The dtype spec only defines `__eq__`, which also exists in `object`. So it's *impossible* to (structurally) type a "dtype".
- In the same way, it's impossible to (structurally) type a "device".
- Shapes are defined as tuples of "something". So the best we can do is type the number of dimensions, and ignore the axis types.

This means that static type-checkers won't be able to see the difference between a boolean array on your CPU, and a complex-floating array on a quantum computer within the core of a neutron star in the Andromeda galaxy.

## so now what?

We can still figure out the following types:

- `Array` + the dimensionality (because `shape: tuple[?, ...]` kinda tells us the `ndim`, but not the actual shape)
- `Namespace`, which is a protocollable module-type (often named `xp`), that could give us 
    - `xp.array`: the specific `Array` type (but there's still no HKT after asking for it for over 6 years...), and 
    - `xp.(bool|((u?)int|float|complex)(8|16|32|64))` -i.e. the impossible-to-type `DType` types (🎩 🪄 🐰)
    - `xp.__array_namespace_info__() -> Info` ([docs](https://data-apis.org/array-api/latest/API_specification/inspection.html)), an object with some other "magical" methods:
        - `.default_device() -> DefaultDevice`  
        - `.devices() -> list[Device]`
        - `.dtypes(*, device: Device?, kind: Kind?)` - with `Kind` a literal string category, which allows us to "categorize" the e.g. floats, ints, and complexes (assuming the lib properly typed it), i.e. as type aliases.

## problem solved: just type the untypeable

Note that we can also obtain the `Info` type via `Array`, since `Array.__array_namespace__() -> Namespace[Info]` s.t. `Namespace[InfoT].__array_namespace_info__() -> InfoT` (ignoring the versions). 
So we can get all dtype types from either `Array` or `Namespace`, and we can either one from the other, and vice-versa (i.e. bidirectionally in both ways, and also the other way around).



Discussion / hints:

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d85079fc..b461693b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,7 +8,6 @@ ci:
     - ruff-format
     - basedmypy
     - basedpyright
-    - basedpyright-verifytypes
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
@@ -59,7 +58,7 @@ repos:
           - tomli
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.7.1
+    rev: v0.7.3
     hooks:
       - id: ruff
         args: [--fix, --show-fixes]
@@ -75,13 +74,6 @@ repos:
 
       - id: basedpyright
         name: basedpyright
-        entry: uv run basedpyright
+        entry: uv run basedpyright --threads=3
         language: system
         types_or: [python, pyi]
-
-      - id: basedpyright-verifytypes
-        name: basedpyright --verifytypes
-        entry: uv run basedpyright --ignoreexternal --verifytypes optype
-        language: system
-        always_run: true
-        pass_filenames: false
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 95c09bd8..633056cc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -8,7 +8,11 @@
     },
     "editor.rulers": [88],
     "git.branchProtection": ["master"],
-    "mypy-type-checker.args": ["--config-file=pyproject.toml", "--ide"],
-    "python.testing.pytestEnabled": true,
-    "editor.defaultFormatter": "charliermarsh.ruff"
+    "mypy-type-checker.args": [
+      "--ide",
+      "--config-file=${workspaceFolder}/pyproject.toml"
+    ],
+    "mypy-type-checker.cwd": "${fileDirname}",
+    "mypy-type-checker.path": ["uv", "run", "mypy"],
+    "python.testing.pytestEnabled": true
   }
diff --git a/README.md b/README.md
index 6fefee20..a3aaf3ab 100644
--- a/README.md
+++ b/README.md
@@ -225,6 +225,7 @@ The API of `optype` is flat; a single `import optype as opt` is all you need
     - [`Any*` type aliases](#any-type-aliases)
     - [`Empty*` type aliases](#empty-type-aliases)
     - [Literal types](#literal-types)
+- [`optype.array_api`](#optypearray_api)
 - [`optype.dlpack`](#optypedlpack)
 - [`optype.numpy`](#optypenumpy)
     - [`Array`](#array)
@@ -2096,6 +2097,10 @@ yield no elements.
     </tr>
 </table>
 
+### `optype.array_api`
+
+*Coming soon...*
+
 ### `optype.dlpack`
 
 A collection of low-level types for working [DLPack](DOC-DLPACK).
diff --git a/optype/__init__.py b/optype/__init__.py
index 50d065f2..530f0991 100644
--- a/optype/__init__.py
+++ b/optype/__init__.py
@@ -1,6 +1,6 @@
 from importlib import metadata as _metadata
 
-from . import copy, dataclasses, inspect, json, pickle, string, types, typing
+from . import array_api, copy, dataclasses, inspect, json, pickle, string, types, typing
 from ._core._can import (
     CanAEnter,
     CanAEnterSelf,
@@ -539,6 +539,7 @@
     "HasTypeParams",
     "HasWrapped",
     "__version__",
+    "array_api",
     "copy",
     "dataclasses",
     "do_abs",
diff --git a/optype/_core/_does.py b/optype/_core/_does.py
index eccb288f..7da6be77 100644
--- a/optype/_core/_does.py
+++ b/optype/_core/_does.py
@@ -192,7 +192,7 @@ class DoesEq(Protocol):
     @overload
     def __call__(self, lhs: _c.CanEq[_RightT, _OutT], rhs: _RightT, /) -> _OutT: ...
     @overload
-    def __call__(self, lhs: _LeftT, rhs: _c.CanEq[_LeftT, _OutT], /) -> _OutT: ...  # type: ignore[misc]  # pyright: ignore[reportOverlappingOverload]
+    def __call__(self, lhs: _LeftT, rhs: _c.CanEq[_LeftT, _OutT], /) -> _OutT: ...  # type: ignore[overload-cannot-match]  # pyright: ignore[reportOverlappingOverload]
 
 
 @set_module("optype")
@@ -200,7 +200,7 @@ class DoesNe(Protocol):
     @overload
     def __call__(self, lhs: _c.CanNe[_RightT, _OutT], rhs: _RightT, /) -> _OutT: ...
     @overload
-    def __call__(self, lhs: _LeftT, rhs: _c.CanNe[_LeftT, _OutT], /) -> _OutT: ...  # type: ignore[misc]  # pyright: ignore[reportOverlappingOverload]
+    def __call__(self, lhs: _LeftT, rhs: _c.CanNe[_LeftT, _OutT], /) -> _OutT: ...  # type: ignore[overload-cannot-match]  # pyright: ignore[reportOverlappingOverload]
 
 
 @set_module("optype")
diff --git a/optype/_core/_has.py b/optype/_core/_has.py
index f950b979..fab220b4 100644
--- a/optype/_core/_has.py
+++ b/optype/_core/_has.py
@@ -103,7 +103,7 @@ class HasQualname(Protocol[_QualnameT]):  # pyright: ignore[reportInvalidTypeVar
 
 @set_module("optype")
 @runtime_checkable
-class HasNames(  # pyright: ignore[reportIncompatibleVariableOverride,reportInvalidTypeVarUse]
+class HasNames(  # pyright: ignore[reportInvalidTypeVarUse]
     HasName[_NameT],
     HasQualname[_QualnameT],
     Protocol[_NameT, _QualnameT],
diff --git a/optype/array_api/__init__.py b/optype/array_api/__init__.py
new file mode 100644
index 00000000..cbfac28a
--- /dev/null
+++ b/optype/array_api/__init__.py
@@ -0,0 +1,54 @@
+from ._array import Array
+from ._device import Device
+from ._dtypes import (
+    Bool,
+    Complex0,
+    Complex64,
+    Complex128,
+    ComplexFloating,
+    DType,
+    Float0,
+    Float32,
+    Float64,
+    Int0,
+    Int8,
+    Int16,
+    Int32,
+    Int64,
+    Integer_co,
+    Numeric,
+    Real,
+    RealFloating,
+    UInt8,
+    UInt16,
+    UInt32,
+    UInt64,
+)
+
+
+__all__ = [
+    "Array",
+    "Bool",
+    "Complex0",
+    "Complex64",
+    "Complex128",
+    "ComplexFloating",
+    "DType",
+    "Device",
+    "Float0",
+    "Float32",
+    "Float64",
+    "Int0",
+    "Int8",
+    "Int16",
+    "Int32",
+    "Int64",
+    "Integer_co",
+    "Numeric",
+    "Real",
+    "RealFloating",
+    "UInt8",
+    "UInt16",
+    "UInt32",
+    "UInt64",
+]
diff --git a/optype/array_api/_array.py b/optype/array_api/_array.py
new file mode 100644
index 00000000..6ce7f146
--- /dev/null
+++ b/optype/array_api/_array.py
@@ -0,0 +1,138 @@
+# ruff: noqa: ERA001
+# mypy: disable-error-code="no-any-explicit, no-any-decorated"
+from __future__ import annotations
+
+import sys
+import types
+from typing import TYPE_CHECKING, Any, Literal, TypeAlias
+
+from optype._core._can import CanIndex
+
+from ._dtypes import Bool, DType
+
+
+if sys.version_info >= (3, 13):
+    from types import CapsuleType
+    from typing import Never, Protocol, Self, TypeVar, runtime_checkable
+else:
+    from typing_extensions import (
+        CapsuleType,
+        Never,
+        Protocol,
+        Self,
+        TypeVar,
+        runtime_checkable,
+    )
+
+
+if TYPE_CHECKING:
+    import enum
+
+    from ._device import Device
+
+
+__all__ = ["Array"]
+
+
+_IndexSingle: TypeAlias = CanIndex | slice | types.EllipsisType | None
+_IndexMulti: TypeAlias = _IndexSingle | tuple[_IndexSingle, ...]
+
+_T_co = TypeVar("_T_co", covariant=True, bound=DType, default=DType)
+
+
+@runtime_checkable
+class BaseArray(Protocol[_T_co]):
+    @property
+    def dtype(self, /) -> _T_co: ...
+    @property
+    def device(self, /) -> Device: ...
+    def to_device(self, device: Never, /, *, stream: int | None = None) -> Self: ...
+
+    # TODO: Return a dedicated array-api namespace type.
+    def __array_namespace__(
+        self,
+        /,
+        *,
+        api_version: Literal["2023.12"] | None = None,
+    ) -> types.ModuleType: ...
+
+    def __dlpack__(
+        self,
+        /,
+        *,
+        stream: int | None = None,
+        max_version: tuple[int, int] | None = None,
+        dl_device: tuple[int, Literal[0]] | None = None,
+        # NOTE: thee `copy: bool | None` is omitted because of a `numpy<2.2` typing bug
+    ) -> CapsuleType: ...
+    def __dlpack_device__(self, /) -> tuple[enum.Enum | int, int]: ...
+
+
+@runtime_checkable
+class Array(BaseArray[_T_co], Protocol[_T_co]):
+    @property
+    def shape(self, /) -> tuple[int, ...]: ...
+    @property
+    def ndim(self, /) -> int: ...
+    @property
+    def size(self, /) -> int: ...
+
+    @property
+    def T(self, /) -> Self: ...  # noqa: N802
+    @property
+    def mT(self, /) -> Self: ...  # noqa: N802
+
+    # NOTE: for `numpy.ndarray` compatibility, we can't accept or return `Array` here.
+    def __lt__(self, x: float, /) -> Any: ...  # pyright: ignore[reportAny]
+    def __le__(self, x: float, /) -> Any: ...  # pyright: ignore[reportAny]
+    def __ge__(self, x: float, /) -> Any: ...  # pyright: ignore[reportAny]
+    def __gt__(self, x: float, /) -> Any: ...  # pyright: ignore[reportAny]
+
+    # binary arithmetic operators
+    def __matmul__(self, x: Self, /) -> Self: ...
+    def __add__(self, x: int | Self, /) -> Self: ...
+    def __sub__(self, x: int | Self, /) -> Self: ...
+    def __mul__(self, x: int | Self, /) -> Self: ...
+    def __pow__(self, x: int | Self, /) -> Self: ...
+    def __mod__(self, x: int | Self, /) -> Self: ...
+
+    def __rmatmul__(self, x: Self, /) -> Self: ...
+    def __radd__(self, x: int, /) -> Self: ...
+    def __rsub__(self, x: int, /) -> Self: ...
+    def __rmul__(self, x: int, /) -> Self: ...
+    def __rpow__(self, x: int, /) -> Self: ...
+    def __rmod__(self, x: int, /) -> Self: ...
+
+    # NOTE: uncommenting these methods causes causes type-checking performance issues
+    # def __truediv__(self, x: float | Self, /) -> Array[_T_co] | Array[Float0]: ...
+    # def __floordiv__(self, x: float | Self, /) -> Array[_T_co] | Array[Float0]: ...
+    # def __rtruediv__(self, x: float, /) -> Array[_T_co] | Array[Float0]: ...
+    # def __rfloordiv__(self, x: float, /) -> Array[_T_co] | Array[Float0]: ...
+
+    # bitwise operators
+    # TODO: fix the `numpy.ndarray` bitwise ops (currently impossible to match against)
+    # NOTE: uncommenting the following causes pyrght performance issues
+    # def __and__(self, x: Any, /) -> Array[Integer_co]: ...
+    # def __or__(self, x: Any, /) -> Array[Integer_co]: ...
+    # def __xor__(self, x: Any, /) -> Array[Integer_co]: ...
+
+    # subscripting operators
+    def __getitem__(self, k: _IndexMulti | Array[Bool], /) -> Self: ...
+
+    # TODO: require the scalar value to match the array dtype
+    def __setitem__(self, k: _IndexMulti | Array, v: complex | Array, /) -> None: ...
+
+    # builtin type conversion
+    def __bool__(self, /) -> bool: ...
+    def __index__(self, /) -> int: ...
+    def __int__(self, /) -> int: ...
+    def __float__(self, /) -> float: ...
+    def __complex__(self, /) -> complex: ...
+
+    # unary arithmetic operators
+    def __pos__(self, /) -> Self: ...
+    def __neg__(self, /) -> Self: ...
+
+    # TODO: Re-enable after https://github.com/numpy/numpy/pull/27659
+    # def __abs__(self, /) -> Array[Any]: ...
+    # def __invert__(self, /) -> Self: ...
diff --git a/optype/array_api/_device.py b/optype/array_api/_device.py
new file mode 100644
index 00000000..23a38a45
--- /dev/null
+++ b/optype/array_api/_device.py
@@ -0,0 +1,41 @@
+from typing import Literal, Protocol, TypeAlias, runtime_checkable
+
+from ._device_dask import _DeviceDask
+
+
+__all__ = ["Device"]
+
+
+_DeviceNP: TypeAlias = Literal["cpu"]
+
+
+@runtime_checkable
+class _DeviceXPS(Protocol):
+    # https://github.com/data-apis/array-api-strict/blob/2.1/array_api_strict/_array_object.py#L46-L61
+    _device: Literal["CPU_DEVICE", "device1", "device2"]
+
+
+@runtime_checkable
+class _DeviceTorch(Protocol):
+    # https://pytorch.org/docs/stable/tensor_attributes.html#torch.device
+    # https://github.com/pytorch/pytorch/blob/v2.5.0/torch/_C/__init__.pyi.in#L106-L124
+    @property
+    def index(self, /) -> int: ...
+    @property
+    def type(self, /) -> str: ...
+
+
+@runtime_checkable
+class _HasID(Protocol):
+    # the common denominator of the cupy and jax device type
+    #
+    # https://docs.cupy.dev/en/stable/reference/generated/cupy.cuda.Device.html
+    # https://github.com/cupy/cupy/blob/v13.3.0/cupy/cuda/device.pyx#L120
+    #
+    # https://jax.readthedocs.io/en/latest/_autosummary/jax.Device.html
+    # https://github.com/openxla/xla/blob/4d0fe88/xla/python/xla_extension/__init__.pyi#L442-L460
+    @property
+    def id(self, /) -> int: ...
+
+
+Device: TypeAlias = _DeviceNP | _HasID | _DeviceXPS | _DeviceTorch | _DeviceDask
diff --git a/optype/array_api/_device_dask.py b/optype/array_api/_device_dask.py
new file mode 100644
index 00000000..6029ffc6
--- /dev/null
+++ b/optype/array_api/_device_dask.py
@@ -0,0 +1,11 @@
+# mypy: disable-error-code="import-untyped"
+# pyright: reportPrivateUsage=false, reportMissingTypeStubs=false
+# ruff: noqa: N812
+
+try:
+    from array_api_compat.common._helpers import _dask_device as _DeviceDask
+except ImportError:
+    from typing import NoReturn as _DeviceDask
+
+
+__all__ = ["_DeviceDask"]
diff --git a/optype/array_api/_device_dask.pyi b/optype/array_api/_device_dask.pyi
new file mode 100644
index 00000000..8901a62e
--- /dev/null
+++ b/optype/array_api/_device_dask.pyi
@@ -0,0 +1,15 @@
+import sys
+from typing import Literal, TypeAlias
+
+if sys.version_info >= (3, 13):
+    from typing import override
+else:
+    from typing_extensions import override
+
+__all__ = ["_DeviceDask"]
+
+class _dask_device:  # noqa: N801
+    @override
+    def __repr__(self, /) -> Literal["DASK_DEVICE"]: ...
+
+_DeviceDask: TypeAlias = _dask_device
diff --git a/optype/array_api/_dtypes.py b/optype/array_api/_dtypes.py
new file mode 100644
index 00000000..15a93aeb
--- /dev/null
+++ b/optype/array_api/_dtypes.py
@@ -0,0 +1,103 @@
+# ruff: noqa: PYI042
+from __future__ import annotations
+
+import sys
+from typing import Protocol, TypeAlias, runtime_checkable
+
+# this weird import prevents a circular import (according to pyright)
+import optype.array_api._sctypes_numpy as _sct_np
+
+
+if sys.version_info >= (3, 13):
+    from typing import TypeVar
+else:
+    from typing_extensions import TypeVar
+
+
+__all__ = [  # noqa: RUF022
+    "DType",
+    "Numeric",
+    "Integer_co",
+    "Real",
+    "ComplexFloating",
+    "RealFloating",
+    "Bool",
+    "Int8", "Int16", "Int32", "Int64",
+    "UInt8", "UInt16", "UInt32", "UInt64",
+    "Float32", "Float64",
+    "Complex64", "Complex128",
+    "Int0", "Float0", "Complex0",
+]  # fmt: skip
+
+
+# numpy
+
+_T = TypeVar("_T")
+_T_co = TypeVar("_T_co", covariant=True)
+
+
+@runtime_checkable
+class _DTypeNP(Protocol[_T_co]):
+    @property
+    def type(self) -> type[_T_co]: ...
+
+
+# pytorch
+
+
+@runtime_checkable
+class _DTypeTorch(Protocol):
+    # the torch stubs don't use literals, so we can't distinguish between dtypes
+    # https://github.com/pytorch/pytorch/blob/v2.5.0/torch/_C/__init__.pyi.in#L181-L189
+    @property
+    def itemsize(self, /) -> int: ...
+    @property
+    def is_signed(self, /) -> bool: ...
+    @property
+    def is_floating_point(self, /) -> bool: ...
+    @property
+    def is_complex(self, /) -> bool: ...
+
+
+# array-api-strict
+
+
+@runtime_checkable
+class _DTypeXP(Protocol[_T_co]):
+    # https://github.com/data-apis/array-api-strict/blob/2.1/array_api_strict/_dtypes.py
+    @property
+    def _np_dtype(self, /) -> _T_co: ...
+    def __init__(self, /, np_dtype: _T_co) -> None: ...
+
+
+# putting it all together
+
+_DType: TypeAlias = _DTypeNP[_T] | _DTypeXP[_DTypeNP[_T]] | _DTypeTorch
+
+Bool: TypeAlias = _DType[_sct_np.Bool]
+Int8: TypeAlias = _DType[_sct_np.Integer]
+Int16: TypeAlias = _DType[_sct_np.Integer]
+Int32: TypeAlias = _DType[_sct_np.Integer]
+Int64: TypeAlias = _DType[_sct_np.Integer]
+UInt8: TypeAlias = _DType[_sct_np.Integer]
+UInt16: TypeAlias = _DType[_sct_np.Integer]
+UInt32: TypeAlias = _DType[_sct_np.Integer]
+UInt64: TypeAlias = _DType[_sct_np.Integer]
+Float32: TypeAlias = _DType[_sct_np.Floating]
+Float64: TypeAlias = _DType[_sct_np.Float64]
+Complex64: TypeAlias = _DType[_sct_np.ComplexFloating]
+Complex128: TypeAlias = _DType[_sct_np.Complex128]
+
+Int0: TypeAlias = _DType[_sct_np.Integer]
+Float0: TypeAlias = _DType[_sct_np.Float64]
+Complex0: TypeAlias = _DType[_sct_np.Complex128]
+
+Integer: TypeAlias = _DType[_sct_np.Integer]
+RealFloating: TypeAlias = _DType[_sct_np.Floating]
+ComplexFloating: TypeAlias = _DType[_sct_np.ComplexFloating]
+
+Integer_co: TypeAlias = _DType[_sct_np.Integer_co]
+Real: TypeAlias = _DType[_sct_np.Floating_co]
+Numeric: TypeAlias = _DType[_sct_np.Number]
+
+DType: TypeAlias = _DType[_sct_np.ComplexFloating_co]
diff --git a/optype/array_api/_sctypes_numpy.py b/optype/array_api/_sctypes_numpy.py
new file mode 100644
index 00000000..3795a20d
--- /dev/null
+++ b/optype/array_api/_sctypes_numpy.py
@@ -0,0 +1,139 @@
+# ruff: noqa: N801
+"""
+Runtime protocols for NumPy's numeric scalar types.
+See https://github.com/jorenham/optype/issues/25#issuecomment-2453445552
+"""
+
+import sys
+from types import GenericAlias, ModuleType
+from typing import TYPE_CHECKING, Protocol, TypeAlias, TypeVar, runtime_checkable
+
+
+if sys.version_info >= (3, 13):
+    from typing import Self
+else:
+    from typing_extensions import Self
+
+
+_ItemT = TypeVar("_ItemT", bound=complex)
+_NewArgsT_co = TypeVar("_NewArgsT_co", covariant=True, bound=tuple[float, ...])
+
+_NewArgs1: TypeAlias = tuple[float]
+_NewArgs2: TypeAlias = tuple[float, float]
+
+
+__all__ = [
+    "Bool",
+    "Complex128",
+    "ComplexFloating_co",
+    "Float64",
+    "Floating",
+    "Floating_co",
+    "Integer",
+    "Integer_co",
+    "Number",
+    "RealNumber",
+]
+
+
+@runtime_checkable
+class _CanArrayNamespace(Protocol):
+    def __array_namespace__(self, /, *, api_version: None = None) -> ModuleType: ...
+
+
+@runtime_checkable
+class _CanGetNewArgs(Protocol[_NewArgsT_co]):
+    def __getnewargs__(self, /) -> _NewArgsT_co: ...
+
+
+@runtime_checkable
+class _CanClassGetItem(Protocol):
+    def __class_getitem__(cls, k: object, /) -> GenericAlias: ...
+
+
+# NOTE: forcibly set to invariant to work around the incorrect int/float promotion rules
+@runtime_checkable
+class _CanItem(Protocol[_ItemT]):  # type:ignore[misc]  # pyright: ignore[reportInvalidTypeVarUse]
+    def item(self, /) -> _ItemT: ...
+
+
+@runtime_checkable
+class Integer_co(_CanArrayNamespace, _CanItem[int], Protocol):
+    """Booleans and (signed and unsigned) integers."""
+
+    if not TYPE_CHECKING:
+        # NOTE: this is a workaround for `numpy<2.2`
+        def __index__(self, /) -> Self: ...
+
+
+@runtime_checkable
+class Floating_co(_CanArrayNamespace, _CanItem[float], Protocol):
+    """Booleans, integers, and real floats."""
+
+
+@runtime_checkable
+class ComplexFloating_co(_CanArrayNamespace, _CanItem[complex], Protocol):
+    """Booleans, integers, real- and complex floats."""
+
+
+@runtime_checkable
+class Number(_CanArrayNamespace, _CanClassGetItem, _CanItem[complex], Protocol):
+    """Integers, real- and complex floats, i.e. `ComplexFloating_co` minus `Bool`."""
+
+    if not TYPE_CHECKING:
+        # NOTE: this is a workaround for `numpy<2.2`
+        def __round__(self, ndigits: int | None = None, /) -> int | Self: ...
+
+
+@runtime_checkable
+class RealNumber(_CanArrayNamespace, _CanClassGetItem, _CanItem[float], Protocol):
+    """Integers and real floats, i.e. `Number` minus `Bool`."""
+
+    def is_integer(self, /) -> bool: ...
+
+    if not TYPE_CHECKING:
+        # NOTE: this is a workaround for `numpy<2.2`
+        def __round__(self, ndigits: int | None = None, /) -> int | Self: ...
+
+
+@runtime_checkable
+class Integer(Integer_co, Protocol):
+    """Signed and unsigned integers."""
+
+    def bit_count(self, /) -> int: ...
+
+
+@runtime_checkable
+class Floating(Floating_co, Protocol):
+    """Real floats."""
+
+    def as_integer_ratio(self, /) -> tuple[int, int]: ...
+
+
+@runtime_checkable
+class ComplexFloating(Number, Protocol):
+    """
+    IMPORTANT: On `python<3.11` `complex.__complex__` does not exist, which makes
+    distringuishing between float and complex types impossible in certain cases.
+    IMPORTANT: On `numpy<2.2` this does not work as expected when type-checking!
+    Use `Complex128` or `Number` instead.
+    """
+
+    if sys.version_info >= (3, 11):
+
+        def __complex__(self, /) -> complex: ...
+
+
+@runtime_checkable
+class Bool(_CanArrayNamespace, _CanItem[bool], Protocol):
+    if not TYPE_CHECKING:
+        # NOTE: this is a workaround for `numpy<2.2`
+        def __index__(self, /) -> int: ...
+
+
+@runtime_checkable
+class Float64(_CanGetNewArgs[_NewArgs1], Floating, Protocol): ...
+
+
+@runtime_checkable
+class Complex128(_CanGetNewArgs[_NewArgs2], ComplexFloating, Protocol): ...
diff --git a/pyproject.toml b/pyproject.toml
index 3c8e8912..c9ede6fe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,9 +6,7 @@ build-backend = "hatchling.build"
 name = "optype"
 description = "Building blocks for precise & flexible type hints"
 version = "0.7.0.dev0"
-authors = [
-  {name = "Joren Hammudoglu", email = "jhammudoglu@gmail.com"},
-]
+authors = [{name = "Joren Hammudoglu", email = "jhammudoglu@gmail.com"}]
 license = {file = "LICENSE"}
 readme = "README.md"
 keywords = ["type", "typing", "type hints", "numpy"]
@@ -25,36 +23,62 @@ classifiers = [
 ]
 # https://scientific-python.org/specs/spec-0000/
 requires-python = ">=3.10"
-dependencies = [
-    "typing-extensions >= 4.10; python_version < '3.13'"
-]
+dependencies = ["typing-extensions>=4.10; python_version<'3.13'"]
+
+[project.optional-dependencies]
+numpy = ["numpy>=1.24"]
+
+[project.urls]
+Repository = "https://github.com/jorenham/optype/"
+Documentation = "https://github.com/jorenham/optype/blob/master/README.md"
+Issues = "https://github.com/jorenham/optype/issues"
+Changelog = "https://github.com/jorenham/optype/releases"
+Funding = "https://github.com/sponsors/jorenham"
 
-    [project.optional-dependencies]
-    numpy = ["numpy >= 1.24"]
-
-    [project.urls]
-    Repository = "https://github.com/jorenham/optype/"
-    Documentation = "https://github.com/jorenham/optype/blob/master/README.md"
-    Issues = "https://github.com/jorenham/optype/issues"
-    Changelog = "https://github.com/jorenham/optype/releases"
-    Funding = "https://github.com/sponsors/jorenham"
-
-[tool.uv]
-dev-dependencies = [
-  "numpy",
-  "typing_extensions",
-
-  "basedmypy >= 2.6.0",
-  "basedpyright >= 1.19.1",
-  "beartype >= 0.19.0",
-  "codespell >= 2.3.0",
-  "pre-commit >= 4.0.1",
-  "pytest >= 8.3.3",
-  "ruff >= 0.7.1",
-  "sp-repo-review[cli] >= 2024.8.19",
-  "tox >= 4.23.2",
+[dependency-groups]
+lint = [
+    "codespell>=2.3.0",
+    "ruff>=0.7.3",
+    "sp-repo-review[cli]>=2024.8.19",
+]
+common = [
+    "numpy>=1.24",
+    "typing-extensions>=4.10",
+]
+type = [
+    {include-group = "common"},
+    "basedmypy[faster-cache]>=2.7.0",
+    "basedpyright>=1.21.0",
+]
+array-api = [
+    "array-api-compat>=1.9.1",
+    "array-api-strict>=2.1.3",
+    "torch>=2.5.1; python_version<'3.13' or sys_platform=='linux'",
 ]
-python-downloads = "manual"
+test = [
+    {include-group = "common"},
+    {include-group = "array-api"},
+    "beartype>=0.19.0",
+    "pytest>=8.3.3",
+]
+typetest = [
+    {include-group = "type"},
+    {include-group = "test"},
+]
+dev = [
+    {include-group = "lint"},
+    {include-group = "typetest"},
+    "pre-commit>=4.0.1",
+    "tox>=4.23.2",
+]
+
+[tool.uv.sources]
+torch = {index = "pytorch", marker = "sys_platform == 'linux'"}
+
+[[tool.uv.index]]
+name = "pytorch"
+url = "https://download.pytorch.org/whl/cpu"
+explicit = true
 
 [tool.hatch.build.targets.sdist]
 exclude = [
@@ -73,6 +97,9 @@ exclude = [
     ".gitignore",
     ".mardownlint.yaml",
     ".pre-commit-config.yaml",
+    "CODE_OF_CONDUCT.md",
+    "CONTRIBUTING.md",
+    "SECURITY.md",
     "uv.lock",
 ]
 
@@ -83,17 +110,19 @@ ignore-words-list = "AtMost,AtLeast,CanGe,ND,nin"
 
 [tool.mypy]
 python_version = "3.10"
-modules = ["optype", "examples", "tests"]
+packages = ["optype", "examples", "tests"]
 always_true = "NP2"
 always_false = "NP20"
 strict = true
+disable_bytearray_promotion = true
+disable_memoryview_promotion = true
+plugins = ["numpy.typing.mypy_plugin"]
 
 allow_redefinition = true
 disallow_untyped_defs = true
 disallow_incomplete_defs = true
 warn_redundant_casts = true
 warn_unused_ignores = true
-show_error_code_links = true
 # based
 disallow_any_expr = false  # https://github.com/numpy/numpy/pull/27211
 # repo-review recommendations
@@ -103,6 +132,8 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
     [[tool.mypy.overrides]]
     module = ["tests.*", "tests.numpy.*"]
     disallow_any_decorated = false  # blame pytest
+    warn_unreachable = false  # assert not isinstance(...)
+    disable_error_code = ["no-any-explicit", "unreachable"]
 
 [tool.pyright]
 pythonVersion = "3.10"
@@ -113,11 +144,14 @@ venvPath = "."
 venv = ".venv"
 typeCheckingMode = "all"
 defineConstant = {NP2 = true, NP20 = false}
+useLibraryCodeForTypes = true
+enableReachabilityAnalysis = false
 reportUnreachable = false  # nothing wrong with `if sys.version_info() >= ...`
 reportUnusedCallResult = false  # https://github.com/microsoft/pyright/issues/8650
 reportUnusedImport = false  # dupe of F401
 reportUnusedVariable = false  # dupe of F841
 
+
 [tool.pytest.ini_options]
 minversion = "8.0"
 testpaths = ["optype", "examples", "tests"]
@@ -169,7 +203,7 @@ force-exclude = true
         "DTZ",      # flake8-datetimez
         "T10",      # flake8-debugger
         "EXE",      # flake8-executable
-        "EM",      # flake8-errmsg
+        "EM",       # flake8-errmsg
         "FA",       # flake8-future-annotations
         "ISC",      # flake8-implicit-str-concat
         "ICN",      # flake8-import-conventions
@@ -299,6 +333,9 @@ force-exclude = true
             "__dlpack__",
             "__dlpack_device__",
 
+            # array-api
+            "__array_namespace__",
+
             # `numpy` special methods
             "__array__",
             "__array_finalize__",
diff --git a/uv.lock b/uv.lock
index 457fbaf4..701aa591 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,9 +1,36 @@
 version = 1
 requires-python = ">=3.10"
+resolution-markers = [
+    "python_full_version < '3.12' and sys_platform == 'linux'",
+    "python_full_version >= '3.12' and sys_platform == 'linux'",
+    "python_full_version < '3.12' and sys_platform != 'linux'",
+    "python_full_version >= '3.12' and sys_platform != 'linux'",
+]
+
+[[package]]
+name = "array-api-compat"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/62/ec/6b8535d1839cb8f785001c8f343c89639d9fe5eac2ace67bf674d82f791f/array_api_compat-1.9.1.tar.gz", hash = "sha256:17bab828c93c79a5bb8b867145b71fcb889686607c5672b060aef437e0359ea8", size = 43248 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/13/1d/2b2d33635de5dbf5e703114c11f1129394e68be16cc4dc5ccc2021a17f7b/array_api_compat-1.9.1-py3-none-any.whl", hash = "sha256:41a2703a662832d21619359ddddc5c0449876871f6c01e108c335f2a9432df94", size = 50116 },
+]
+
+[[package]]
+name = "array-api-strict"
+version = "2.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/86/04/5bea309e7409c62a49433dfcc29a899e3c8fba99c313568488c6af0b2ae7/array_api_strict-2.1.3.tar.gz", hash = "sha256:f7b41168a9fa71d87922fffd141ae61f4ceb38a0906fc7d128b1f3349f7c6ce6", size = 69648 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/06/68/88cd07c9cfe954f5bf970108e118e6be642aba566547a22a5389824d0072/array_api_strict-2.1.3-py3-none-any.whl", hash = "sha256:7ba42a4d4023fe9e9e3805ac964885ae70adead5bff184fe995c62c8d457dc0a", size = 65312 },
+]
 
 [[package]]
 name = "basedmypy"
-version = "2.6.0"
+version = "2.7.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "basedtyping" },
@@ -11,48 +38,58 @@ dependencies = [
     { name = "tomli", marker = "python_full_version < '3.11'" },
     { name = "typing-extensions" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/76/0f/1eadffe7042cd3f0a4478ac9f050fc23a353f9cf140378fdebe47be4f8f7/basedmypy-2.6.0.tar.gz", hash = "sha256:6ff3607d6e0ef776b9c0c9fdb24706f96783686ec7805c3bc28dd19eb07e5dbd", size = 5390877 }
+sdist = { url = "https://files.pythonhosted.org/packages/b1/1f/de937c1535462ebaba5b639215b33bd94e157a958f6e809e0f46ba6e8677/basedmypy-2.7.0.tar.gz", hash = "sha256:a8ff08c667d8ca06c6ab5acd574e683b557ce64671b2a0e0c2503979f1186411", size = 5435024 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/d7/63/300e65e95dbcf17bf326894d0a43f1a2cd12361c847efcf9a055958bb13f/basedmypy-2.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2dde708c6e130df4d0ae5931095844cc4157ac0e1a99c676a023b1c31dbdccd4", size = 11269329 },
-    { url = "https://files.pythonhosted.org/packages/91/f3/add3051e396464bac01d368f743a9fdcaa5c7bdde986bd109866c19ec15b/basedmypy-2.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bfaef963805a02c24153c3d8af89b508248cdedec63c937af01e94237902dbdd", size = 10413575 },
-    { url = "https://files.pythonhosted.org/packages/9d/32/6f14c41276aad1d6c3a88eeea71f2b5de64947594411c19c1196bc0fe612/basedmypy-2.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf928660d50ce1a2182b9162d15dca3b370a82a054a54c9cb7ec90f4b1f1cdc2", size = 12871654 },
-    { url = "https://files.pythonhosted.org/packages/57/88/39cb3cbb7c8aeb5d43642b93bf86d12971a4ba086d4e95192b26274a2467/basedmypy-2.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1cadba3f48ce22e82b7f31ab6dcad1d74c47f7aaf25a4aa094b1468923dd1292", size = 13414483 },
-    { url = "https://files.pythonhosted.org/packages/6b/1d/52906e72c6f72cdc36e2d61394c92c30661a8f8200cba6732edec6116760/basedmypy-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:5ee0b49c577d93d401b029eeeb028f130340ba05d6048ce6e6adcbc609846d3b", size = 9855441 },
-    { url = "https://files.pythonhosted.org/packages/34/f0/efe19fd89d57c9de166faadf01b1a1f88b9e71e4471bd8bcb157c4d93c84/basedmypy-2.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5bfcd0cd73aa51cf263fa88d6225f4dce8ddf24789ac7007f3b4f7ab410edf3", size = 11189454 },
-    { url = "https://files.pythonhosted.org/packages/a5/f9/7c1d03c3fcee87db5c4968e328eff0750987a8541828f882b05e5058a92a/basedmypy-2.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e2dcade460f9837a84907c8dc2bb6186e3792779f482f09bd84ec160129b5ed", size = 10337931 },
-    { url = "https://files.pythonhosted.org/packages/ea/48/29e0f26a1fdca68c1fc5c8f30344e2a1382f3784d0ab81477daa0498efe9/basedmypy-2.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12829f2619b733013d7fc9dfca76cdeb8c33724eab9d54ac5db7e23b902a0e3", size = 12791245 },
-    { url = "https://files.pythonhosted.org/packages/29/78/dcdb109bcc29a236700beb80df0c17495b9e3b7451da421b6205e9201941/basedmypy-2.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c16a1c36ba528d5922be8fbaa918616cfe82f041a6a8fb95ef0c580761818cc", size = 13293946 },
-    { url = "https://files.pythonhosted.org/packages/b4/2d/c7a73964cac38408aadb4dd73a4bf3241fb27c6fe89714d1f70d6aa3d521/basedmypy-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb22d5814601883451539ef83d2e362f6cad02a109afcf2af2628320cfc5198f", size = 9845382 },
-    { url = "https://files.pythonhosted.org/packages/60/ff/76d8c8b12629d774b87867ae078e133640676522857bdf654ab9cd88b79c/basedmypy-2.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:590688b27afec8303b8e42043cd7afaa6b60d0ac0bb894a49e16f541378adca8", size = 11333627 },
-    { url = "https://files.pythonhosted.org/packages/94/74/b3f753aeba62ae104daaa52baf202c851d9e374b2f0869de9dd4449436de/basedmypy-2.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0dbf81111b710ab8fc4e6ba014061d71bcf5951c405d73220c783bc283691bff", size = 10299699 },
-    { url = "https://files.pythonhosted.org/packages/8a/88/dd5b8d5ff489b701d7d16f887bc8b6c4f8dee849c21aab480b2f47c14c0b/basedmypy-2.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e36eeb0f63e02b5ff9828208803b711d755ba493aba5b237832e38a8f4837ad1", size = 12886699 },
-    { url = "https://files.pythonhosted.org/packages/a0/c4/c95bdccf3e45b40cde71132d33dd27f123617ac793f0857df475a54a0224/basedmypy-2.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b9819ea425a769b9bdfd8568d8ef6451e4f169f8df39fdb9d5014ee0a4ba15f4", size = 13358166 },
-    { url = "https://files.pythonhosted.org/packages/74/39/6e99b76a5266a48df3bf03f3a98e1e7c6e7fb09efb4e3f6861c530ff30b9/basedmypy-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:74cecf63520469d49f360d4226701b4e563bfc911dc84747a9abf67a00133f8b", size = 9954015 },
-    { url = "https://files.pythonhosted.org/packages/12/78/ac0e47575435a88799b028539b7087dc7cf17f0d3c2c58351d093e3e6eaf/basedmypy-2.6.0-py3-none-any.whl", hash = "sha256:329244dfbdd0507e83f6813e1509849e60b75321d193fa156f6151fb564faa17", size = 2658512 },
+    { url = "https://files.pythonhosted.org/packages/ab/67/e0ac30fa29643cb26ed3f291c6bf933c29637b2f487baae078b8e351cc80/basedmypy-2.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:283635cb57c917c7f88146e801005dc0034a74c25d48397bb65699e2359d1085", size = 11358422 },
+    { url = "https://files.pythonhosted.org/packages/55/7b/ef7c97422560f4e2ae9f2f5d76513142d8cd55a655b5ed29fe7069c2737e/basedmypy-2.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5158709f8c04fdb0f62d97551ad75366f3838e5dd3db0ffd83c32c3b64867e7d", size = 10486231 },
+    { url = "https://files.pythonhosted.org/packages/9c/55/47bb0e39a4d3bf9cb0b226f10ba61be6fd09174022fd636966e1eb3c2032/basedmypy-2.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d76a8ee5e755024dcfb4e1ec0309ae6f95a0330cd71151cbe956a6740da5bc8", size = 12961616 },
+    { url = "https://files.pythonhosted.org/packages/98/fd/3a91fe615fae65de1f5fbba60055fdd639a2671ef3d63e881ad86faffa87/basedmypy-2.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0794aa3281bae1e88922223da6da5db9c6e495694bdc65cfc03acd97d1a94602", size = 13509169 },
+    { url = "https://files.pythonhosted.org/packages/42/c2/6b39b0fe1560fcde2b9310703baf9ba567a00a0b71ee376a40ea945ec57d/basedmypy-2.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:58a75892e74a8679a54a18b8e5959ae8c1612e925ccb8ccc07a5e767c44eaf8d", size = 9922469 },
+    { url = "https://files.pythonhosted.org/packages/1f/f7/935e8249da989b1546cc0882ec81dc37595215a36d11c14a94770cdd5419/basedmypy-2.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f7a467856b12fc5b8085f065eaa36e1c08ea4e048a11777f47715dc4a9108e39", size = 11277220 },
+    { url = "https://files.pythonhosted.org/packages/e5/68/6769b01d4eefa6d60dba1123943a509728a45ceafd68d4a3f55258c84a1f/basedmypy-2.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94988835eec3c886daeae717b5dc16befbd339b520bd3d1d005b562836530d21", size = 10408011 },
+    { url = "https://files.pythonhosted.org/packages/35/d7/23b31814e6d07aff02b4a9d05fb8840d0ff92cd4dbadd0c76e03da0ba744/basedmypy-2.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cab30d2bfbba4e8bbd36b7bc1ea8811b40a062796edc653a8e4fd0881d4899e6", size = 12884427 },
+    { url = "https://files.pythonhosted.org/packages/68/66/cc7de13bb872836fb952710d59f18a6779f4c8b468708c312a0862d9fe46/basedmypy-2.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:40f2324c82e7befc94ad722fc3e0fe9362b7607b93d29ae7930831e30e22ff6a", size = 13384613 },
+    { url = "https://files.pythonhosted.org/packages/db/9a/758eb4e8fca3a60de7c70fab8cdc82f6b834efaa1259cad35550d8e80d97/basedmypy-2.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:9470812c9a977d832bee52ebdd9b76bd30cea8f37f42ab1ce509e871b79996d3", size = 9916651 },
+    { url = "https://files.pythonhosted.org/packages/28/f8/91eb12e00aa8ab424adb4c7ac74451ab50c8e275adecb1297ead7d8e575a/basedmypy-2.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:678fc29448b87c92df4c035fac2ae53a8e675a3b29d46d08d6328147f95b4832", size = 11420273 },
+    { url = "https://files.pythonhosted.org/packages/b5/e3/753aa78fad1439f31bc911e39f31e919729d0541fa13852939886be0c1ec/basedmypy-2.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5a770fb4e4714ef359b766dc29f44212f562ca387883c6ee5997cad2f4b6a838", size = 10373827 },
+    { url = "https://files.pythonhosted.org/packages/d1/4d/018b76e4dd3c12fde9502834c2ad10973a31097d60a37d47a1aa72bbe3da/basedmypy-2.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91caccc79132091e62dce7d6927003dda5b6930deb50c18bdae2b19c22c1c924", size = 12976992 },
+    { url = "https://files.pythonhosted.org/packages/7d/7b/d4962e0361b395eccacc2b005806c0c3953744bc8126a5931ece8712e087/basedmypy-2.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1e3f32699843071df47445e71d53495e4e120b2df6469cc7efb3837080b770ab", size = 13446050 },
+    { url = "https://files.pythonhosted.org/packages/f6/5e/e0c2c69f5e4f9b24381fc345112ad661d236c317bc8c76da8f673a0573f8/basedmypy-2.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:f7f0e7d2f5bd943a7f3dfa6b314e0684d38fd7665f560bcec0c4958d2dd710d1", size = 10020738 },
+    { url = "https://files.pythonhosted.org/packages/8c/ff/4b999f7441c75ba5749a4f9fcd387c7c71fef0b940f3d8331bc90044f56d/basedmypy-2.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4269027ee62f6ed4ded804e523a69a71eaec888ced8b6bc94c05b902b4dc970f", size = 11412795 },
+    { url = "https://files.pythonhosted.org/packages/d6/ff/c9a0be388eb2399e10955d50e8ee79ddd67686155c1d2b6f0c588fbfb5a8/basedmypy-2.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee773922a22a3aba0eb2ce8f9b8733e1b6086b7b75b885f887cff0f9746bf607", size = 10374562 },
+    { url = "https://files.pythonhosted.org/packages/4a/c0/57dd357faee7ef5172a3434565c823b84e6ec6625c7e80d9813e4e7b5e15/basedmypy-2.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa155eb6506c2caf7296e97eb57e3870bc65e271af2582f2bb245b49acd283b0", size = 12970058 },
+    { url = "https://files.pythonhosted.org/packages/1c/11/558950128ae9b3ca1449e6e0716af850a5fa71f02b8c489d898bf01c4f9f/basedmypy-2.7.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f938bc49ebe8ae81c1091f1934ac014b03464ead3cf93893b69f5da98c3ffbde", size = 13444481 },
+    { url = "https://files.pythonhosted.org/packages/e9/3c/c6243926f5710becd95abd6af4d496e04f03fe43d76d1ef50937399ec2ed/basedmypy-2.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:0758a45dc099f57110b21fd480dae82d289620501acbbb2854db5d22d6dd7b2a", size = 10025002 },
+    { url = "https://files.pythonhosted.org/packages/48/aa/95f5d986b4b827372c74f7d7014c2740709ae269f05be8bd5b4b9a537cab/basedmypy-2.7.0-py3-none-any.whl", hash = "sha256:59661555a2637e0ece7ac65aca774451d60c3a9c1bd2613a96a067418fc381a7", size = 2686981 },
+]
+
+[package.optional-dependencies]
+faster-cache = [
+    { name = "orjson" },
 ]
 
 [[package]]
 name = "basedpyright"
-version = "1.19.1"
+version = "1.21.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "nodejs-wheel-binaries" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/d0/b5/090fbffb5191c83a0701960ee1c8e50cc5128e869af1bf9d277e25b3b725/basedpyright-1.19.1.tar.gz", hash = "sha256:2863e619296ddd7f9f566c44e4fa8f781d727a83fa2da6ef62951e0b657655f6", size = 55095769 }
+sdist = { url = "https://files.pythonhosted.org/packages/94/91/d66f9ece468b3daa2f3d357304c816438be4595006928ea5639fb6b5139e/basedpyright-1.21.0.tar.gz", hash = "sha256:e3a9f5b89acc7f23d5a10d95ea6056b2247fcd15b8b248ad44c560c85c39d5e3", size = 20789741 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/24/52/d79749e40819cb3bce6895d9303c3dd754ff40cc27a667158eb911e53760/basedpyright-1.19.1-py3-none-any.whl", hash = "sha256:976744146fcecb7413a726fbb8e52f4606738e02820a7dfbe310199f8915311e", size = 11523359 },
+    { url = "https://files.pythonhosted.org/packages/20/f7/97af385e43208d600f892da1b8c94f35d4e9165d5c774013bb202617dd7d/basedpyright-1.21.0-py3-none-any.whl", hash = "sha256:48902c476d6301c556df6eeae9acf1e34b176b14f8702ad5c770f4e6a747018a", size = 11148428 },
 ]
 
 [[package]]
 name = "basedtyping"
-version = "0.1.4"
+version = "0.1.7"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "typing-extensions" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/ec/22/f0d1bbd34766de14a6ed5b4d3a09ab9d5de12376655b4d9df9e05d90c5a3/basedtyping-0.1.4.tar.gz", hash = "sha256:8a3ba80d4628eb2f6a205759a8151e6df7341ceba03e5daf6b395fcc873a7967", size = 9776 }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/50/b665ea83292b70ee0895f99828a9c5f5ab29a681f47c14fd1ff8c5ad039d/basedtyping-0.1.7.tar.gz", hash = "sha256:6a3b6cf64b6d62b990e492dfad27981060e2d238b11e962bfa4e2d3bd3cb8c87", size = 13298 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/b0/5f/c1864efc5423ba25e512bfa7589eb1135885650e116de4eef39078b7ce82/basedtyping-0.1.4-py3-none-any.whl", hash = "sha256:d4832ab7af178e6a5394199725dc55f26e36e1ad5fae68db41b956c1cb99a0d2", size = 9637 },
+    { url = "https://files.pythonhosted.org/packages/b0/f5/72fe4ec0cf5669f7a4fc9bbb94382cabccc580fd69162070cf733709b54b/basedtyping-0.1.7-py3-none-any.whl", hash = "sha256:2ad697605d1ae437c0e3d76ed61c0dc3657ab0ba43ad6e507f311cc311d4f592", size = 13801 },
 ]
 
 [[package]]
@@ -148,6 +185,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 },
 ]
 
+[[package]]
+name = "fsspec"
+version = "2024.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a0/52/f16a068ebadae42526484c31f4398e62962504e5724a8ba5dc3409483df2/fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493", size = 286853 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", size = 179641 },
+]
+
 [[package]]
 name = "identify"
 version = "2.6.1"
@@ -166,6 +212,18 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
 ]
 
+[[package]]
+name = "jinja2"
+version = "3.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
+]
+
 [[package]]
 name = "markdown-it-py"
 version = "3.0.0"
@@ -178,6 +236,64 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
 ]
 
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 },
+    { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 },
+    { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 },
+    { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 },
+    { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 },
+    { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 },
+    { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 },
+    { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 },
+    { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 },
+    { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 },
+    { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 },
+    { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 },
+    { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 },
+    { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 },
+    { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 },
+    { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 },
+    { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 },
+    { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 },
+    { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 },
+    { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 },
+    { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 },
+    { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 },
+    { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 },
+    { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 },
+    { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 },
+    { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 },
+    { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 },
+    { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 },
+    { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 },
+    { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 },
+    { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
+    { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
+    { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
+    { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
+    { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
+    { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
+    { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
+    { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
+    { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
+    { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
+    { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
+    { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
+    { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
+    { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
+    { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
+    { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
+    { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
+    { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
+    { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
+    { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
+]
+
 [[package]]
 name = "mdurl"
 version = "0.1.2"
@@ -187,6 +303,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
 ]
 
+[[package]]
+name = "mpmath"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
+]
+
 [[package]]
 name = "mypy-extensions"
 version = "1.0.0"
@@ -196,6 +321,15 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
 ]
 
+[[package]]
+name = "networkx"
+version = "3.4.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 },
+]
+
 [[package]]
 name = "nodeenv"
 version = "1.9.1"
@@ -207,78 +341,154 @@ wheels = [
 
 [[package]]
 name = "nodejs-wheel-binaries"
-version = "20.18.0"
+version = "22.11.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/119ab02492636261958f2b219471be193c58870a27072248053571e5a657/nodejs_wheel_binaries-20.18.0.tar.gz", hash = "sha256:1d574a3d0e503b42414e80b4529da9fc71104b0d5e1d75606b3fb5802960fb6f", size = 6933 }
+sdist = { url = "https://files.pythonhosted.org/packages/b7/29/1ae5b1d715cb0a3d72ca09cacc4f75d274010bddb2372c61b981a068e47d/nodejs_wheel_binaries-22.11.0.tar.gz", hash = "sha256:e67f4e4a646bba24baa2150460c9cfbde0f75169ba37e58a2341930a5c1456ee", size = 7004 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/52/bd/3a87efc6c746487b9996515adf477a908f33dbd47b5a0865e4e0e1c8b11e/nodejs_wheel_binaries-20.18.0-py2.py3-none-macosx_10_15_x86_64.whl", hash = "sha256:74273eab1c2423c04d034d3f707f517da32d3a2b20ca244b5667f3a4e38003ac", size = 45323646 },
-    { url = "https://files.pythonhosted.org/packages/03/b1/c07f24a759d7c9de5a7a56cdc60feb50739cdd4198822b077099698dcf35/nodejs_wheel_binaries-20.18.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:f95fb0989dfc54fd6932850e589000a8d6fc902527cebe7afd747696561d94b8", size = 44670947 },
-    { url = "https://files.pythonhosted.org/packages/81/ab/6e08de4aa76eefb144085a2c336488b4312516e0cfce1acee0e532450f8b/nodejs_wheel_binaries-20.18.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f62e38288d3a129b962dcba61b911d72b3d691935b8a2facfc0fa9433d9260b5", size = 49627957 },
-    { url = "https://files.pythonhosted.org/packages/24/65/03e263c82c2513a1f165ee7669e677ebbb95b90c141a8407fc5f79acbbd4/nodejs_wheel_binaries-20.18.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33b138288dbeb9aafc6d54f43fbca6545b37e8fd9cbb8f68275ff2a47d4fed07", size = 49821713 },
-    { url = "https://files.pythonhosted.org/packages/f5/f9/39bfe5d27c1cada5b940cb318865d71606ecf7ab6f4b5a696f76846b2217/nodejs_wheel_binaries-20.18.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb4009d4d6abfb6765fabb62f322efdc520b6930d041af1d7f64d58445ce3e91", size = 51929574 },
-    { url = "https://files.pythonhosted.org/packages/d0/90/921823227b4d49b9dadf9f38d072b5f28f883b0f83e697489de0f9c24674/nodejs_wheel_binaries-20.18.0-py2.py3-none-win_amd64.whl", hash = "sha256:51c0cecb429a111351a54346909e672a57b96233a363c79cc0a2bbdbfa397304", size = 33985033 },
+    { url = "https://files.pythonhosted.org/packages/a5/16/4cd2c0791567ee7b0203c3c6b59341854f0aeecb7315159d634c2b54b6d4/nodejs_wheel_binaries-22.11.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:00afada277fd6e945a74f881831aaf1bb7f853a15e15e8c998238ab88d327f6a", size = 50288370 },
+    { url = "https://files.pythonhosted.org/packages/c9/94/0677ace2cdd1a7ce61d7b2fedafb76818aaee94c14e817fd990f45f4f125/nodejs_wheel_binaries-22.11.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:f29471263d65a66520a04a0e74ff641a775df1135283f0b4d1826048932b289d", size = 51107812 },
+    { url = "https://files.pythonhosted.org/packages/d4/69/a3d4c761044de8cedd962b20ed4ae4d8a574b4e3e015f8a111549b815b62/nodejs_wheel_binaries-22.11.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd2ff0e20389f22927e311ccab69c1ecb34c3431fa809d1548e7000dc8248680", size = 56384790 },
+    { url = "https://files.pythonhosted.org/packages/d8/16/e34cf573096e7b25c85829e99f7e47d6cda0a6cdc4bd078d6bcdcb4dc979/nodejs_wheel_binaries-22.11.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9545cc43f1ba2c9f467f3444e9cd7f8db059933be1a5215135610dee5b38bf3", size = 56713946 },
+    { url = "https://files.pythonhosted.org/packages/a6/8a/ec28fe41159f51d9147d141d446305ce0494cb79966b79cd308c6bea9911/nodejs_wheel_binaries-22.11.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43a277cbabf4b68e0a4798578a4f17e5f518ada1f79a174bfde06eb2bb47e730", size = 58814450 },
+    { url = "https://files.pythonhosted.org/packages/68/69/f0dbbf72c8bdd9149ee00427c282d392da7fad9c53bd96f4844c2ab9021c/nodejs_wheel_binaries-22.11.0-py2.py3-none-win_amd64.whl", hash = "sha256:8310ab182ee159141e08c85bc07f11e67ac3044922e6e4958f4a8f3ba6860185", size = 39331516 },
 ]
 
 [[package]]
 name = "numpy"
-version = "2.1.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/1c/a2/40a76d357f168e9f9f06d6cc2c8e22dd5fb2bfbe63fe2c433057258c145a/numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee", size = 21150947 },
-    { url = "https://files.pythonhosted.org/packages/b5/d0/ba271ea9108d7278d3889a7eb38d77370a88713fb94339964e71ac184d4a/numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884", size = 13758184 },
-    { url = "https://files.pythonhosted.org/packages/7c/b9/5c6507439cd756201010f7937bf90712c2469052ae094584af14557dd64f/numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648", size = 5354091 },
-    { url = "https://files.pythonhosted.org/packages/60/21/7938cf724d9e84e45fb886f3fc794ab431d71facfebc261e3e9f19f3233a/numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d", size = 6887169 },
-    { url = "https://files.pythonhosted.org/packages/09/8d/42a124657f5d31902fca73921b25a0d022cead2b32ce7e6975762cd2995a/numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86", size = 13888165 },
-    { url = "https://files.pythonhosted.org/packages/fb/25/ba023652a39a2c127200e85aed975fc6119b421e2c348e5d0171e2046edb/numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7", size = 16326954 },
-    { url = "https://files.pythonhosted.org/packages/34/58/23e6b07fad492b7c47cf09cd8bad6983658f0f925b6c535fd008e3e86274/numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03", size = 16702916 },
-    { url = "https://files.pythonhosted.org/packages/91/24/37b5cf2dc7d385ac97f7b7fe50cba312abb70a2a5eac74c23af028811f73/numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466", size = 14384372 },
-    { url = "https://files.pythonhosted.org/packages/ea/ec/0f6d471058a01d1a05a50d2793898de1549280fa715a8537987ee866b5d9/numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb", size = 6535361 },
-    { url = "https://files.pythonhosted.org/packages/c2/3d/293cc5927f916a7bc6bf74da8f6defab63d1b13f0959d7e21878ad8a20d8/numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2", size = 12865501 },
-    { url = "https://files.pythonhosted.org/packages/aa/9c/9a6ec3ae89cd0648d419781284308f2956d2a61d932b5ac9682c956a171b/numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", size = 21154845 },
-    { url = "https://files.pythonhosted.org/packages/02/69/9f05c4ecc75fabf297b17743996371b4c3dfc4d92e15c5c38d8bb3db8d74/numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", size = 13789409 },
-    { url = "https://files.pythonhosted.org/packages/34/4e/f95c99217bf77bbfaaf660d693c10bd0dc03b6032d19316d316088c9e479/numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", size = 5352097 },
-    { url = "https://files.pythonhosted.org/packages/06/13/f5d87a497c16658e9af8920449b0b5692b469586b8231340c672962071c5/numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4", size = 6891195 },
-    { url = "https://files.pythonhosted.org/packages/6c/89/691ac07429ac061b344d5e37fa8e94be51a6017734aea15f2d9d7c6d119a/numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a", size = 13895153 },
-    { url = "https://files.pythonhosted.org/packages/23/69/538317f0d925095537745f12aced33be1570bbdc4acde49b33748669af96/numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1", size = 16338306 },
-    { url = "https://files.pythonhosted.org/packages/af/03/863fe7062c2106d3c151f7df9353f2ae2237c1dd6900f127a3eb1f24cb1b/numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2", size = 16710893 },
-    { url = "https://files.pythonhosted.org/packages/70/77/0ad9efe25482009873f9660d29a40a8c41a6f0e8b541195e3c95c70684c5/numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146", size = 14398048 },
-    { url = "https://files.pythonhosted.org/packages/3e/0f/e785fe75544db9f2b0bb1c181e13ceff349ce49753d807fd9672916aa06d/numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c", size = 6533458 },
-    { url = "https://files.pythonhosted.org/packages/d4/96/450054662295125af861d48d2c4bc081dadcf1974a879b2104613157aa62/numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9", size = 12870896 },
-    { url = "https://files.pythonhosted.org/packages/a0/7d/554a6838f37f3ada5a55f25173c619d556ae98092a6e01afb6e710501d70/numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b", size = 20848077 },
-    { url = "https://files.pythonhosted.org/packages/b0/29/cb48a402ea879e645b16218718f3f7d9588a77d674a9dcf22e4c43487636/numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db", size = 13493242 },
-    { url = "https://files.pythonhosted.org/packages/56/44/f899b0581766c230da42f751b7b8896d096640b19b312164c267e48d36cb/numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1", size = 5089219 },
-    { url = "https://files.pythonhosted.org/packages/79/8f/b987070d45161a7a4504afc67ed38544ed2c0ed5576263599a0402204a9c/numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426", size = 6620167 },
-    { url = "https://files.pythonhosted.org/packages/c4/a7/af3329fda3c3ec31d9b650e42bbcd3422fc62a765cbb1405fde4177a0996/numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0", size = 13604905 },
-    { url = "https://files.pythonhosted.org/packages/9b/b4/e3c7e6fab0f77fff6194afa173d1f2342073d91b1d3b4b30b17c3fb4407a/numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df", size = 16041825 },
-    { url = "https://files.pythonhosted.org/packages/e9/50/6828e66a78aa03147c111f84d55f33ce2dde547cb578d6744a3b06a0124b/numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366", size = 16409541 },
-    { url = "https://files.pythonhosted.org/packages/bf/72/66af7916d9c3c6dbfbc8acdd4930c65461e1953374a2bc43d00f948f004a/numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142", size = 14081134 },
-    { url = "https://files.pythonhosted.org/packages/dc/5a/59a67d84f33fe00ae74f0b5b69dd4f93a586a4aba7f7e19b54b2133db038/numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550", size = 6237784 },
-    { url = "https://files.pythonhosted.org/packages/4c/79/73735a6a5dad6059c085f240a4e74c9270feccd2bc66e4d31b5ca01d329c/numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e", size = 12568254 },
-    { url = "https://files.pythonhosted.org/packages/16/72/716fa1dbe92395a9a623d5049203ff8ddb0cfce65b9df9117c3696ccc011/numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", size = 20834690 },
-    { url = "https://files.pythonhosted.org/packages/1e/fb/3e85a39511586053b5c6a59a643879e376fae22230ebfef9cfabb0e032e2/numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", size = 13507474 },
-    { url = "https://files.pythonhosted.org/packages/35/eb/5677556d9ba13436dab51e129f98d4829d95cd1b6bd0e199c14485a4bdb9/numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", size = 5074742 },
-    { url = "https://files.pythonhosted.org/packages/3e/c5/6c5ef5ba41b65a7e51bed50dbf3e1483eb578055633dd013e811a28e96a1/numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", size = 6606787 },
-    { url = "https://files.pythonhosted.org/packages/08/ac/f2f29dd4fd325b379c7dc932a0ebab22f0e031dbe80b2f6019b291a3a544/numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", size = 13601333 },
-    { url = "https://files.pythonhosted.org/packages/44/26/63f5f4e5089654dfb858f4892215ed968cd1a68e6f4a83f9961f84f855cb/numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", size = 16038090 },
-    { url = "https://files.pythonhosted.org/packages/1d/21/015e0594de9c3a8d5edd24943d2bd23f102ec71aec026083f822f86497e2/numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", size = 16410865 },
-    { url = "https://files.pythonhosted.org/packages/df/01/c1bcf9e6025d79077fbf3f3ee503b50aa7bfabfcd8f4b54f5829f4c00f3f/numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", size = 14078077 },
-    { url = "https://files.pythonhosted.org/packages/ba/06/db9d127d63bd11591770ba9f3d960f8041e0f895184b9351d4b1b5b56983/numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", size = 6234904 },
-    { url = "https://files.pythonhosted.org/packages/a9/96/9f61f8f95b6e0ea0aa08633b704c75d1882bdcb331bdf8bfd63263b25b00/numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", size = 12561910 },
-    { url = "https://files.pythonhosted.org/packages/36/b8/033f627821784a48e8f75c218033471eebbaacdd933f8979c79637a1b44b/numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", size = 20857719 },
-    { url = "https://files.pythonhosted.org/packages/96/46/af5726fde5b74ed83f2f17a73386d399319b7ed4d51279fb23b721d0816d/numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", size = 13518826 },
-    { url = "https://files.pythonhosted.org/packages/db/6e/8ce677edf36da1c4dae80afe5529f47690697eb55b4864673af260ccea7b/numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", size = 5115036 },
-    { url = "https://files.pythonhosted.org/packages/6a/ba/3cce44fb1b8438042c11847048812a776f75ee0e7070179c22e4cfbf420c/numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", size = 6628641 },
-    { url = "https://files.pythonhosted.org/packages/59/c8/e722998720ccbd35ffbcf1d1b8ed0aa2304af88d3f1c38e06ebf983599b3/numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", size = 13574803 },
-    { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 },
-    { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 },
-    { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 },
-    { url = "https://files.pythonhosted.org/packages/73/c9/3e1d6bbe6d3d2e2c5a9483b24b2f29a229b323f62054278a3bba7fee11e5/numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952", size = 20981945 },
-    { url = "https://files.pythonhosted.org/packages/6e/62/989c4988bde1a8e08117fccc3bab73d2886421fb98cde597168714f3c54e/numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5", size = 6750558 },
-    { url = "https://files.pythonhosted.org/packages/53/b1/00ef9f30975f1312a53257f68e57b4513d14d537e03d507e2773a684b1e8/numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7", size = 16141552 },
-    { url = "https://files.pythonhosted.org/packages/c0/ec/0c04903b48dfea6be1d7b47ba70f98709fb7198fd970784a1400c391d522/numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e", size = 12789924 },
+version = "2.1.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f1/80/d572a4737626372915bca41c3afbfec9d173561a39a0a61bacbbfd1dafd4/numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", size = 21152472 },
+    { url = "https://files.pythonhosted.org/packages/6f/bb/7bfba10c791ae3bb6716da77ad85a82d5fac07fc96fb0023ef0571df9d20/numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", size = 13747967 },
+    { url = "https://files.pythonhosted.org/packages/da/d6/2df7bde35f0478455f0be5934877b3e5a505f587b00230f54a519a6b55a5/numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", size = 5354921 },
+    { url = "https://files.pythonhosted.org/packages/d1/bb/75b945874f931494891eac6ca06a1764d0e8208791f3addadb2963b83527/numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", size = 6888603 },
+    { url = "https://files.pythonhosted.org/packages/68/a7/fde73636f6498dbfa6d82fc336164635fe592f1ad0d13285fcb6267fdc1c/numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", size = 13889862 },
+    { url = "https://files.pythonhosted.org/packages/05/db/5d9c91b2e1e2e72be1369278f696356d44975befcae830daf2e667dcb54f/numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", size = 16328151 },
+    { url = "https://files.pythonhosted.org/packages/3e/6a/7eb732109b53ae64a29e25d7e68eb9d6611037f6354875497008a49e74d3/numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", size = 16704107 },
+    { url = "https://files.pythonhosted.org/packages/88/cc/278113b66a1141053cbda6f80e4200c6da06b3079c2d27bda1fde41f2c1f/numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4", size = 14385789 },
+    { url = "https://files.pythonhosted.org/packages/f5/69/eb20f5e1bfa07449bc67574d2f0f7c1e6b335fb41672e43861a7727d85f2/numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", size = 6536706 },
+    { url = "https://files.pythonhosted.org/packages/8e/8b/1c131ab5a94c1086c289c6e1da1d843de9dbd95fe5f5ee6e61904c9518e2/numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", size = 12864165 },
+    { url = "https://files.pythonhosted.org/packages/ad/81/c8167192eba5247593cd9d305ac236847c2912ff39e11402e72ae28a4985/numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", size = 21156252 },
+    { url = "https://files.pythonhosted.org/packages/da/74/5a60003fc3d8a718d830b08b654d0eea2d2db0806bab8f3c2aca7e18e010/numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", size = 13784119 },
+    { url = "https://files.pythonhosted.org/packages/47/7c/864cb966b96fce5e63fcf25e1e4d957fe5725a635e5f11fe03f39dd9d6b5/numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", size = 5352978 },
+    { url = "https://files.pythonhosted.org/packages/09/ac/61d07930a4993dd9691a6432de16d93bbe6aa4b1c12a5e573d468eefc1ca/numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", size = 6892570 },
+    { url = "https://files.pythonhosted.org/packages/27/2f/21b94664f23af2bb52030653697c685022119e0dc93d6097c3cb45bce5f9/numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", size = 13896715 },
+    { url = "https://files.pythonhosted.org/packages/7a/f0/80811e836484262b236c684a75dfc4ba0424bc670e765afaa911468d9f39/numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", size = 16339644 },
+    { url = "https://files.pythonhosted.org/packages/fa/81/ce213159a1ed8eb7d88a2a6ef4fbdb9e4ffd0c76b866c350eb4e3c37e640/numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", size = 16712217 },
+    { url = "https://files.pythonhosted.org/packages/7d/84/4de0b87d5a72f45556b2a8ee9fc8801e8518ec867fc68260c1f5dcb3903f/numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", size = 14399053 },
+    { url = "https://files.pythonhosted.org/packages/7e/1c/e5fabb9ad849f9d798b44458fd12a318d27592d4bc1448e269dec070ff04/numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", size = 6534741 },
+    { url = "https://files.pythonhosted.org/packages/1e/48/a9a4b538e28f854bfb62e1dea3c8fea12e90216a276c7777ae5345ff29a7/numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", size = 12869487 },
+    { url = "https://files.pythonhosted.org/packages/8a/f0/385eb9970309643cbca4fc6eebc8bb16e560de129c91258dfaa18498da8b/numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", size = 20849658 },
+    { url = "https://files.pythonhosted.org/packages/54/4a/765b4607f0fecbb239638d610d04ec0a0ded9b4951c56dc68cef79026abf/numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", size = 13492258 },
+    { url = "https://files.pythonhosted.org/packages/bd/a7/2332679479c70b68dccbf4a8eb9c9b5ee383164b161bee9284ac141fbd33/numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", size = 5090249 },
+    { url = "https://files.pythonhosted.org/packages/c1/67/4aa00316b3b981a822c7a239d3a8135be2a6945d1fd11d0efb25d361711a/numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", size = 6621704 },
+    { url = "https://files.pythonhosted.org/packages/5e/da/1a429ae58b3b6c364eeec93bf044c532f2ff7b48a52e41050896cf15d5b1/numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", size = 13606089 },
+    { url = "https://files.pythonhosted.org/packages/9e/3e/3757f304c704f2f0294a6b8340fcf2be244038be07da4cccf390fa678a9f/numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", size = 16043185 },
+    { url = "https://files.pythonhosted.org/packages/43/97/75329c28fea3113d00c8d2daf9bc5828d58d78ed661d8e05e234f86f0f6d/numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", size = 16410751 },
+    { url = "https://files.pythonhosted.org/packages/ad/7a/442965e98b34e0ae9da319f075b387bcb9a1e0658276cc63adb8c9686f7b/numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", size = 14082705 },
+    { url = "https://files.pythonhosted.org/packages/ac/b6/26108cf2cfa5c7e03fb969b595c93131eab4a399762b51ce9ebec2332e80/numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", size = 6239077 },
+    { url = "https://files.pythonhosted.org/packages/a6/84/fa11dad3404b7634aaab50733581ce11e5350383311ea7a7010f464c0170/numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", size = 12566858 },
+    { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 },
+    { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 },
+    { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 },
+    { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 },
+    { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 },
+    { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 },
+    { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 },
+    { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 },
+    { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 },
+    { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 },
+    { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 },
+    { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 },
+    { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 },
+    { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 },
+    { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 },
+    { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 },
+    { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 },
+    { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 },
+    { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 },
+    { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 },
+    { url = "https://files.pythonhosted.org/packages/00/e7/8d8bb791b62586cc432ecbb70632b4f23b7b7c88df41878de7528264f6d7/numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", size = 20983893 },
+    { url = "https://files.pythonhosted.org/packages/5e/f3/cb8118a044b5007586245a650360c9f5915b2f4232dd7658bb7a63dd1d02/numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", size = 6752501 },
+    { url = "https://files.pythonhosted.org/packages/53/f5/365b46439b518d2ec6ebb880cc0edf90f225145dfd4db7958334f7164530/numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", size = 16142601 },
+    { url = "https://files.pythonhosted.org/packages/03/c2/d1fee6ba999aa7cd41ca6856937f2baaf604c3eec1565eae63451ec31e5e/numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", size = 12771397 },
+]
+
+[[package]]
+name = "nvidia-cublas-cu12"
+version = "12.4.5.8"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-cuda-cupti-cu12"
+version = "12.4.127"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-cuda-nvrtc-cu12"
+version = "12.4.127"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-cuda-runtime-cu12"
+version = "12.4.127"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-cudnn-cu12"
+version = "9.1.0.70"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-cublas-cu12", marker = "sys_platform != 'linux'" },
+]
+
+[[package]]
+name = "nvidia-cufft-cu12"
+version = "11.2.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'linux'" },
 ]
 
+[[package]]
+name = "nvidia-curand-cu12"
+version = "10.3.5.147"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-cusolver-cu12"
+version = "11.6.1.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-cublas-cu12", marker = "sys_platform != 'linux'" },
+    { name = "nvidia-cusparse-cu12", marker = "sys_platform != 'linux'" },
+    { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'linux'" },
+]
+
+[[package]]
+name = "nvidia-cusparse-cu12"
+version = "12.3.1.170"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'linux'" },
+]
+
+[[package]]
+name = "nvidia-nccl-cu12"
+version = "2.21.5"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-nvjitlink-cu12"
+version = "12.4.127"
+source = { registry = "https://pypi.org/simple" }
+
+[[package]]
+name = "nvidia-nvtx-cu12"
+version = "12.4.127"
+source = { registry = "https://pypi.org/simple" }
+
 [[package]]
 name = "optype"
 version = "0.7.0.dev0"
@@ -292,9 +502,21 @@ numpy = [
     { name = "numpy" },
 ]
 
-[package.dependency-groups]
+[package.dev-dependencies]
+array-api = [
+    { name = "array-api-compat" },
+    { name = "array-api-strict" },
+    { name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and sys_platform != 'linux'" },
+    { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" },
+]
+common = [
+    { name = "numpy" },
+    { name = "typing-extensions" },
+]
 dev = [
-    { name = "basedmypy" },
+    { name = "array-api-compat" },
+    { name = "array-api-strict" },
+    { name = "basedmypy", extra = ["faster-cache"] },
     { name = "basedpyright" },
     { name = "beartype" },
     { name = "codespell" },
@@ -303,9 +525,44 @@ dev = [
     { name = "pytest" },
     { name = "ruff" },
     { name = "sp-repo-review", extra = ["cli"] },
+    { name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and sys_platform != 'linux'" },
+    { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" },
     { name = "tox" },
     { name = "typing-extensions" },
 ]
+lint = [
+    { name = "codespell" },
+    { name = "ruff" },
+    { name = "sp-repo-review", extra = ["cli"] },
+]
+test = [
+    { name = "array-api-compat" },
+    { name = "array-api-strict" },
+    { name = "beartype" },
+    { name = "numpy" },
+    { name = "pytest" },
+    { name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and sys_platform != 'linux'" },
+    { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" },
+    { name = "typing-extensions" },
+]
+type = [
+    { name = "basedmypy", extra = ["faster-cache"] },
+    { name = "basedpyright" },
+    { name = "numpy" },
+    { name = "typing-extensions" },
+]
+typetest = [
+    { name = "array-api-compat" },
+    { name = "array-api-strict" },
+    { name = "basedmypy", extra = ["faster-cache"] },
+    { name = "basedpyright" },
+    { name = "beartype" },
+    { name = "numpy" },
+    { name = "pytest" },
+    { name = "torch", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13' and sys_platform != 'linux'" },
+    { name = "torch", version = "2.5.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux'" },
+    { name = "typing-extensions" },
+]
 
 [package.metadata]
 requires-dist = [
@@ -313,28 +570,120 @@ requires-dist = [
     { name = "typing-extensions", marker = "python_full_version < '3.13'", specifier = ">=4.10" },
 ]
 
-[package.metadata.dependency-groups]
+[package.metadata.requires-dev]
+array-api = [
+    { name = "array-api-compat", specifier = ">=1.9.1" },
+    { name = "array-api-strict", specifier = ">=2.1.3" },
+    { name = "torch", marker = "python_full_version < '3.13' and sys_platform != 'linux'", specifier = ">=2.5.1" },
+    { name = "torch", marker = "sys_platform == 'linux'", specifier = ">=2.5.1", index = "https://download.pytorch.org/whl/cpu" },
+]
+common = [
+    { name = "numpy", specifier = ">=1.24" },
+    { name = "typing-extensions", specifier = ">=4.10" },
+]
 dev = [
-    { name = "basedmypy", specifier = ">=2.6.0" },
-    { name = "basedpyright", specifier = ">=1.19.1" },
+    { name = "array-api-compat", specifier = ">=1.9.1" },
+    { name = "array-api-strict", specifier = ">=2.1.3" },
+    { name = "basedmypy", extras = ["faster-cache"], specifier = ">=2.7.0" },
+    { name = "basedpyright", specifier = ">=1.21.0" },
     { name = "beartype", specifier = ">=0.19.0" },
     { name = "codespell", specifier = ">=2.3.0" },
-    { name = "numpy" },
+    { name = "numpy", specifier = ">=1.24" },
     { name = "pre-commit", specifier = ">=4.0.1" },
     { name = "pytest", specifier = ">=8.3.3" },
-    { name = "ruff", specifier = ">=0.7.1" },
+    { name = "ruff", specifier = ">=0.7.3" },
     { name = "sp-repo-review", extras = ["cli"], specifier = ">=2024.8.19" },
+    { name = "torch", marker = "python_full_version < '3.13' and sys_platform != 'linux'", specifier = ">=2.5.1" },
+    { name = "torch", marker = "sys_platform == 'linux'", specifier = ">=2.5.1", index = "https://download.pytorch.org/whl/cpu" },
     { name = "tox", specifier = ">=4.23.2" },
-    { name = "typing-extensions" },
+    { name = "typing-extensions", specifier = ">=4.10" },
+]
+lint = [
+    { name = "codespell", specifier = ">=2.3.0" },
+    { name = "ruff", specifier = ">=0.7.3" },
+    { name = "sp-repo-review", extras = ["cli"], specifier = ">=2024.8.19" },
+]
+test = [
+    { name = "array-api-compat", specifier = ">=1.9.1" },
+    { name = "array-api-strict", specifier = ">=2.1.3" },
+    { name = "beartype", specifier = ">=0.19.0" },
+    { name = "numpy", specifier = ">=1.24" },
+    { name = "pytest", specifier = ">=8.3.3" },
+    { name = "torch", marker = "python_full_version < '3.13' and sys_platform != 'linux'", specifier = ">=2.5.1" },
+    { name = "torch", marker = "sys_platform == 'linux'", specifier = ">=2.5.1", index = "https://download.pytorch.org/whl/cpu" },
+    { name = "typing-extensions", specifier = ">=4.10" },
+]
+type = [
+    { name = "basedmypy", extras = ["faster-cache"], specifier = ">=2.7.0" },
+    { name = "basedpyright", specifier = ">=1.21.0" },
+    { name = "numpy", specifier = ">=1.24" },
+    { name = "typing-extensions", specifier = ">=4.10" },
+]
+typetest = [
+    { name = "array-api-compat", specifier = ">=1.9.1" },
+    { name = "array-api-strict", specifier = ">=2.1.3" },
+    { name = "basedmypy", extras = ["faster-cache"], specifier = ">=2.7.0" },
+    { name = "basedpyright", specifier = ">=1.21.0" },
+    { name = "beartype", specifier = ">=0.19.0" },
+    { name = "numpy", specifier = ">=1.24" },
+    { name = "pytest", specifier = ">=8.3.3" },
+    { name = "torch", marker = "python_full_version < '3.13' and sys_platform != 'linux'", specifier = ">=2.5.1" },
+    { name = "torch", marker = "sys_platform == 'linux'", specifier = ">=2.5.1", index = "https://download.pytorch.org/whl/cpu" },
+    { name = "typing-extensions", specifier = ">=4.10" },
+]
+
+[[package]]
+name = "orjson"
+version = "3.10.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/3a/10320029954badc7eaa338a15ee279043436f396e965dafc169610e4933f/orjson-3.10.11.tar.gz", hash = "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef", size = 5444879 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3e/63/f7d412e09f6e2c4e2562ddc44e86f2316a7ce9d7f353afa7cbce4f6a78d5/orjson-3.10.11-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6dade64687f2bd7c090281652fe18f1151292d567a9302b34c2dbb92a3872f1f", size = 266434 },
+    { url = "https://files.pythonhosted.org/packages/a2/6a/3dfcd3a8c0e588581c8d1f3d9002cca970432da8a8096c1a42b99914a34d/orjson-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f07c550a6ccd2b9290849b22316a609023ed851a87ea888c0456485a7d196a", size = 151884 },
+    { url = "https://files.pythonhosted.org/packages/41/02/8981bc5ccbc04a2bd49cd86224d5b1e2c7417fb33e83590c66c3a028ede5/orjson-3.10.11-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd9a187742d3ead9df2e49240234d728c67c356516cf4db018833a86f20ec18c", size = 167371 },
+    { url = "https://files.pythonhosted.org/packages/df/3f/772a12a417444eccc54fa597955b689848eb121d5e43dd7da9f6658c314d/orjson-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77b0fed6f209d76c1c39f032a70df2d7acf24b1812ca3e6078fd04e8972685a3", size = 154367 },
+    { url = "https://files.pythonhosted.org/packages/8a/63/d0d6ba28410ec603fc31726a49dc782c72c0a64f4cd0a6734a6d8bc07a4a/orjson-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63fc9d5fe1d4e8868f6aae547a7b8ba0a2e592929245fff61d633f4caccdcdd6", size = 165726 },
+    { url = "https://files.pythonhosted.org/packages/97/6e/d291bf382173af7788b368e4c22d02c7bdb9b7ac29b83e92930841321c16/orjson-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65cd3e3bb4fbb4eddc3c1e8dce10dc0b73e808fcb875f9fab40c81903dd9323e", size = 142522 },
+    { url = "https://files.pythonhosted.org/packages/6d/3b/7364c10fcadf7c08e3658fe7103bf3b0408783f91022be4691fbe0b5ba1d/orjson-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f67c570602300c4befbda12d153113b8974a3340fdcf3d6de095ede86c06d92", size = 146931 },
+    { url = "https://files.pythonhosted.org/packages/95/8c/43f454e642cc85ef845cda6efcfddc6b5fe46b897b692412022012e1272c/orjson-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1f39728c7f7d766f1f5a769ce4d54b5aaa4c3f92d5b84817053cc9995b977acc", size = 142900 },
+    { url = "https://files.pythonhosted.org/packages/bb/29/ca24efe043501b4a4584d728fdc65af5cfc070ab9021a07fb195bce98919/orjson-3.10.11-cp310-none-win32.whl", hash = "sha256:1789d9db7968d805f3d94aae2c25d04014aae3a2fa65b1443117cd462c6da647", size = 144456 },
+    { url = "https://files.pythonhosted.org/packages/b7/ec/f15dc012928459cfb96ed86178d92fddb5c01847f2c53fd8be2fa62dee6c/orjson-3.10.11-cp310-none-win_amd64.whl", hash = "sha256:5576b1e5a53a5ba8f8df81872bb0878a112b3ebb1d392155f00f54dd86c83ff6", size = 136442 },
+    { url = "https://files.pythonhosted.org/packages/1e/25/c869a1fbd481dcb02c70032fd6a7243de7582bc48c7cae03d6f0985a11c0/orjson-3.10.11-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1444f9cb7c14055d595de1036f74ecd6ce15f04a715e73f33bb6326c9cef01b6", size = 266432 },
+    { url = "https://files.pythonhosted.org/packages/6a/a4/2307155ee92457d28345308f7d8c0e712348404723025613adeffcb531d0/orjson-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdec57fe3b4bdebcc08a946db3365630332dbe575125ff3d80a3272ebd0ddafe", size = 151884 },
+    { url = "https://files.pythonhosted.org/packages/aa/82/daf1b2596dd49fe44a1bd92367568faf6966dcb5d7f99fd437c3d0dc2de6/orjson-3.10.11-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4eed32f33a0ea6ef36ccc1d37f8d17f28a1d6e8eefae5928f76aff8f1df85e67", size = 167371 },
+    { url = "https://files.pythonhosted.org/packages/63/a8/680578e4589be5fdcfe0186bdd7dc6fe4a39d30e293a9da833cbedd5a56e/orjson-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80df27dd8697242b904f4ea54820e2d98d3f51f91e97e358fc13359721233e4b", size = 154368 },
+    { url = "https://files.pythonhosted.org/packages/6e/ce/9cb394b5b01ef34579eeca6d704b21f97248f607067ce95a24ba9ea2698e/orjson-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:705f03cee0cb797256d54de6695ef219e5bc8c8120b6654dd460848d57a9af3d", size = 165725 },
+    { url = "https://files.pythonhosted.org/packages/49/24/55eeb05cfb36b9e950d05743e6f6fdb7d5f33ca951a27b06ea6d03371aed/orjson-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03246774131701de8e7059b2e382597da43144a9a7400f178b2a32feafc54bd5", size = 142522 },
+    { url = "https://files.pythonhosted.org/packages/94/0c/3a6a289e56dcc9fe67dc6b6d33c91dc5491f9ec4a03745efd739d2acf0ff/orjson-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8b5759063a6c940a69c728ea70d7c33583991c6982915a839c8da5f957e0103a", size = 146934 },
+    { url = "https://files.pythonhosted.org/packages/1d/5c/a08c0e90a91e2526029a4681ff8c6fc4495b8bab77d48801144e378c7da9/orjson-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:677f23e32491520eebb19c99bb34675daf5410c449c13416f7f0d93e2cf5f981", size = 142904 },
+    { url = "https://files.pythonhosted.org/packages/2c/c9/710286a60b14e88288ca014d43befb08bb0a4a6a0f51b875f8c2f05e8205/orjson-3.10.11-cp311-none-win32.whl", hash = "sha256:a11225d7b30468dcb099498296ffac36b4673a8398ca30fdaec1e6c20df6aa55", size = 144459 },
+    { url = "https://files.pythonhosted.org/packages/7d/68/ef7b920e0a09e02b1a30daca1b4864938463797995c2fabe457c1500220a/orjson-3.10.11-cp311-none-win_amd64.whl", hash = "sha256:df8c677df2f9f385fcc85ab859704045fa88d4668bc9991a527c86e710392bec", size = 136444 },
+    { url = "https://files.pythonhosted.org/packages/78/f2/a712dbcef6d84ff53e13056e7dc69d9d4844bd1e35e51b7431679ddd154d/orjson-3.10.11-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:360a4e2c0943da7c21505e47cf6bd725588962ff1d739b99b14e2f7f3545ba51", size = 266505 },
+    { url = "https://files.pythonhosted.org/packages/94/54/53970831786d71f98fdc13c0f80451324c9b5c20fbf42f42ef6147607ee7/orjson-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:496e2cb45de21c369079ef2d662670a4892c81573bcc143c4205cae98282ba97", size = 151745 },
+    { url = "https://files.pythonhosted.org/packages/35/38/482667da1ca7ef95d44d4d2328257a144fd2752383e688637c53ed474d2a/orjson-3.10.11-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7dfa8db55c9792d53c5952900c6a919cfa377b4f4534c7a786484a6a4a350c19", size = 167274 },
+    { url = "https://files.pythonhosted.org/packages/23/2f/5bb0a03e819781d82dadb733fde8ebbe20d1777d1a33715d45ada4d82ce8/orjson-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f3382415747e0dbda9dade6f1e1a01a9d37f630d8c9049a8ed0e385b7a90c0", size = 154605 },
+    { url = "https://files.pythonhosted.org/packages/49/e9/14cc34d45c7bd51665aff9b1bb6b83475a61c52edb0d753fffe1adc97764/orjson-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f35a1b9f50a219f470e0e497ca30b285c9f34948d3c8160d5ad3a755d9299433", size = 165874 },
+    { url = "https://files.pythonhosted.org/packages/7b/61/c2781ecf90f99623e97c67a31e8553f38a1ecebaf3189485726ac8641576/orjson-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f3b7c5803138e67028dde33450e054c87e0703afbe730c105f1fcd873496d5", size = 142813 },
+    { url = "https://files.pythonhosted.org/packages/4d/4f/18c83f78b501b6608569b1610fcb5a25c9bb9ab6a7eb4b3a55131e0fba37/orjson-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f91d9eb554310472bd09f5347950b24442600594c2edc1421403d7610a0998fd", size = 146762 },
+    { url = "https://files.pythonhosted.org/packages/ba/19/ea80d5b575abd3f76a790409c2b7b8a60f3fc9447965c27d09613b8bddf4/orjson-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfbb2d460a855c9744bbc8e36f9c3a997c4b27d842f3d5559ed54326e6911f9b", size = 143186 },
+    { url = "https://files.pythonhosted.org/packages/2c/f5/d835fee01a0284d4b78effc24d16e7609daac2ff6b6851ca1bdd3b6194fc/orjson-3.10.11-cp312-none-win32.whl", hash = "sha256:d4a62c49c506d4d73f59514986cadebb7e8d186ad510c518f439176cf8d5359d", size = 144489 },
+    { url = "https://files.pythonhosted.org/packages/03/60/748e0e205060dec74328dfd835e47902eb5522ae011766da76bfff64e2f4/orjson-3.10.11-cp312-none-win_amd64.whl", hash = "sha256:f1eec3421a558ff7a9b010a6c7effcfa0ade65327a71bb9b02a1c3b77a247284", size = 136614 },
+    { url = "https://files.pythonhosted.org/packages/13/92/400970baf46b987c058469e9e779fb7a40d54a5754914d3634cca417e054/orjson-3.10.11-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c46294faa4e4d0eb73ab68f1a794d2cbf7bab33b1dda2ac2959ffb7c61591899", size = 266402 },
+    { url = "https://files.pythonhosted.org/packages/3c/fa/f126fc2d817552bd1f67466205abdcbff64eab16f6844fe6df2853528675/orjson-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52e5834d7d6e58a36846e059d00559cb9ed20410664f3ad156cd2cc239a11230", size = 140826 },
+    { url = "https://files.pythonhosted.org/packages/ad/18/9b9664d7d4af5b4fe9fe6600b7654afc0684bba528260afdde10c4a530aa/orjson-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2fc947e5350fdce548bfc94f434e8760d5cafa97fb9c495d2fef6757aa02ec0", size = 142593 },
+    { url = "https://files.pythonhosted.org/packages/20/f9/a30c68f12778d5e58e6b5cdd26f86ee2d0babce1a475073043f46fdd8402/orjson-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0efabbf839388a1dab5b72b5d3baedbd6039ac83f3b55736eb9934ea5494d258", size = 146777 },
+    { url = "https://files.pythonhosted.org/packages/f2/97/12047b0c0e9b391d589fb76eb40538f522edc664f650f8e352fdaaf77ff5/orjson-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3f29634260708c200c4fe148e42b4aae97d7b9fee417fbdd74f8cfc265f15b0", size = 142961 },
+    { url = "https://files.pythonhosted.org/packages/a4/97/d904e26c1cabf2dd6ab1b0909e9b790af28a7f0fcb9d8378d7320d4869eb/orjson-3.10.11-cp313-none-win32.whl", hash = "sha256:1a1222ffcee8a09476bbdd5d4f6f33d06d0d6642df2a3d78b7a195ca880d669b", size = 144486 },
+    { url = "https://files.pythonhosted.org/packages/42/62/3760bd1e6e949321d99bab238d08db2b1266564d2f708af668f57109bb36/orjson-3.10.11-cp313-none-win_amd64.whl", hash = "sha256:bc274ac261cc69260913b2d1610760e55d3c0801bb3457ba7b9004420b6b4270", size = 136361 },
 ]
 
 [[package]]
 name = "packaging"
-version = "24.1"
+version = "24.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 },
+    { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
 ]
 
 [[package]]
@@ -478,16 +827,16 @@ cli = [
 
 [[package]]
 name = "rich"
-version = "13.9.2"
+version = "13.9.4"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "markdown-it-py" },
     { name = "pygments" },
     { name = "typing-extensions", marker = "python_full_version < '3.11'" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/aa/9e/1784d15b057b0075e5136445aaea92d23955aad2c93eaede673718a40d95/rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", size = 222843 }
+sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/67/91/5474b84e505a6ccc295b2d322d90ff6aa0746745717839ee0c5fb4fdcceb/rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1", size = 242117 },
+    { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
 ]
 
 [[package]]
@@ -506,27 +855,36 @@ wheels = [
 
 [[package]]
 name = "ruff"
-version = "0.7.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a6/21/5c6e05e0fd3fbb41be4fb92edbc9a04de70baf60adb61435ce0c6b8c3d55/ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4", size = 3181670 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/65/45/8a20a9920175c9c4892b2420f80ff3cf14949cf3067118e212f9acd9c908/ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89", size = 10389268 },
-    { url = "https://files.pythonhosted.org/packages/1b/d3/2f8382db2cf4f9488e938602e33e36287f9d26cb283aa31f11c31297ce79/ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35", size = 10188348 },
-    { url = "https://files.pythonhosted.org/packages/a2/31/7d14e2a88da351200f844b7be889a0845d9e797162cf76b136d21b832a23/ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99", size = 9841448 },
-    { url = "https://files.pythonhosted.org/packages/db/99/738cafdc768eceeca0bd26c6f03e213aa91203d2278e1d95b1c31c4ece41/ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca", size = 10674864 },
-    { url = "https://files.pythonhosted.org/packages/fe/12/bcf2836b50eab53c65008383e7d55201e490d75167c474f14a16e1af47d2/ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250", size = 10192105 },
-    { url = "https://files.pythonhosted.org/packages/2b/71/261d5d668bf98b6c44e89bfb5dfa4cb8cb6c8b490a201a3d8030e136ea4f/ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c", size = 11194144 },
-    { url = "https://files.pythonhosted.org/packages/90/1f/0926d18a3b566fa6e7b3b36093088e4ffef6b6ba4ea85a462d9a93f7e35c/ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565", size = 11917066 },
-    { url = "https://files.pythonhosted.org/packages/cd/a8/9fac41f128b6a44ab4409c1493430b4ee4b11521e8aeeca19bfe1ce851f9/ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7", size = 11458821 },
-    { url = "https://files.pythonhosted.org/packages/25/cd/59644168f086ab13fe4e02943b9489a0aa710171f66b178e179df5383554/ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a", size = 12700379 },
-    { url = "https://files.pythonhosted.org/packages/fb/30/3bac63619eb97174661829c07fc46b2055a053dee72da29d7c304c1cd2c0/ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad", size = 11019813 },
-    { url = "https://files.pythonhosted.org/packages/4b/af/f567b885b5cb3bcdbcca3458ebf210cc8c9c7a9f61c332d3c2a050c3b21e/ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112", size = 10662146 },
-    { url = "https://files.pythonhosted.org/packages/bc/ad/eb930d3ad117a9f2f7261969c21559ebd82bb13b6e8001c7caed0d44be5f/ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378", size = 10256911 },
-    { url = "https://files.pythonhosted.org/packages/20/d5/af292ce70a016fcec792105ca67f768b403dd480a11888bc1f418fed0dd5/ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8", size = 10767488 },
-    { url = "https://files.pythonhosted.org/packages/24/85/cc04a3bd027f433bebd2a097e63b3167653c079f7f13d8f9a1178e693412/ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd", size = 11093368 },
-    { url = "https://files.pythonhosted.org/packages/0b/fb/c39cbf32d1f3e318674b8622f989417231794926b573f76dd4d0ca49f0f1/ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9", size = 8594180 },
-    { url = "https://files.pythonhosted.org/packages/5a/71/ec8cdea34ecb90c830ca60d54ac7b509a7b5eab50fae27e001d4470fe813/ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307", size = 9419751 },
-    { url = "https://files.pythonhosted.org/packages/79/7b/884553415e9f0a9bf358ed52fb68b934e67ef6c5a62397ace924a1afdf9a/ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37", size = 8717402 },
+version = "0.7.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4b/06/09d1276df977eece383d0ed66052fc24ec4550a61f8fbc0a11200e690496/ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313", size = 3243664 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c0/56/933d433c2489e4642487b835f53dd9ff015fb3d8fa459b09bb2ce42d7c4b/ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344", size = 10372090 },
+    { url = "https://files.pythonhosted.org/packages/20/ea/1f0a22a6bcdd3fc26c73f63a025d05bd565901b729d56bcb093c722a6c4c/ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0", size = 10190037 },
+    { url = "https://files.pythonhosted.org/packages/16/74/aca75666e0d481fe394e76a8647c44ea919087748024924baa1a17371e3e/ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9", size = 9811998 },
+    { url = "https://files.pythonhosted.org/packages/20/a1/cf446a0d7f78ea1f0bd2b9171c11dfe746585c0c4a734b25966121eb4f5d/ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5", size = 10620626 },
+    { url = "https://files.pythonhosted.org/packages/cd/c1/82b27d09286ae855f5d03b1ad37cf243f21eb0081732d4d7b0d658d439cb/ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299", size = 10177598 },
+    { url = "https://files.pythonhosted.org/packages/b9/42/c0acac22753bf74013d035a5ef6c5c4c40ad4d6686bfb3fda7c6f37d9b37/ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e", size = 11171963 },
+    { url = "https://files.pythonhosted.org/packages/43/18/bb0befb7fb9121dd9009e6a72eb98e24f1bacb07c6f3ecb55f032ba98aed/ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29", size = 11856157 },
+    { url = "https://files.pythonhosted.org/packages/5e/91/04e98d7d6e32eca9d1372be595f9abc7b7f048795e32eb2edbd8794d50bd/ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5", size = 11440331 },
+    { url = "https://files.pythonhosted.org/packages/f5/dc/3fe99f2ce10b76d389041a1b9f99e7066332e479435d4bebcceea16caff5/ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67", size = 12725354 },
+    { url = "https://files.pythonhosted.org/packages/43/7b/1daa712de1c5bc6cbbf9fa60e9c41cc48cda962dc6d2c4f2a224d2c3007e/ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2", size = 11010091 },
+    { url = "https://files.pythonhosted.org/packages/b6/db/1227a903587432eb569e57a95b15a4f191a71fe315cde4c0312df7bc85da/ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d", size = 10610687 },
+    { url = "https://files.pythonhosted.org/packages/db/e2/dc41ee90c3085aadad4da614d310d834f641aaafddf3dfbba08210c616ce/ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2", size = 10254843 },
+    { url = "https://files.pythonhosted.org/packages/6f/09/5f6cac1c91542bc5bd33d40b4c13b637bf64d7bb29e091dadb01b62527fe/ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2", size = 10730962 },
+    { url = "https://files.pythonhosted.org/packages/d3/42/89a4b9a24ef7d00269e24086c417a006f9a3ffeac2c80f2629eb5ce140ee/ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16", size = 11101907 },
+    { url = "https://files.pythonhosted.org/packages/b0/5c/efdb4777686683a8edce94ffd812783bddcd3d2454d38c5ac193fef7c500/ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc", size = 8611095 },
+    { url = "https://files.pythonhosted.org/packages/bb/b8/28fbc6a4efa50178f973972d1c84b2d0a33cdc731588522ab751ac3da2f5/ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088", size = 9418283 },
+    { url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
+]
+
+[[package]]
+name = "setuptools"
+version = "75.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ed/22/a438e0caa4576f8c383fa4d35f1cc01655a46c75be358960d815bfbb12bd/setuptools-75.3.0.tar.gz", hash = "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686", size = 1351577 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/90/12/282ee9bce8b58130cb762fbc9beabd531549952cac11fc56add11dcb7ea0/setuptools-75.3.0-py3-none-any.whl", hash = "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd", size = 1251070 },
 ]
 
 [[package]]
@@ -548,6 +906,18 @@ cli = [
     { name = "repo-review", extra = ["cli"] },
 ]
 
+[[package]]
+name = "sympy"
+version = "1.13.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mpmath" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 },
+]
+
 [[package]]
 name = "tomli"
 version = "2.0.2"
@@ -557,6 +927,69 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/cf/db/ce8eda256fa131af12e0a76d481711abe4681b6923c27efb9a255c9e4594/tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", size = 13237 },
 ]
 
+[[package]]
+name = "torch"
+version = "2.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+    "python_full_version < '3.12' and sys_platform != 'linux'",
+    "python_full_version >= '3.12' and sys_platform != 'linux'",
+]
+dependencies = [
+    { name = "filelock", marker = "sys_platform != 'linux'" },
+    { name = "fsspec", marker = "sys_platform != 'linux'" },
+    { name = "jinja2", marker = "sys_platform != 'linux'" },
+    { name = "networkx", marker = "sys_platform != 'linux'" },
+    { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "setuptools", marker = "python_full_version >= '3.12' and sys_platform != 'linux'" },
+    { name = "sympy", marker = "sys_platform != 'linux'" },
+    { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux' and sys_platform != 'linux'" },
+    { name = "typing-extensions", marker = "sys_platform != 'linux'" },
+]
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a5/13/1eb674c8efbd04d71e4a157ceba991904f633e009a584dd65dccbafbb648/torch-2.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:32a037bd98a241df6c93e4c789b683335da76a2ac142c0973675b715102dc5fa", size = 203088048 },
+    { url = "https://files.pythonhosted.org/packages/a9/9d/e0860474ee0ff8f6ef2c50ec8f71a250f38d78a9b9df9fd241ad3397a65b/torch-2.5.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:23d062bf70776a3d04dbe74db950db2a5245e1ba4f27208a87f0d743b0d06e86", size = 63877046 },
+    { url = "https://files.pythonhosted.org/packages/0d/4a/e51420d46cfc90562e85af2fee912237c662ab31140ab179e49bd69401d6/torch-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:603c52d2fe06433c18b747d25f5c333f9c1d58615620578c326d66f258686f9a", size = 203098237 },
+    { url = "https://files.pythonhosted.org/packages/d0/db/5d9cbfbc7968d79c5c09a0bc0bc3735da079f2fd07cc10498a62b320a480/torch-2.5.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:31f8c39660962f9ae4eeec995e3049b5492eb7360dd4f07377658ef4d728fa4c", size = 63884466 },
+    { url = "https://files.pythonhosted.org/packages/5f/ba/607d013b55b9fd805db2a5c2662ec7551f1910b4eef39653eeaba182c5b2/torch-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:73e58e78f7d220917c5dbfad1a40e09df9929d3b95d25e57d9f8558f84c9a11c", size = 203046841 },
+    { url = "https://files.pythonhosted.org/packages/57/6c/bf52ff061da33deb9f94f4121fde7ff3058812cb7d2036c97bc167793bd1/torch-2.5.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:8c712df61101964eb11910a846514011f0b6f5920c55dbf567bff8a34163d5b1", size = 63858109 },
+]
+
+[[package]]
+name = "torch"
+version = "2.5.1+cpu"
+source = { registry = "https://download.pytorch.org/whl/cpu" }
+resolution-markers = [
+    "python_full_version < '3.12' and sys_platform == 'linux'",
+    "python_full_version >= '3.12' and sys_platform == 'linux'",
+]
+dependencies = [
+    { name = "filelock", marker = "sys_platform == 'linux'" },
+    { name = "fsspec", marker = "sys_platform == 'linux'" },
+    { name = "jinja2", marker = "sys_platform == 'linux'" },
+    { name = "networkx", marker = "sys_platform == 'linux'" },
+    { name = "setuptools", marker = "python_full_version >= '3.12' and sys_platform == 'linux'" },
+    { name = "sympy", marker = "sys_platform == 'linux'" },
+    { name = "typing-extensions", marker = "sys_platform == 'linux'" },
+]
+wheels = [
+    { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1%2Bcpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:7f91a2200e352745d70e22396bd501448e28350fbdbd8d8b1c83037e25451150" },
+    { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1%2Bcpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:07d7c9e069123d5af08b0cf0013d74f680b2d8be7d9e2cf561a52c90c55d9409" },
+    { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1%2Bcpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:4856f9d6925121d13c2df07aa7580b767f449dfe71ae5acde9c27535d5da4840" },
+    { url = "https://download.pytorch.org/whl/cpu/torch-2.5.1%2Bcpu-cp313-cp313-linux_x86_64.whl", hash = "sha256:5dbbdf83caa90d0bcaa50e4933ca424889133b35226db79000877d4ec5d9ea37" },
+]
+
 [[package]]
 name = "tox"
 version = "4.23.2"
@@ -579,6 +1012,14 @@ wheels = [
     { url = "https://files.pythonhosted.org/packages/af/c0/124b73d01c120e917383bc6c53ebc34efdf7243faa9fca64d105c94cf2ab/tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38", size = 166758 },
 ]
 
+[[package]]
+name = "triton"
+version = "3.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "filelock", marker = "sys_platform != 'linux'" },
+]
+
 [[package]]
 name = "typing-extensions"
 version = "4.12.2"
@@ -590,14 +1031,14 @@ wheels = [
 
 [[package]]
 name = "virtualenv"
-version = "20.26.6"
+version = "20.27.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "distlib" },
     { name = "filelock" },
     { name = "platformdirs" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/3f/40/abc5a766da6b0b2457f819feab8e9203cbeae29327bd241359f866a3da9d/virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48", size = 9372482 }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/b3/7b6a79c5c8cf6d90ea681310e169cf2db2884f4d583d16c6e1d5a75a4e04/virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba", size = 6491145 }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/59/90/57b8ac0c8a231545adc7698c64c5a36fa7cd8e376c691b9bde877269f2eb/virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2", size = 5999862 },
+    { url = "https://files.pythonhosted.org/packages/ae/92/78324ff89391e00c8f4cf6b8526c41c6ef36b4ea2d2c132250b1a6fc2b8d/virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4", size = 3117838 },
 ]


Test patch:
diff --git a/tests/array_api/__init__.py b/tests/array_api/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/array_api/test_array.py b/tests/array_api/test_array.py
new file mode 100644
index 00000000..110180a9
--- /dev/null
+++ b/tests/array_api/test_array.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+from optype.array_api._array import Array, BaseArray
+
+
+def test_numpy_ndarray() -> None:
+    a_np = np.empty((3, 2), dtype=np.dtypes.Float64DType())
+
+    a_xp0: BaseArray = a_np
+    assert isinstance(a_np, BaseArray)
+
+    a_xp: Array = a_np
+    assert isinstance(a_np, Array)
diff --git a/tests/array_api/test_dtypes.py b/tests/array_api/test_dtypes.py
new file mode 100644
index 00000000..cd4a91f6
--- /dev/null
+++ b/tests/array_api/test_dtypes.py
@@ -0,0 +1,174 @@
+# pyright: reportPrivateUsage=false
+import sys
+from typing import Any, TypeAlias
+
+import array_api_strict as xp  # type: ignore[import-untyped]  # pyright: ignore[reportMissingTypeStubs]
+import numpy as np
+import pytest
+
+import optype.array_api as oxp  # noqa: TCH001
+from optype.array_api._dtypes import _DTypeNP, _DTypeTorch, _DTypeXP
+
+
+_AnyDTypeNP: TypeAlias = _DTypeNP[Any]
+
+
+@pytest.mark.skipif(
+    sys.platform != "linux" and sys.version_info >= (3, 13),
+    reason="pytorch 1.5.1 on python 3.13 requires linux",
+)
+@pytest.mark.parametrize(
+    "dtype_name",
+    [
+        "bool",
+        "int8",
+        "int16",
+        "int32",
+        "int64",
+        "uint8",
+        "uint16",
+        "uint32",
+        "uint64",
+        "float32",
+        "float64",
+        "complex64",
+        "complex128",
+    ],
+)
+def test_torch(dtype_name: str) -> None:
+    import torch  # noqa: PLC0415
+
+    dtype: torch.dtype = getattr(torch, dtype_name)
+    _dtype: _DTypeTorch = dtype
+    assert isinstance(dtype, _DTypeTorch)
+
+
+def test_bool() -> None:
+    b1: oxp.Bool = np.dtype(np.bool_)
+    b1_biufc: _AnyDTypeNP = b1
+
+    assert isinstance(b1, _DTypeNP)
+    assert isinstance(xp.bool, _DTypeXP)
+
+
+def test_int8() -> None:
+    i1: oxp.Int8 = np.dtype(np.int8)
+    i1_iu: oxp.Integer_co = i1
+    i1_iuf: oxp.Real = i1
+    i1_iufc: oxp.Numeric = i1
+    i1_biufc: oxp.DType = i1
+
+    assert isinstance(i1, _DTypeNP)
+    assert isinstance(xp.int8, _DTypeXP)
+
+
+def test_int16() -> None:
+    i2: oxp.Int16 = np.dtype(np.int16)
+    i2_iu: oxp.Integer_co = i2
+    i2_iuf: oxp.Real = i2
+    i2_iufc: oxp.Numeric = i2
+    i2_biufc: oxp.DType = i2
+    assert isinstance(i2, _DTypeNP)
+    assert isinstance(xp.int16, _DTypeXP)
+
+
+def test_int32() -> None:
+    i4: oxp.Int32 = np.dtype(np.int32)
+    i4_i0: oxp.Int0 = i4
+    i4_iu: oxp.Integer_co = i4
+    i4_iuf: oxp.Real = i4
+    i4_iufc: oxp.Numeric = i4
+    i4_biufc: oxp.DType = i4
+    assert isinstance(i4, _DTypeNP)
+    assert isinstance(xp.int32, _DTypeXP)
+
+
+def test_int64() -> None:
+    i8: oxp.Int64 = np.dtype(np.int64)
+    i8_i0: oxp.Int0 = i8
+    i8_iu: oxp.Integer_co = i8
+    i8_iuf: oxp.Real = i8
+    i8_iufc: oxp.Numeric = i8
+    i8_biufc: oxp.DType = i8
+    assert isinstance(i8, _DTypeNP)
+    assert isinstance(xp.int64, _DTypeXP)
+
+
+def test_uint8() -> None:
+    u1: oxp.UInt8 = np.dtype(np.uint16)
+    u1_iu: oxp.Integer_co = u1
+    u1_iuf: oxp.Real = u1
+    u1_iufc: oxp.Numeric = u1
+    u1_biufc: oxp.DType = u1
+    assert isinstance(u1, _DTypeNP)
+    assert isinstance(xp.uint8, _DTypeXP)
+
+
+def test_uint16() -> None:
+    u2: oxp.UInt16 = np.dtype(np.uint16)
+    u2_iu: oxp.Integer_co = u2
+    u2_iuf: oxp.Real = u2
+    u2_iufc: oxp.Numeric = u2
+    u2_biufc: oxp.DType = u2
+    assert isinstance(u2, _DTypeNP)
+    assert isinstance(xp.uint16, _DTypeXP)
+
+
+def test_uint32() -> None:
+    u4: oxp.UInt32 = np.dtype(np.uint32)
+    u4_iu: oxp.Integer_co = u4
+    u4_iuf: oxp.Real = u4
+    u4_iufc: oxp.Numeric = u4
+    u4_biufc: oxp.DType = u4
+    assert isinstance(u4, _DTypeNP)
+    assert isinstance(xp.uint32, _DTypeXP)
+
+
+def test_uint64() -> None:
+    u8: oxp.UInt64 = np.dtype(np.uint64)
+    u8_iu: oxp.Integer_co = u8
+    u8_iuf: oxp.Real = u8
+    u8_iufc: oxp.Numeric = u8
+    u8_biufc: oxp.DType = u8
+    assert isinstance(u8, _DTypeNP)
+    assert isinstance(xp.uint64, _DTypeXP)
+
+
+def test_float32() -> None:
+    f4: oxp.Float32 = np.dtype(np.float32)
+    f4_f: oxp.RealFloating = f4
+    f4_iuf: oxp.Real = f4
+    f4_iufc: oxp.Numeric = f4
+    f4_biufc: oxp.DType = f4
+    assert isinstance(f4, _DTypeNP)
+    assert isinstance(xp.float32, _DTypeXP)
+
+
+def test_float64() -> None:
+    f8: oxp.Float64 = np.dtype(np.float64)
+    f8_f0: oxp.Float0 = f8
+    f8_f: oxp.RealFloating = f8
+    f8_iuf: oxp.Real = f8
+    f8_iufc: oxp.Numeric = f8
+    f8_biufc: oxp.DType = f8
+    assert isinstance(f8, _DTypeNP)
+    assert isinstance(xp.float64, _DTypeXP)
+
+
+def test_complex64() -> None:
+    c8: oxp.Complex64 = np.dtype(np.complex64)
+    c8_c: oxp.ComplexFloating = c8
+    c8_iufc: oxp.Numeric = c8
+    c8_biufc: oxp.DType = c8
+    assert isinstance(c8, _DTypeNP)
+    assert isinstance(xp.complex64, _DTypeXP)
+
+
+def test_complex128() -> None:
+    c16: oxp.Complex128 = np.dtype(np.complex128)
+    c16_c0: oxp.Complex0 = c16
+    c16_c: oxp.ComplexFloating = c16
+    c16_iufc: oxp.Numeric = c16
+    c16_biufc: oxp.DType = c16
+    assert isinstance(c16, _DTypeNP)
+    assert isinstance(xp.complex128, _DTypeXP)
diff --git a/tests/array_api/test_sctypes_builtins.py b/tests/array_api/test_sctypes_builtins.py
new file mode 100644
index 00000000..bace7ef2
--- /dev/null
+++ b/tests/array_api/test_sctypes_builtins.py
@@ -0,0 +1,32 @@
+import pytest
+
+from optype.array_api import _sctypes_numpy as sct
+
+
+@pytest.mark.parametrize("v", [False, 0, 0.0, 0j])
+def test_no_builtins(v: complex) -> None:
+    b: sct.Bool = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f8: sct.Float64 = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c16: sct.Complex128 = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    assert not isinstance(v, sct.Bool)
+    assert not isinstance(v, sct.Float64)
+    assert not isinstance(v, sct.Complex128)
+
+    iu: sct.Integer = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f: sct.Floating = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c: sct.ComplexFloating = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    assert not isinstance(v, sct.Integer)
+    assert not isinstance(v, sct.Floating)
+    assert not isinstance(v, sct.ComplexFloating)
+
+    iuf: sct.RealNumber = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    iufc: sct.Number = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    assert not isinstance(v, sct.RealNumber)
+    assert not isinstance(v, sct.Number)
+
+    biu: sct.Integer_co = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    biuf: sct.Floating_co = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    biufc: sct.ComplexFloating_co = v  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    assert not isinstance(v, sct.Integer_co)
+    assert not isinstance(v, sct.Floating_co)
+    assert not isinstance(v, sct.ComplexFloating_co)
diff --git a/tests/array_api/test_sctypes_np.py b/tests/array_api/test_sctypes_np.py
new file mode 100644
index 00000000..32e85430
--- /dev/null
+++ b/tests/array_api/test_sctypes_np.py
@@ -0,0 +1,146 @@
+import sys
+from typing import Any
+
+import numpy as np
+import pytest
+
+from optype.array_api import _sctypes_numpy as sct
+
+
+def test_bool() -> None:
+    i1_x: sct.Bool = np.int8()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    u1_x: sct.Bool = np.uint8()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f2_x: sct.Bool = np.float16()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f4_x: sct.Bool = np.float32()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f8_x: sct.Bool = np.float64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c8_x: sct.Bool = np.complex64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c16_x: sct.Bool = np.complex128()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+
+    v = np.bool_()
+    b1: sct.Bool = v
+
+    assert isinstance(v, sct.ComplexFloating_co)
+    assert isinstance(v, sct.Floating_co)
+    assert isinstance(v, sct.Integer_co)
+    assert isinstance(v, sct.Bool)
+
+    assert not isinstance(v, sct.Complex128)
+    assert not isinstance(v, sct.Float64)
+
+    assert not isinstance(v, sct.Number)
+    assert not isinstance(v, sct.ComplexFloating)
+    assert not isinstance(v, sct.Floating)
+    assert not isinstance(v, sct.Integer)
+
+
+def test_float64() -> None:
+    b1_x: sct.Float64 = np.bool_()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    i8_x: sct.Float64 = np.int64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    u8_x: sct.Float64 = np.uint64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c8_x: sct.Float64 = np.complex64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c16_x: sct.Float64 = np.complex128()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    # mypy false negatives
+    f2_x: sct.Float64 = np.float16()  # pyright: ignore[reportAssignmentType]
+    f4_x: sct.Float64 = np.float32()  # pyright: ignore[reportAssignmentType]
+
+    v = np.float64()
+    f8: sct.Float64 = v
+
+    assert isinstance(v, sct.Number)
+    assert isinstance(v, sct.ComplexFloating_co)
+    assert isinstance(v, sct.Floating_co)
+    assert isinstance(v, sct.Floating)
+    assert isinstance(v, sct.Float64)
+
+    if sys.version_info >= (3, 11):
+        assert not isinstance(v, sct.Complex128)
+    assert not isinstance(v, sct.Integer_co)
+    assert not isinstance(v, sct.Integer)
+
+
+def test_complex128() -> None:
+    b1_x: sct.Complex128 = np.bool_()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f2_x: sct.Complex128 = np.float16()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f4_x: sct.Complex128 = np.float32()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f8_x: sct.Complex128 = np.float64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    i8_x: sct.Complex128 = np.int64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    u8_x: sct.Complex128 = np.uint64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    # mypy false negative
+    c8_x: sct.Complex128 = np.complex64()  # pyright: ignore[reportAssignmentType]
+
+    c16: sct.Complex128 = np.complex128()
+
+    assert isinstance(c16, sct.Number)
+    assert isinstance(c16, sct.ComplexFloating_co)
+    assert isinstance(c16, sct.Complex128)
+
+    assert not isinstance(c16, sct.Float64)
+    assert not isinstance(c16, sct.Floating)
+    assert not isinstance(c16, sct.Integer)
+    assert not isinstance(c16, sct.Integer_co)
+    assert not isinstance(c16, sct.RealNumber)
+
+
+@pytest.mark.parametrize(
+    "sctype",
+    [np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64],
+)
+def test_integer(sctype: type[np.integer[Any]]) -> None:
+    b1_x: sct.Integer = np.bool_()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f2_x: sct.Integer = np.float16()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f4_x: sct.Integer = np.float32()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    f8_x: sct.Integer = np.float64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c8_x: sct.Integer = np.complex64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c16_x: sct.Integer = np.complex128()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+
+    iu: sct.Integer = sctype()
+
+    assert isinstance(iu, sct.Number)
+    assert isinstance(iu, sct.ComplexFloating_co)
+    assert isinstance(iu, sct.Floating_co)
+    assert isinstance(iu, sct.Integer_co)
+    assert isinstance(iu, sct.Integer)
+
+    assert not isinstance(iu, sct.Complex128)
+    assert not isinstance(iu, sct.Float64)
+    assert not isinstance(iu, sct.Floating)
+
+
+@pytest.mark.parametrize("sctype", [np.float16, np.float32, np.float64, np.longdouble])
+def test_floating(sctype: type[np.floating[Any]]) -> None:
+    b1_x: sct.Floating = np.bool_()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    i8_x: sct.Floating = np.int64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    u8_x: sct.Floating = np.uint64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c8_x: sct.Floating = np.complex64()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+    c16_x: sct.Floating = np.complex128()  # type: ignore[assignment]  # pyright: ignore[reportAssignmentType]
+
+    f: sct.Floating = sctype()
+
+    assert isinstance(f, sct.Number)
+    assert isinstance(f, sct.RealNumber)
+    assert isinstance(f, sct.ComplexFloating_co)
+    assert isinstance(f, sct.Floating_co)
+    assert isinstance(f, sct.Floating)
+
+    if sys.version_info >= (3, 11):
+        assert not isinstance(f, sct.Complex128)
+    assert not isinstance(f, sct.Integer_co)
+    assert not isinstance(f, sct.Integer)
+
+
+@pytest.mark.parametrize("sctype", [np.complex64, np.complex128, np.clongdouble])
+def test_complexfloating(sctype: type[np.complexfloating[Any, Any]]) -> None:
+    # NOTE: The negative assignment type-tests all fail in `numpy<2.2`
+
+    c: sct.ComplexFloating = sctype()
+
+    assert isinstance(c, sct.ComplexFloating_co)
+    assert isinstance(c, sct.ComplexFloating)
+    assert isinstance(c, sct.Number)
+
+    assert not isinstance(c, sct.Bool)
+    assert not isinstance(c, sct.Integer)
+    assert not isinstance(c, sct.Integer_co)
+    assert not isinstance(c, sct.Float64)
+    assert not isinstance(c, sct.Floating)
+    assert not isinstance(c, sct.RealNumber)
diff --git a/tests/test_does.py b/tests/test_does.py
index 43368930..dcf7ddd5 100644
--- a/tests/test_does.py
+++ b/tests/test_does.py
@@ -1,4 +1,5 @@
 import sys
+from typing import final
 
 from optype import do_iadd
 
@@ -10,6 +11,7 @@
 
 
 def test_iadd_iadd() -> None:
+    @final
     class IntIAdd:
         def __init__(self, x: int, /) -> None:
             self.x = x
@@ -26,6 +28,7 @@ def __iadd__(self, y: int, /) -> int:
 
 
 def test_iadd_add() -> None:
+    @final
     class IntAdd:
         def __init__(self, x: int, /) -> None:
             self.x = x
@@ -41,6 +44,7 @@ def __add__(self, y: int, /) -> int:
 
 
 def test_iadd_radd() -> None:
+    @final
     class IntRAdd:
         def __init__(self, x: int, /) -> None:
             self.x = x
diff --git a/tests/test_typing.py b/tests/test_typing.py
index b88b5555..0ddc6751 100644
--- a/tests/test_typing.py
+++ b/tests/test_typing.py
@@ -1,5 +1,5 @@
 import enum
-from typing import Generic, Literal
+from typing import Generic, Literal, final
 
 from typing_extensions import TypeVar
 
@@ -12,6 +12,7 @@
 # `Any*` type aliases
 
 
+@final
 class Index(Generic[_IntT_co]):
     def __init__(self, x: _IntT_co, /) -> None:
         self._x = x
@@ -20,6 +21,7 @@ def __index__(self, /) -> _IntT_co:
         return self._x
 
 
+@final
 class IntLike(Generic[_IntT_co]):
     def __init__(self, x: _IntT_co, /) -> None:
         self._x = x
@@ -28,6 +30,7 @@ def __int__(self, /) -> _IntT_co:
         return self._x
 
 
+@final
 class FloatLike:
     def __init__(self, x: float, /) -> None:
         self._x = x
@@ -36,6 +39,7 @@ def __float__(self, /) -> float:
         return self._x
 
 
+@final
 class ComplexLike:
     def __init__(self, x: complex, /) -> None:
         self._x = x
@@ -47,6 +51,7 @@ def __complex__(self, /) -> complex:
 _V_co = TypeVar("_V_co", covariant=True)
 
 
+@final
 class SequenceLike(Generic[_V_co]):
     def __init__(self, /, *values: _V_co) -> None:
         self._xs = values