initial vendoring, pre-fixing

This commit is contained in:
sneakers-the-rat 2024-07-31 13:06:10 -07:00
parent 8007cebbaa
commit cbe5d33fd9
Signed by untrusted user who does not match committer: jonny
GPG key ID: 6DCB96EF1E4D232D
26 changed files with 2206 additions and 1 deletions

View file

@ -416,4 +416,11 @@ dumped = instance.model_dump_json(context={'zarr_dump_array': True})
"hexdigest": "c51604eace325fe42bbebf39146c0956bd2ed13c" "hexdigest": "c51604eace325fe42bbebf39146c0956bd2ed13c"
} }
} }
``` ```
## Vendored Dependencies
We have vendored dependencies in the `src/numpydantic/vendor` package,
and reproduced their licenses in the `licenses` directory.
- [nptyping](https://github.com/ramonhagenaars/nptyping) - `numpydantic.vendor.nptyping` - `/licenses/nptyping.txt`

7
licenses/nptyping.txt Normal file
View file

@ -0,0 +1,7 @@
Copyright 2022, Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -131,4 +131,9 @@ fixable = ["ALL"]
[tool.mypy] [tool.mypy]
plugins = [ plugins = [
"pydantic.mypy" "pydantic.mypy"
]
[tool.coverage.run]
omit = [
"src/numpydantic/vendor/*"
] ]

0
src/numpydantic/vendor/__init__.py vendored Normal file
View file

View file

@ -0,0 +1,188 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from nptyping.assert_isinstance import assert_isinstance
from nptyping.error import (
InvalidArgumentsError,
InvalidDTypeError,
InvalidShapeError,
InvalidStructureError,
NPTypingError,
)
from nptyping.ndarray import NDArray
from nptyping.package_info import __version__
from nptyping.pandas_.dataframe import DataFrame
from nptyping.recarray import RecArray
from nptyping.shape import Shape
from nptyping.shape_expression import (
normalize_shape_expression,
validate_shape_expression,
)
from nptyping.structure import Structure
from nptyping.typing_ import (
Bool,
Bool8,
Byte,
Bytes,
Bytes0,
CDouble,
CFloat,
Character,
CLongDouble,
CLongFloat,
Complex,
Complex64,
Complex128,
ComplexFloating,
CSingle,
Datetime64,
Double,
DType,
Flexible,
Float,
Float16,
Float32,
Float64,
Floating,
Half,
Inexact,
Int,
Int0,
Int8,
Int16,
Int32,
Int64,
IntC,
Integer,
IntP,
LongComplex,
LongDouble,
LongFloat,
LongLong,
Number,
Object,
Object0,
Short,
SignedInteger,
Single,
SingleComplex,
Str0,
String,
Timedelta64,
UByte,
UInt,
UInt0,
UInt8,
UInt16,
UInt32,
UInt64,
UIntC,
UIntP,
ULongLong,
Unicode,
UnsignedInteger,
UShort,
Void,
Void0,
)
__all__ = [
"NDArray",
"RecArray",
"assert_isinstance",
"validate_shape_expression",
"normalize_shape_expression",
"NPTypingError",
"InvalidArgumentsError",
"InvalidShapeError",
"InvalidStructureError",
"InvalidDTypeError",
"Shape",
"Structure",
"__version__",
"DType",
"Number",
"Bool",
"Bool8",
"Object",
"Object0",
"Datetime64",
"Integer",
"SignedInteger",
"Int8",
"Int16",
"Int32",
"Int64",
"Byte",
"Short",
"IntC",
"IntP",
"Int0",
"Int",
"LongLong",
"Timedelta64",
"UnsignedInteger",
"UInt8",
"UInt16",
"UInt32",
"UInt64",
"UByte",
"UShort",
"UIntC",
"UIntP",
"UInt0",
"UInt",
"ULongLong",
"Inexact",
"Floating",
"Float16",
"Float32",
"Float64",
"Half",
"Single",
"Double",
"Float",
"LongDouble",
"LongFloat",
"ComplexFloating",
"Complex64",
"Complex128",
"CSingle",
"SingleComplex",
"CDouble",
"Complex",
"CFloat",
"CLongDouble",
"CLongFloat",
"LongComplex",
"Flexible",
"Void",
"Void0",
"Character",
"Bytes",
"String",
"Bytes0",
"Unicode",
"Str0",
"DataFrame",
]

View file

@ -0,0 +1,52 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from typing import (
Any,
Optional,
Type,
TypeVar,
)
try:
from typing import TypeGuard # type: ignore[attr-defined]
except ImportError: # pragma: no cover
from typing_extensions import TypeGuard # type: ignore[attr-defined]
TYPE = TypeVar("TYPE")
def assert_isinstance(
instance: Any, cls: Type[TYPE], message: Optional[str] = None
) -> TypeGuard[TYPE]:
"""
A TypeGuard function that is equivalent to `assert instance, cls, message`
that hides nasty MyPy or IDE warnings.
:param instance: the instance that is checked against cls.
:param cls: the class
:param message: any message that is displayed when the assert check fails.
:return: the type of cls.
"""
message = message or f"instance={instance!r}, cls={cls!r}"
assert isinstance(instance, cls), message
return True

View file

@ -0,0 +1,248 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABCMeta, abstractmethod
from inspect import FrameInfo
from typing import (
Any,
Dict,
List,
Optional,
Set,
Tuple,
TypeVar,
)
from nptyping.error import InvalidArgumentsError, NPTypingError
_T = TypeVar("_T")
class InconstructableMeta(ABCMeta):
"""
Makes it impossible for a class to get instantiated.
"""
def __call__(cls, *_: Any, **__: Any) -> None:
raise NPTypingError(
f"Cannot instantiate nptyping.{cls.__name__}. Did you mean to use [ ] ?"
)
class ImmutableMeta(ABCMeta):
"""
Makes it impossible to changes values on a class.
"""
def __setattr__(cls, key: str, value: Any) -> None:
if key not in ("_abc_impl", "__abstractmethods__"):
raise NPTypingError(f"Cannot set values to nptyping.{cls.__name__}.")
class FinalMeta(ABCMeta):
"""
Makes it impossible for classes to inherit from some class.
An concrete inheriting meta class requires to define a name for its
implementation. The class with this name will be the only class that is
allowed to use that concrete meta class.
"""
_name_per_meta_cls: Dict[type, Optional[str]] = {}
def __init_subclass__(cls, implementation: Optional[str] = None) -> None:
# implementation is made Optional here, to allow other meta classes to
# inherit.
cls._name_per_meta_cls[cls] = implementation
def __new__(cls, name: str, *args: Any, **kwargs: Any) -> type:
if name == cls._name_per_meta_cls[cls]:
assert name, "cls_name not set"
return type.__new__(cls, name, *args, **kwargs)
raise NPTypingError(f"Cannot subclass nptyping.{cls._name_per_meta_cls[cls]}.")
class MaybeCheckableMeta(ABCMeta):
"""
Makes instance and subclass checks raise by default.
"""
def __instancecheck__(cls, instance: Any) -> bool:
raise NPTypingError(
f"Instance checking is not supported for nptyping.{cls.__name__}."
)
def __subclasscheck__(cls, subclass: Any) -> bool:
raise NPTypingError(
f"Subclass checking is not supported for nptyping.{cls.__name__}."
)
class PrintableMeta(ABCMeta):
"""
Ensures that a class can be printed nicely.
"""
@abstractmethod
def __str__(cls) -> str:
... # pragma: no cover
def __repr__(cls) -> str:
return str(cls)
class SubscriptableMeta(ABCMeta):
"""
Makes a class subscriptable: it accepts arguments between brackets and a
new type is returned for every unique set of arguments.
"""
_all_types: Dict[Tuple[type, Tuple[Any, ...]], type] = {}
_parameterized: bool = False
@abstractmethod
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
... # pragma: no cover
def _get_module(cls, stack: List[FrameInfo], module: str) -> str:
# The magic below makes Python's help function display a meaningful
# text with nptyping types.
return "typing" if stack[1][3] == "formatannotation" else module
def _get_additional_values(
cls, item: Any # pylint: disable=unused-argument
) -> Dict[str, Any]:
# This method is invoked after _get_item and right before returning
# the result of __getitem__. It can be overridden to provide extra
# values that are to be set as attributes on the new type.
return {}
def __getitem__(cls, item: Any) -> type:
if getattr(cls, "_parameterized", False):
raise NPTypingError(f"Type nptyping.{cls} is already parameterized.")
args = cls._get_item(item)
additional_values = cls._get_additional_values(item)
assert hasattr(cls, "__args__"), "A SubscriptableMeta must have __args__."
if args != cls.__args__: # type: ignore[attr-defined]
result = cls._create_type(args, additional_values)
else:
result = cls
return result
def _create_type(
cls, args: Tuple[Any, ...], additional_values: Dict[str, Any]
) -> type:
key = (cls, args)
if key not in cls._all_types:
cls._all_types[key] = type(
cls.__name__,
(cls,),
{"__args__": args, "_parameterized": True, **additional_values},
)
return cls._all_types[key]
class ComparableByArgsMeta(ABCMeta):
"""
Makes a class comparable by means of its __args__.
"""
__args__: Tuple[Any, ...]
def __eq__(cls, other: Any) -> bool:
return (
hasattr(cls, "__args__")
and hasattr(other, "__args__")
and cls.__args__ == other.__args__
)
def __hash__(cls) -> int:
return hash(cls.__args__)
class ContainerMeta(
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
ComparableByArgsMeta,
ABCMeta,
):
"""
Base meta class for "containers" such as Shape and Structure.
"""
_known_expressions: Set[str] = set()
__args__: Tuple[str, ...]
@abstractmethod
def _validate_expression(cls, item: str) -> None:
... # pragma: no cover
@abstractmethod
def _normalize_expression(cls, item: str) -> str:
... # pragma: no cover
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
if not isinstance(item, str):
raise InvalidArgumentsError(
f"Unexpected argument of type {type(item)}, expecting a string."
)
if item in cls._known_expressions:
# No need to do costly validations and normalizations if it has been done
# before.
return (item,)
cls._validate_expression(item)
norm_shape_expression = cls._normalize_expression(item)
cls._known_expressions.add(norm_shape_expression)
return (norm_shape_expression,)
def __subclasscheck__(cls, subclass: Any) -> bool:
type_match = type(subclass) == type( # pylint: disable=unidiomatic-typecheck
cls
)
return type_match and (
subclass.__args__ == cls.__args__ or not cls._parameterized
)
def __str__(cls) -> str:
return f"{cls.__name__}['{cls.__args__[0]}']"
def __eq__(cls, other: Any) -> bool:
result = cls is other
if not result and hasattr(cls, "__args__") and hasattr(other, "__args__"):
normalized_args = tuple(
cls._normalize_expression(str(arg)) for arg in other.__args__
)
result = cls.__args__ == normalized_args
return result
def __hash__(cls) -> int:
return hash(cls.__args__)

View file

@ -0,0 +1,47 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
class NPTypingError(Exception):
"""Base error for all NPTyping errors."""
class InvalidArgumentsError(NPTypingError):
"""Raised when a invalid arguments are provided to an nptyping type."""
class InvalidShapeError(NPTypingError):
"""Raised when a shape is considered not valid."""
class InvalidStructureError(NPTypingError):
"""Raised when a structure is considered not valid."""
class InvalidDTypeError(NPTypingError):
"""Raised when an argument is not a DType."""
class DependencyError(NPTypingError):
"""Raised when a dependency has not been installed."""

View file

@ -0,0 +1,196 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import inspect
from abc import ABC
from typing import Any, Tuple
import numpy as np
from nptyping.base_meta_classes import (
FinalMeta,
ImmutableMeta,
InconstructableMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
)
from nptyping.error import InvalidArgumentsError
from nptyping.nptyping_type import NPTypingType
from nptyping.shape import Shape
from nptyping.shape_expression import check_shape
from nptyping.structure import Structure
from nptyping.structure_expression import check_structure, check_type_names
from nptyping.typing_ import (
DType,
dtype_per_name,
name_per_dtype,
)
class NDArrayMeta(
SubscriptableMeta,
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,
PrintableMeta,
implementation="NDArray",
):
"""
Metaclass that is coupled to nptyping.NDArray. It contains all actual logic
such as instance checking.
"""
__args__: Tuple[Shape, DType]
_parameterized: bool
@property
def __module__(cls) -> str:
return cls._get_module(inspect.stack(), "nptyping.ndarray")
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
cls._check_item(item)
shape, dtype = cls._get_from_tuple(item)
return shape, dtype
def __instancecheck__( # pylint: disable=bad-mcs-method-argument
self, instance: Any
) -> bool:
shape, dtype = self.__args__
dtype_is_structure = issubclass(dtype, Structure)
structure_is_ok = dtype_is_structure and check_structure(
instance.dtype, dtype, dtype_per_name
)
return (
isinstance(instance, np.ndarray)
and (shape is Any or check_shape(instance.shape, shape))
and (
dtype is Any
or structure_is_ok
or issubclass(instance.dtype.type, dtype)
)
)
def __str__(cls) -> str:
shape, dtype = cls.__args__
return (
f"{cls.__name__}[{cls._shape_expression_to_str(shape)}, "
f"{cls._dtype_to_str(dtype)}]"
)
def _is_literal_like(cls, item: Any) -> bool:
# item is a Literal or "Literal enough" (ducktyping).
return hasattr(item, "__args__")
def _check_item(cls, item: Any) -> None:
# Check if the item is what we expect and raise if it is not.
if not isinstance(item, tuple):
raise InvalidArgumentsError(f"Unexpected argument of type {type(item)}.")
if len(item) > 2:
raise InvalidArgumentsError(f"Unexpected argument {item[2]}.")
def _get_from_tuple(cls, item: Tuple[Any, ...]) -> Tuple[Shape, DType]:
# Return the Shape Expression and DType from a tuple.
shape = cls._get_shape(item[0])
dtype = cls._get_dtype(item[1])
return shape, dtype
def _get_shape(cls, dtype_candidate: Any) -> Shape:
if dtype_candidate is Any or dtype_candidate is Shape:
shape = Any
elif issubclass(dtype_candidate, Shape):
shape = dtype_candidate
elif cls._is_literal_like(dtype_candidate):
shape_expression = dtype_candidate.__args__[0]
shape = Shape[shape_expression]
else:
raise InvalidArgumentsError(
f"Unexpected argument '{dtype_candidate}', expecting"
" Shape[<ShapeExpression>]"
" or Literal[<ShapeExpression>]"
" or typing.Any."
)
return shape
def _get_dtype(cls, dtype_candidate: Any) -> DType:
is_dtype = isinstance(dtype_candidate, type) and issubclass(
dtype_candidate, np.generic
)
if dtype_candidate is Any:
dtype = Any
elif is_dtype:
dtype = dtype_candidate
elif issubclass(dtype_candidate, Structure):
dtype = dtype_candidate
check_type_names(dtype, dtype_per_name)
elif cls._is_literal_like(dtype_candidate):
structure_expression = dtype_candidate.__args__[0]
dtype = Structure[structure_expression]
check_type_names(dtype, dtype_per_name)
else:
raise InvalidArgumentsError(
f"Unexpected argument '{dtype_candidate}', expecting"
" Structure[<StructureExpression>]"
" or Literal[<StructureExpression>]"
" or a dtype"
" or typing.Any."
)
return dtype
def _dtype_to_str(cls, dtype: Any) -> str:
if dtype is Any:
result = "Any"
elif issubclass(dtype, Structure):
result = str(dtype)
else:
result = name_per_dtype[dtype]
return result
def _shape_expression_to_str(cls, shape_expression: Any) -> str:
return "Any" if shape_expression is Any else str(shape_expression)
class NDArray(NPTypingType, ABC, metaclass=NDArrayMeta):
"""
An nptyping equivalent of numpy ndarray.
## No arguments means an NDArray with any DType and any shape.
>>> NDArray
NDArray[Any, Any]
## You can provide a DType and a Shape Expression.
>>> from nptyping import Int32, Shape
>>> NDArray[Shape["2, 2"], Int32]
NDArray[Shape['2, 2'], Int32]
## Instance checking can be done and the shape is also checked.
>>> import numpy as np
>>> isinstance(np.array([[1, 2], [3, 4]]), NDArray[Shape['2, 2'], Int32])
True
>>> isinstance(np.array([[1, 2], [3, 4], [5, 6]]), NDArray[Shape['2, 2'], Int32])
False
"""
__args__ = (Any, Any)

View file

@ -0,0 +1,30 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABC
class NPTypingType(ABC):
"""
Baseclass for all nptyping types.
"""

View file

@ -0,0 +1,37 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
__title__ = "nptyping"
__version__ = "2.5.0"
__author__ = "Ramon Hagenaars"
__author_email__ = "ramon.hagenaars@gmail.com"
__description__ = "Type hints for NumPy."
__url__ = "https://github.com/ramonhagenaars/nptyping"
__license__ = "MIT"
__python_versions__ = [
"3.7",
"3.8",
"3.9",
"3.10",
"3.11",
]

View file

View file

@ -0,0 +1,139 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import inspect
from abc import ABC
from typing import Any, Tuple
import numpy as np
from nptyping import InvalidArgumentsError
from nptyping.base_meta_classes import (
FinalMeta,
ImmutableMeta,
InconstructableMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
)
from nptyping.error import DependencyError
from nptyping.nptyping_type import NPTypingType
from nptyping.pandas_.typing_ import dtype_per_name
from nptyping.structure import Structure
from nptyping.structure_expression import check_structure
try:
import pandas as pd
except ImportError: # pragma: no cover
pd = None # type: ignore[misc, assignment]
class DataFrameMeta(
SubscriptableMeta,
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,
PrintableMeta,
implementation="DataFrame",
):
"""
Metaclass that is coupled to nptyping.DataFrame. It contains all actual logic
such as instance checking.
"""
__args__: Tuple[Structure]
_parameterized: bool
def __instancecheck__( # pylint: disable=bad-mcs-method-argument
self, instance: Any
) -> bool:
structure = self.__args__[0]
if pd is None:
raise DependencyError( # pragma: no cover
"Pandas needs to be installed for instance checking. Use `pip "
"install nptyping[pandas]` or `pip install nptyping[complete]`"
)
if not isinstance(instance, pd.DataFrame):
return False
if structure is Any:
return True
structured_dtype = np.dtype(
[(column, dtype.str) for column, dtype in instance.dtypes.items()]
)
return check_structure(structured_dtype, structure, dtype_per_name)
def _get_item(cls, item: Any) -> Tuple[Structure]:
if item is Any:
return (Any,)
cls._check_item(item)
return (Structure[getattr(item, "__args__")[0]],)
def __str__(cls) -> str:
structure = cls.__args__[0]
structure_str = "Any" if structure is Any else structure.__args__[0]
return f"{cls.__name__}[{structure_str}]"
def __repr__(cls) -> str:
structure = cls.__args__[0]
structure_str = "Any" if structure is Any else structure
return f"{cls.__name__}[{structure_str}]"
@property
def __module__(cls) -> str:
return cls._get_module(inspect.stack(), "nptyping.pandas_.dataframe")
def _check_item(cls, item: Any) -> None:
# Check if the item is what we expect and raise if it is not.
if not hasattr(item, "__args__"):
raise InvalidArgumentsError(f"Unexpected argument of type {type(item)}.")
class DataFrame(NPTypingType, ABC, metaclass=DataFrameMeta):
"""
An nptyping equivalent of pandas DataFrame.
## No arguments means a DataFrame of any structure.
>>> DataFrame
DataFrame[Any]
## You can use Structure Expression.
>>> from nptyping import DataFrame, Structure
>>> DataFrame[Structure["x: Int, y: Int"]]
DataFrame[Structure['[x, y]: Int']]
## Instance checking can be done and the structure is also checked.
>>> import pandas as pd
>>> df = pd.DataFrame({'x': [1, 2, 3], 'y': [4., 5., 6.]})
>>> isinstance(df, DataFrame[Structure['x: Int, y: Float']])
True
>>> isinstance(df, DataFrame[Structure['x: Float, y: Int']])
False
"""
__args__ = (Any,)

View file

@ -0,0 +1,27 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import pandas as pd
DataFrame = pd.DataFrame

View file

@ -0,0 +1,33 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from nptyping.typing_ import Object
from nptyping.typing_ import dtype_per_name as dtype_per_name_default
dtype_per_name = {
**dtype_per_name_default, # type: ignore[arg-type]
# Override the `String` and `Str` to point to `Object`. Pandas uses Object
# for string types in Dataframes and Series.
"String": Object,
"Str": Object,
}

View file

View file

@ -0,0 +1,78 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import inspect
from typing import Any, Tuple
import numpy as np
from nptyping.error import InvalidArgumentsError
from nptyping.ndarray import NDArray, NDArrayMeta
from nptyping.structure import Structure
from nptyping.typing_ import DType
class RecArrayMeta(NDArrayMeta, implementation="RecArray"):
"""
Metaclass that is coupled to nptyping.RecArray. It takes most of its logic
from NDArrayMeta.
"""
def _get_item(cls, item: Any) -> Tuple[Any, ...]:
cls._check_item(item)
shape, dtype = cls._get_from_tuple(item)
return shape, dtype
def _get_dtype(cls, dtype_candidate: Any) -> DType:
if not issubclass(dtype_candidate, Structure) and dtype_candidate is not Any:
raise InvalidArgumentsError(
f"Unexpected argument {dtype_candidate}. Expecting a Structure."
)
return dtype_candidate
@property
def __module__(cls) -> str:
return cls._get_module(inspect.stack(), "nptyping.recarray")
def __instancecheck__( # pylint: disable=bad-mcs-method-argument
self, instance: Any
) -> bool:
return isinstance(instance, np.recarray) and NDArrayMeta.__instancecheck__(
self, instance
)
class RecArray(NDArray, metaclass=RecArrayMeta):
"""
An nptyping equivalent of numpy recarray.
## RecArrays can take a Shape and must take a Structure
>>> from nptyping import Shape, Structure
>>> RecArray[Shape["2, 2"], Structure["x: Float, y: Float"]]
RecArray[Shape['2, 2'], Structure['[x, y]: Float']]
## Or Any
>>> from typing import Any
>>> RecArray[Shape["2, 2"], Any]
RecArray[Shape['2, 2'], Any]
"""

View file

@ -0,0 +1,27 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import numpy as np
RecArray = np.recarray

View file

@ -0,0 +1,75 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABC
from typing import Any, Dict
from nptyping.base_meta_classes import ContainerMeta
from nptyping.nptyping_type import NPTypingType
from nptyping.shape_expression import (
get_dimensions,
normalize_shape_expression,
remove_labels,
validate_shape_expression,
)
class ShapeMeta(ContainerMeta, implementation="Shape"):
"""
Metaclass that is coupled to nptyping.Shape.
"""
def _validate_expression(cls, item: str) -> None:
validate_shape_expression(item)
def _normalize_expression(cls, item: str) -> str:
return normalize_shape_expression(item)
def _get_additional_values(cls, item: Any) -> Dict[str, Any]:
dim_strings = get_dimensions(item)
dim_string_without_labels = remove_labels(dim_strings)
return {"prepared_args": dim_string_without_labels}
class Shape(NPTypingType, ABC, metaclass=ShapeMeta):
"""
A container for shape expressions that describe the shape of an multi
dimensional array.
Simple example:
>>> Shape['2, 2']
Shape['2, 2']
A Shape can be compared to a typing.Literal. You can use Literals in
NDArray as well.
>>> from typing import Literal
>>> Shape['2, 2'] == Literal['2, 2']
True
"""
__args__ = ("*, ...",)
prepared_args = "*, ..."

View file

@ -0,0 +1,36 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
try:
from typing import Literal # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Literal # type: ignore[attr-defined,misc,assignment]
from typing import Any, cast
# For MyPy:
Shape = cast(Literal, Shape) # type: ignore[has-type,misc,valid-type]
# For PyRight:
class Shape: # type: ignore[no-redef]
def __class_getitem__(cls, item: Any) -> Any: ...

View file

@ -0,0 +1,190 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import re
import string
from functools import lru_cache
from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Union,
)
from nptyping.error import InvalidShapeError
from nptyping.typing_ import ShapeExpression, ShapeTuple
if TYPE_CHECKING:
from nptyping.shape import Shape # pragma: no cover
@lru_cache()
def check_shape(shape: ShapeTuple, target: "Shape") -> bool:
"""
Check whether the given shape corresponds to the given shape_expression.
:param shape: the shape in question.
:param target: the shape expression to which shape is tested.
:return: True if the given shape corresponds to shape_expression.
"""
target_shape = _handle_ellipsis(shape, target.prepared_args)
return _check_dimensions_against_shape(shape, target_shape)
def validate_shape_expression(shape_expression: Union[ShapeExpression, Any]) -> None:
"""
Validate shape_expression and raise an InvalidShapeError if it is not
considered valid.
:param shape_expression: the shape expression to validate.
:return: None.
"""
shape_expression_no_quotes = shape_expression.replace("'", "").replace('"', "")
if shape_expression is not Any and not re.match(
_REGEX_SHAPE_EXPRESSION, shape_expression_no_quotes
):
raise InvalidShapeError(
f"'{shape_expression}' is not a valid shape expression."
)
def normalize_shape_expression(shape_expression: ShapeExpression) -> ShapeExpression:
"""
Normalize the given shape expression, e.g. by removing whitespaces, making
similar expressions look the same.
:param shape_expression: the shape expression that is to be normalized.
:return: a normalized shape expression.
"""
shape_expression = shape_expression.replace("'", "").replace('"', "")
# Replace whitespaces right before labels with $.
shape_expression = re.sub(rf"\s*{_REGEX_LABEL}", r"$\1", shape_expression)
# Let all commas be followed by a $.
shape_expression = shape_expression.replace(",", ",$")
# Remove all whitespaces left.
shape_expression = re.sub(r"\s*", "", shape_expression)
# Remove $ right after a bracket.
shape_expression = re.sub(r"\[\$+", "[", shape_expression)
# Replace $ with a single space.
shape_expression = re.sub(r"\$+", " ", shape_expression)
return shape_expression
def get_dimensions(shape_expression: str) -> List[str]:
"""
Find all "break downs" (the parts between brackets) in a shape expressions
and replace them with mere dimension sizes.
:param shape_expression: the shape expression that gets the break downs replaced.
:return: a list of dimensions without break downs.
"""
shape_expression_without_breakdowns = shape_expression
for dim_breakdown in re.findall(
r"(\[[^\]]+\])", shape_expression_without_breakdowns
):
dim_size = len(dim_breakdown.split(","))
shape_expression_without_breakdowns = (
shape_expression_without_breakdowns.replace(dim_breakdown, str(dim_size))
)
return shape_expression_without_breakdowns.split(",")
def remove_labels(dimensions: List[str]) -> List[str]:
"""
Remove all labels (words that start with a lowercase).
:param dimensions: a list of dimensions.
:return: a copy of the given list without labels.
"""
return [re.sub(r"\b[a-z]\w*", "", dim).strip() for dim in dimensions]
def _check_dimensions_against_shape(shape: ShapeTuple, target: List[str]) -> bool:
# Walk through the shape and test them against the given target,
# taking into consideration variables, wildcards, etc.
if len(shape) != len(target):
return False
shape_as_strings = (str(dim) for dim in shape)
variables: Dict[str, str] = {}
for dim, target_dim in zip(shape_as_strings, target):
if _is_wildcard(target_dim) or _is_assignable_var(dim, target_dim, variables):
continue
if dim != target_dim:
return False
return True
def _handle_ellipsis(shape: ShapeTuple, target: List[str]) -> List[str]:
# Let the ellipsis allows for any number of dimensions by replacing the
# ellipsis with the dimension size repeated the number of times that
# corresponds to the shape of the instance.
if target[-1] == "...":
dim_to_repeat = target[-2]
target = target[0:-1]
if len(shape) > len(target):
difference = len(shape) - len(target)
target += difference * [dim_to_repeat]
return target
def _is_assignable_var(dim: str, target_dim: str, variables: Dict[str, str]) -> bool:
# Return whether target_dim is a variable and can be assigned with dim.
return _is_variable(target_dim) and _can_assign_variable(dim, target_dim, variables)
def _is_variable(dim: str) -> bool:
# Return whether dim is a variable.
return dim[0] in string.ascii_uppercase
def _can_assign_variable(dim: str, target_dim: str, variables: Dict[str, str]) -> bool:
# Check and assign a variable.
assignable = variables.get(target_dim) in (None, dim)
variables[target_dim] = dim
return assignable
def _is_wildcard(dim: str) -> bool:
# Return whether dim is a wildcard (i.e. the character that takes any
# dimension size).
return dim == "*"
_REGEX_SEPARATOR = r"(\s*,\s*)"
_REGEX_DIMENSION_SIZE = r"(\s*[0-9]+\s*)"
_REGEX_VARIABLE = r"(\s*\b[A-Z]\w*\s*)"
_REGEX_LABEL = r"(\s*\b[a-z]\w*\s*)"
_REGEX_LABELS = rf"({_REGEX_LABEL}({_REGEX_SEPARATOR}{_REGEX_LABEL})*)"
_REGEX_WILDCARD = r"(\s*\*\s*)"
_REGEX_DIMENSION_BREAKDOWN = rf"(\s*\[{_REGEX_LABELS}\]\s*)"
_REGEX_DIMENSION = (
rf"({_REGEX_DIMENSION_SIZE}"
rf"|{_REGEX_VARIABLE}"
rf"|{_REGEX_WILDCARD}"
rf"|{_REGEX_DIMENSION_BREAKDOWN})"
)
_REGEX_DIMENSION_WITH_LABEL = rf"({_REGEX_DIMENSION}(\s+{_REGEX_LABEL})*)"
_REGEX_DIMENSIONS = (
rf"{_REGEX_DIMENSION_WITH_LABEL}({_REGEX_SEPARATOR}{_REGEX_DIMENSION_WITH_LABEL})*"
)
_REGEX_DIMENSIONS_ELLIPSIS = rf"({_REGEX_DIMENSIONS}{_REGEX_SEPARATOR}\.\.\.\s*)"
_REGEX_SHAPE_EXPRESSION = rf"^({_REGEX_DIMENSIONS}|{_REGEX_DIMENSIONS_ELLIPSIS})$"

View file

@ -0,0 +1,107 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from abc import ABC
from typing import (
Any,
Dict,
List,
)
from nptyping.base_meta_classes import ContainerMeta
from nptyping.nptyping_type import NPTypingType
from nptyping.structure_expression import (
create_name_to_type_dict,
normalize_structure_expression,
validate_structure_expression,
)
class StructureMeta(ContainerMeta, implementation="Structure"):
"""
Metaclass that is coupled to nptyping.Structure.
"""
__args__ = tuple()
def _validate_expression(cls, item: str) -> None:
validate_structure_expression(item)
def _normalize_expression(cls, item: str) -> str:
return normalize_structure_expression(item)
def _get_additional_values(cls, item: Any) -> Dict[str, Any]:
return {
"_type_per_name": create_name_to_type_dict(item),
"_has_wildcard": item.replace(" ", "").endswith(",*"),
}
class Structure(NPTypingType, ABC, metaclass=StructureMeta):
"""
A container for structure expressions that describe the structured dtype of
an array.
Simple example:
>>> Structure["x: Float, y: Float"]
Structure['[x, y]: Float']
"""
_type_per_name = {}
_has_wildcard = False
@classmethod
def has_wildcard(cls) -> bool:
"""
Returns whether this Structure has a wildcard for any other columns.
:return: True if this Structure expresses "any other columns".
"""
return cls._has_wildcard
@classmethod
def get_types(cls) -> List[str]:
"""
Return a list of all types (strings) in this Structure.
:return: a list of all types in this Structure.
"""
return list(set(cls._type_per_name.values()))
@classmethod
def get_names(cls) -> List[str]:
"""
Return a list of all names in this Structure.
:return: a list of all names in this Structure.
"""
return list(cls._type_per_name.keys())
@classmethod
def get_type(cls, name: str) -> str:
"""
Get the type (str) that corresponds to the given name. For example for
Structure["x: Float"], get_type("x") would give "Float".
:param name: the name of which the type is to be returned.
:return: the type as a string that corresponds to that name.
"""
return cls._type_per_name[name]

View file

@ -0,0 +1,38 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
try:
from typing import Literal # type: ignore[attr-defined]
except ImportError:
from typing_extensions import Literal # type: ignore[attr-defined,misc,assignment]
from typing import Any, cast
import numpy as np
# For MyPy:
Structure = cast(Literal, Structure) # type: ignore[has-type,misc,valid-type]
# For PyRight:
class Structure(np.dtype[Any]): # type: ignore[no-redef,misc]
def __class_getitem__(cls, item: Any) -> Any: ...

View file

@ -0,0 +1,339 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import re
from collections import Counter, defaultdict
from difflib import get_close_matches
from typing import (
TYPE_CHECKING,
Any,
Dict,
Generator,
List,
Mapping,
Tuple,
Type,
Union,
)
import numpy as np
from nptyping.error import InvalidShapeError, InvalidStructureError
from nptyping.shape import Shape
from nptyping.shape_expression import (
check_shape,
normalize_shape_expression,
validate_shape_expression,
)
from nptyping.typing_ import StructureExpression
if TYPE_CHECKING:
from nptyping.structure import Structure # pragma: no cover
def validate_structure_expression(
structure_expression: Union[StructureExpression, Any]
) -> None:
"""
Validate the given structure_expression and raise an InvalidStructureError
if it is deemed invalid.
:param structure_expression: the structure expression in question.
:return: None.
"""
if structure_expression is not Any:
if not re.match(_REGEX_STRUCTURE_EXPRESSION, structure_expression):
raise InvalidStructureError(
f"'{structure_expression}' is not a valid structure expression."
)
_validate_structure_expression_contains_no_multiple_field_names(
structure_expression
)
_validate_sub_array_expressions(structure_expression)
def check_structure(
structured_dtype: np.dtype, # type: ignore[type-arg]
target: "Structure",
type_per_name: Dict[str, type],
) -> bool:
"""
Check the given structured_dtype against the given target Structure and
return whether it corresponds (True) or not (False). The given dictionary
contains the vocabulary context for the check.
:param structured_dtype: the dtype in question.
:param target: the target Structure that is checked against.
:param type_per_name: a dict that holds the types by their names as they
occur in a structure expression.
:return: True if the given dtype is valid with the given target.
"""
fields: Mapping[str, Any] = structured_dtype.fields or {} # type: ignore[assignment]
# Add the wildcard to the lexicon. We want to do this here to keep
# knowledge on wildcards in one place (this module).
type_per_name_with_wildcard: Dict[str, type] = {
**type_per_name,
"*": object,
} # type: ignore[arg-type]
if target.has_wildcard():
# Check from the Target's perspective. All fields in the Target should be
# in the subject.
def iterator() -> Generator[Tuple[str, Tuple[np.dtype, int]], None, None]: # type: ignore[type-arg] # pylint: disable=line-too-long
for name_ in target.get_names():
yield name_, fields.get(name_) # type: ignore[misc]
else:
# Check from the subject's perspective. All fields in the subject
# should be in the target.
if set(target.get_names()) != set(fields.keys()):
return False
def iterator() -> Generator[Tuple[str, Tuple[np.dtype, int]], None, None]: # type: ignore[type-arg] # pylint: disable=line-too-long
for name_, dtype_tuple_ in fields.items():
yield name_, dtype_tuple_ # type: ignore[misc]
for name, dtype_tuple in iterator():
field_in_target_not_in_subject = dtype_tuple is None
if field_in_target_not_in_subject or not _check_structure_field(
name, dtype_tuple, target, type_per_name_with_wildcard
):
return False
return True
def _check_structure_field(
name: str,
dtype_tuple: Tuple[np.dtype, int], # type: ignore[type-arg]
target: "Structure",
type_per_name_with_wildcard: Dict[str, type],
) -> bool:
dtype = dtype_tuple[0]
target_type_name = target.get_type(name)
target_type_shape_match = re.search(_REGEX_FIELD_SHAPE, target_type_name)
actual_type = dtype.type
if target_type_shape_match:
if not dtype.subdtype:
# the dtype does not contain a shape.
return False
actual_type = dtype.subdtype[0].type
target_type_shape = target_type_shape_match.group(1)
shape_corresponds = check_shape(dtype.shape, Shape[target_type_shape])
if not shape_corresponds:
return False
target_type_name = target_type_name.replace(
target_type_shape_match.group(0), ""
)
check_type_name(target_type_name, type_per_name_with_wildcard)
target_type = type_per_name_with_wildcard[target_type_name]
return issubclass(actual_type, target_type)
def check_type_names(
structure: "Structure", type_per_name: Dict[str, Type[object]]
) -> None:
"""
Check the given structure for any invalid type names in the given context
of type_per_name. Raises an InvalidStructureError if a type name is
invalid.
:param structure: the Structure that is checked.
:param type_per_name: the context that determines which type names are valid.
:return: None.
"""
for type_ in structure.get_types():
check_type_name(type_, type_per_name)
def check_type_name(type_name: str, type_per_name: Dict[str, Type[object]]) -> None:
"""
Check if the given type_name is in type_per_name and raise a meaningful
error if not.
:param type_name: the key that is checked to be in type_per_name.
:param type_per_name: a dict that is looked in for type_name.
:return: None.
"""
# Remove any subarray stuff here.
type_name = type_name.split("[")[0]
if type_name not in type_per_name:
close_matches = get_close_matches(
type_name, type_per_name.keys(), 3, cutoff=0.4
)
close_matches_str = ", ".join(f"'{match}'" for match in close_matches)
extra_help = ""
if len(close_matches) > 1:
extra_help = f" Did you mean one of {close_matches_str}?"
elif close_matches:
extra_help = f" Did you mean {close_matches_str}?"
raise InvalidStructureError( # pylint: disable=raise-missing-from
f"Type '{type_name}' is not valid in this context.{extra_help}"
)
def normalize_structure_expression(
structure_expression: StructureExpression,
) -> StructureExpression:
"""
Normalize the given structure expression, e.g. by removing whitespaces,
making similar expressions look the same.
:param structure_expression: the structure expression that is to be normalized.
:return: a normalized structure expression.
"""
structure_expression = re.sub(r"\s*", "", structure_expression)
type_to_names_dict = _create_type_to_names_dict(structure_expression)
normalized_structure_expression = _type_to_names_dict_to_str(type_to_names_dict)
result = normalized_structure_expression.replace(",", ", ").replace(" ", " ")
has_wildcard_end = structure_expression.replace(" ", "").endswith(",*")
if has_wildcard_end:
result += ", *"
return result
def create_name_to_type_dict(
structure_expression: StructureExpression,
) -> Dict[str, str]:
"""
Create a dict with a name as key and a type (str) as value from the given
structure expression. Structure["x: Int, y: Float"] would yield
{"x: "Int", "y": "Float"}.
:param structure_expression: the structure expression from which the dict
is extracted.
:return: a dict with names and their types, both as strings.
"""
type_to_names_dict = _create_type_to_names_dict(structure_expression)
return {
name.strip(): type_.strip()
for type_, names in type_to_names_dict.items()
for name in names
}
def _validate_structure_expression_contains_no_multiple_field_names(
structure_expression: StructureExpression,
) -> None:
# Validate that there are not multiple occurrences of the same field names.
matches = re.findall(_REGEX_FIELD, re.sub(r"\s*", "", structure_expression))
field_name_combinations = [match[0].split(":")[0] for match in matches]
field_names: List[str] = []
for field_name_combination in field_name_combinations:
field_name_combination_match = re.match(
_REGEX_FIELD_NAMES_COMBINATION, field_name_combination
)
if field_name_combination_match:
field_names += field_name_combination_match.group(2).split(_SEPARATOR)
else:
field_names.append(field_name_combination)
field_name_counter = Counter(field_names)
field_names_occurring_multiple_times = [
field_name for field_name, amount in field_name_counter.items() if amount > 1
]
if field_names_occurring_multiple_times:
# If there are multiple, just raise about the first. Otherwise the
# error message gets bloated.
field_name_under_fire = field_names_occurring_multiple_times[0]
raise InvalidStructureError(
f"Field names may occur only once in a structure expression."
f" Field name '{field_name_under_fire}' occurs"
f" {field_name_counter[field_name_under_fire]} times in"
f" '{structure_expression}'."
)
def _validate_sub_array_expressions(structure_expression: str) -> None:
# Validate that the given structure expression does not contain any shape
# expressions for sub arrays that are invalid.
for field_match in re.findall(_REGEX_FIELD, structure_expression):
field_type = field_match[0].split(_FIELD_TYPE_POINTER)[1]
type_shape_match = re.search(_REGEX_FIELD_SHAPE, field_type)
if type_shape_match:
type_shape = type_shape_match[1]
try:
validate_shape_expression(type_shape)
except InvalidShapeError as err:
raise InvalidStructureError(
f"'{structure_expression}' is not a valid structure"
f" expression; {str(err)}"
) from err
def _create_type_to_names_dict(
structure_expression: StructureExpression,
) -> Dict[str, List[str]]:
# Create a dictionary with field names per type, sorted by type and then by
# name.
names_per_type: Dict[str, List[str]] = defaultdict(list)
for field_match in re.findall(_REGEX_FIELD, structure_expression):
field_name_combination, field_type = field_match[0].split(_FIELD_TYPE_POINTER)
field_name_combination_match = re.match(
_REGEX_FIELD_NAMES_COMBINATION, field_name_combination
)
field_type_shape_match = re.search(_REGEX_FIELD_SHAPE, field_type)
if field_name_combination_match:
field_names = field_name_combination_match.group(2).split(_SEPARATOR)
else:
field_names = [field_name_combination]
if field_type_shape_match:
type_shape = field_type_shape_match.group(1)
normalized_type_shape = normalize_shape_expression(type_shape)
field_type = field_type.replace(
field_type_shape_match.group(0), f"[{normalized_type_shape}]"
)
names_per_type[field_type] += field_names
return {
field_type: sorted(names_per_type[field_type])
for field_type in sorted(names_per_type.keys())
}
def _type_to_names_dict_to_str(type_to_names_dict: Dict[str, List[str]]) -> str:
# Turn the given dict into a structure expression.
field_strings = []
for field_type, field_names in type_to_names_dict.items():
field_names_joined = f"{_SEPARATOR}".join(field_names)
if len(field_names) > 1:
field_names_joined = f"[{field_names_joined}]"
field_strings.append(f"{field_names_joined}{_FIELD_TYPE_POINTER} {field_type}")
return f"{_SEPARATOR}".join(field_strings)
_SEPARATOR = ","
_FIELD_TYPE_POINTER = ":"
_REGEX_SEPARATOR = rf"(\s*{_SEPARATOR}\s*)"
_REGEX_FIELD_NAME = r"(\s*[a-zA-Z]\w*\s*)"
_REGEX_FIELD_NAMES = rf"({_REGEX_FIELD_NAME}({_REGEX_SEPARATOR}{_REGEX_FIELD_NAME})+)"
_REGEX_FIELD_NAMES_COMBINATION = rf"(\s*\[{_REGEX_FIELD_NAMES}\]\s*)"
_REGEX_FIELD_LEFT = rf"({_REGEX_FIELD_NAME}|{_REGEX_FIELD_NAMES_COMBINATION})"
_REGEX_FIELD_TYPE = r"(\s*[a-zA-Z]\w*\s*)"
_REGEX_FIELD_TYPE_WILDCARD = r"(\s*\*\s*)"
_REGEX_FIELD_SHAPE = r"\[([^\]]+)\]"
_REGEX_FIELD_SHAPE_MAYBE = rf"\s*({_REGEX_FIELD_SHAPE})?\s*"
_REGEX_FIELD_RIGHT = (
rf"({_REGEX_FIELD_TYPE}|{_REGEX_FIELD_TYPE_WILDCARD}){_REGEX_FIELD_SHAPE_MAYBE}"
)
_REGEX_FIELD_TYPE_POINTER = rf"(\s*{_FIELD_TYPE_POINTER}\s*)"
_REGEX_FIELD = (
rf"(\s*{_REGEX_FIELD_LEFT}{_REGEX_FIELD_TYPE_POINTER}{_REGEX_FIELD_RIGHT}\s*)"
)
_REGEX_STRUCTURE_EXPRESSION = (
rf"^({_REGEX_FIELD}"
rf"({_REGEX_SEPARATOR}{_REGEX_FIELD})*"
rf"({_REGEX_SEPARATOR}{_REGEX_FIELD_TYPE_WILDCARD})?)$"
)

View file

@ -0,0 +1,185 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
try:
from typing import ( # type: ignore[attr-defined,misc] # pylint: disable=unused-import
Literal,
TypeAlias,
TypeGuard,
final,
)
except ImportError: # pragma: no cover
from typing_extensions import ( # type: ignore[attr-defined,misc]
Literal,
TypeAlias,
TypeGuard,
final,
)
from typing import Tuple, Union
import numpy as np
ShapeExpression: TypeAlias = str
StructureExpression: TypeAlias = str
DType: TypeAlias = Union[np.generic, StructureExpression]
ShapeTuple: TypeAlias = Tuple[int, ...]
Number = np.number
Bool = np.bool_
Bool8 = np.bool8
Obj = np.object_ # Obj is a common abbreviation and should be usable.
Object = np.object_
Object0 = np.object0
Datetime64 = np.datetime64
Integer = np.integer
SignedInteger = np.signedinteger
Int8 = np.int8
Int16 = np.int16
Int32 = np.int32
Int64 = np.int64
Byte = np.byte
Short = np.short
IntC = np.intc
IntP = np.intp
Int0 = np.int0
Int = np.integer # Int should translate to the "generic" int type.
Int_ = np.int_
LongLong = np.longlong
Timedelta64 = np.timedelta64
UnsignedInteger = np.unsignedinteger
UInt8 = np.uint8
UInt16 = np.uint16
UInt32 = np.uint32
UInt64 = np.uint64
UByte = np.ubyte
UShort = np.ushort
UIntC = np.uintc
UIntP = np.uintp
UInt0 = np.uint0
UInt = np.uint
ULongLong = np.ulonglong
Inexact = np.inexact
Floating = np.floating
Float16 = np.float16
Float32 = np.float32
Float64 = np.float64
Half = np.half
Single = np.single
Double = np.double
Float = np.float_
LongDouble = np.longdouble
LongFloat = np.longfloat
ComplexFloating = np.complexfloating
Complex64 = np.complex64
Complex128 = np.complex128
CSingle = np.csingle
SingleComplex = np.singlecomplex
CDouble = np.cdouble
Complex = np.complex_
CFloat = np.cfloat
CLongDouble = np.clongdouble
CLongFloat = np.clongfloat
LongComplex = np.longcomplex
Flexible = np.flexible
Void = np.void
Void0 = np.void0
Character = np.character
Bytes = np.bytes_
Str = np.str_
String = np.string_
Bytes0 = np.bytes0
Unicode = np.unicode_
Str0 = np.str0
dtypes = [
(Number, "Number"),
(Bool, "Bool"),
(Bool8, "Bool8"),
(Obj, "Obj"),
(Object, "Object"),
(Object0, "Object0"),
(Datetime64, "Datetime64"),
(Integer, "Integer"),
(SignedInteger, "SignedInteger"),
(Int8, "Int8"),
(Int16, "Int16"),
(Int32, "Int32"),
(Int64, "Int64"),
(Byte, "Byte"),
(Short, "Short"),
(IntC, "IntC"),
(IntP, "IntP"),
(Int0, "Int0"),
(Int, "Int"),
(LongLong, "LongLong"),
(Timedelta64, "Timedelta64"),
(UnsignedInteger, "UnsignedInteger"),
(UInt8, "UInt8"),
(UInt16, "UInt16"),
(UInt32, "UInt32"),
(UInt64, "UInt64"),
(UByte, "UByte"),
(UShort, "UShort"),
(UIntC, "UIntC"),
(UIntP, "UIntP"),
(UInt0, "UInt0"),
(UInt, "UInt"),
(ULongLong, "ULongLong"),
(Inexact, "Inexact"),
(Floating, "Floating"),
(Float16, "Float16"),
(Float32, "Float32"),
(Float64, "Float64"),
(Half, "Half"),
(Single, "Single"),
(Double, "Double"),
(Float, "Float"),
(LongDouble, "LongDouble"),
(LongFloat, "LongFloat"),
(ComplexFloating, "ComplexFloating"),
(Complex64, "Complex64"),
(Complex128, "Complex128"),
(CSingle, "CSingle"),
(SingleComplex, "SingleComplex"),
(CDouble, "CDouble"),
(Complex, "Complex"),
(CFloat, "CFloat"),
(CLongDouble, "CLongDouble"),
(CLongFloat, "CLongFloat"),
(LongComplex, "LongComplex"),
(Flexible, "Flexible"),
(Void, "Void"),
(Void0, "Void0"),
(Character, "Character"),
(Bytes, "Bytes"),
(String, "String"),
(Str, "Str"),
(Bytes0, "Bytes0"),
(Unicode, "Unicode"),
(Str0, "Str0"),
]
name_per_dtype = dict(dtypes)
dtype_per_name = {name: dtype for dtype, name in dtypes}

View file

@ -0,0 +1,114 @@
"""
MIT License
Copyright (c) 2023 Ramon Hagenaars
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
try:
from typing import ( # type: ignore[attr-defined] # pylint: disable=unused-import
Dict,
TypeAlias,
)
except ImportError: # pragma: no cover
from typing_extensions import (
TypeAlias,
)
from typing import (
Any,
Tuple,
Union,
)
import numpy as np
ShapeExpression: TypeAlias = str
StructureExpression: TypeAlias = str
DType: TypeAlias = Union[np.generic, StructureExpression]
ShapeTuple: TypeAlias = Tuple[int, ...]
Number: TypeAlias = np.dtype[np.number[Any]]
Bool: TypeAlias = np.dtype[np.bool_]
Bool8: TypeAlias = np.dtype[np.bool8]
Object: TypeAlias = np.dtype[np.object_]
Object0: TypeAlias = np.dtype[np.object0]
Datetime64: TypeAlias = np.dtype[np.datetime64]
Integer: TypeAlias = np.dtype[np.integer[Any]]
SignedInteger: TypeAlias = np.dtype[np.signedinteger[Any]]
Int8: TypeAlias = np.dtype[np.int8]
Int16: TypeAlias = np.dtype[np.int16]
Int32: TypeAlias = np.dtype[np.int32]
Int64: TypeAlias = np.dtype[np.int64]
Byte: TypeAlias = np.dtype[np.byte]
Short: TypeAlias = np.dtype[np.short]
IntC: TypeAlias = np.dtype[np.intc]
IntP: TypeAlias = np.dtype[np.intp]
Int0: TypeAlias = np.dtype[np.int0]
Int: TypeAlias = np.dtype[np.int_]
LongLong: TypeAlias = np.dtype[np.longlong]
Timedelta64: TypeAlias = np.dtype[np.timedelta64]
UnsignedInteger: TypeAlias = np.dtype[np.unsignedinteger[Any]]
UInt8: TypeAlias = np.dtype[np.uint8]
UInt16: TypeAlias = np.dtype[np.uint16]
UInt32: TypeAlias = np.dtype[np.uint32]
UInt64: TypeAlias = np.dtype[np.uint64]
UByte: TypeAlias = np.dtype[np.ubyte]
UShort: TypeAlias = np.dtype[np.ushort]
UIntC: TypeAlias = np.dtype[np.uintc]
UIntP: TypeAlias = np.dtype[np.uintp]
UInt0: TypeAlias = np.dtype[np.uint0]
UInt: TypeAlias = np.dtype[np.uint]
ULongLong: TypeAlias = np.dtype[np.ulonglong]
Inexact: TypeAlias = np.dtype[np.inexact[Any]]
Floating: TypeAlias = np.dtype[np.floating[Any]]
Float16: TypeAlias = np.dtype[np.float16]
Float32: TypeAlias = np.dtype[np.float32]
Float64: TypeAlias = np.dtype[np.float64]
Half: TypeAlias = np.dtype[np.half]
Single: TypeAlias = np.dtype[np.single]
Double: TypeAlias = np.dtype[np.double]
Float: TypeAlias = np.dtype[np.float_]
LongDouble: TypeAlias = np.dtype[np.longdouble]
LongFloat: TypeAlias = np.dtype[np.longfloat]
ComplexFloating: TypeAlias = np.dtype[np.complexfloating[Any, Any]]
Complex64: TypeAlias = np.dtype[np.complex64]
Complex128: TypeAlias = np.dtype[np.complex128]
CSingle: TypeAlias = np.dtype[np.csingle]
SingleComplex: TypeAlias = np.dtype[np.singlecomplex]
CDouble: TypeAlias = np.dtype[np.cdouble]
Complex: TypeAlias = np.dtype[np.complex_]
CFloat: TypeAlias = np.dtype[np.cfloat]
CLongDouble: TypeAlias = np.dtype[np.clongdouble]
CLongFloat: TypeAlias = np.dtype[np.clongfloat]
LongComplex: TypeAlias = np.dtype[np.longcomplex]
Flexible: TypeAlias = np.dtype[np.flexible]
Void: TypeAlias = np.dtype[np.void]
Void0: TypeAlias = np.dtype[np.void0]
Character: TypeAlias = np.dtype[np.character]
Bytes: TypeAlias = np.dtype[np.bytes_]
Str: TypeAlias = np.dtype[np.str_]
String: TypeAlias = np.dtype[np.string_]
Bytes0: TypeAlias = np.dtype[np.bytes0]
Unicode: TypeAlias = np.dtype[np.unicode_]
Str0: TypeAlias = np.dtype[np.str0]
dtype_per_name: Dict[str, np.dtype[Any]]
name_per_dtype: Dict[np.dtype[Any], str]