Skip to content

Commit

Permalink
Merge pull request #42 from ramonhagenaars/Release/1.4.0
Browse files Browse the repository at this point in the history
Release/1.4.0
  • Loading branch information
ramonhagenaars committed Dec 23, 2020
2 parents c813f6d + 7a31cfe commit bb27a73
Show file tree
Hide file tree
Showing 17 changed files with 346 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [ '3.5', '3.6', '3.7', '3.8' ]
python-version: [ '3.5', '3.6', '3.7', '3.8', '3.9' ]
os: [ ubuntu-latest, macOS-latest, windows-latest ]
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
steps:
Expand Down
9 changes: 8 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
History
-------

1.4.0 (2020-12-23)
++++++++++++++++++

- Added ``SubArrayType``
- Added ``StructuredType``
- Added support for unsigned integers with ``py_type``.

1.3.0 (2020-07-21)
++++++++++++++++++

Expand Down Expand Up @@ -53,4 +60,4 @@ History
0.1.0 (2019-02-05)
++++++++++++++++++

- Initial release
- Initial release
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ NDArray[(3, 3, 5), Int[32]]

```

A structured array:
```python
>>> import numpy as np

>>> NDArray[(Any,...), np.dtype([('x',np.int32), ('y',np.int32)])]
NDArray[(typing.Any, ...), StructuredType[Int[32], Int[32]]]

```

#### (❒) Checking your instances
You can use `NDArray` with `isinstance` to dynamically check your arrays.

Expand Down Expand Up @@ -275,6 +284,28 @@ Object

```

### (❒) StructuredType
An nptyping equivalent of numpy structured dtypes.

```python
>>> from nptyping import StructuredType, Int

>>> StructuredType[Int[32], Int[32]]
StructuredType[Int[32], Int[32]]

```

### (❒) SubArrayType
An nptyping equivalent of numpy subarray dtypes.

```python
>>> from nptyping import SubArrayType, Int

>>> SubArrayType[Int[16], (4,2)]
SubArrayType[Int[16], (4, 2)]

```

### (❒) get_type
With `get_type` you can get `nptyping` equivalent types for your arguments:

Expand All @@ -285,6 +316,8 @@ With `get_type` you can get `nptyping` equivalent types for your arguments:
Int[32]
>>> get_type('some string')
Unicode[11]
>>> get_type(np.dtype([('x', np.int32), ('y', np.int32)]))
StructuredType[Int[32], Int[32]]

```

Expand Down
2 changes: 2 additions & 0 deletions nptyping/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@
Float64,
)
from nptyping.types._object import Object
from nptyping.types._subarray_type import SubArrayType
from nptyping.types._structured_type import StructuredType
from nptyping.types._timedelta64 import Timedelta64
from nptyping.types._unicode import Unicode
2 changes: 1 addition & 1 deletion nptyping/_meta.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = 'nptyping'
__version__ = '1.3.0'
__version__ = '1.4.0'
__author__ = 'Ramon Hagenaars'
__author_email__ = '[email protected]'
__description__ = 'Type hints for Numpy.'
Expand Down
26 changes: 26 additions & 0 deletions nptyping/functions/_get_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
DEFAULT_FLOAT_BITS,
)
from nptyping.types._object import Object
from nptyping.types._subarray_type import SubArrayType, is_subarray_type
from nptyping.types._structured_type import StructuredType, is_structured_type
from nptyping.types._timedelta64 import Timedelta64
from nptyping.types._unicode import Unicode

Expand All @@ -44,6 +46,10 @@ def _get_type_type(type_: type) -> Type['NPType']:

def _get_type_dtype(dtype: numpy.dtype) -> Type['NPType']:
# Return the nptyping type of a numpy dtype.
if is_subarray_type(dtype):
return get_subarray_type(dtype)
if is_structured_type(dtype):
return get_structured_type(dtype)
np_type_per_py_type = {
type: _get_type_type,
bool: get_type_bool,
Expand Down Expand Up @@ -184,6 +190,26 @@ def get_type_complex(_: Any) -> Type[Complex128]:
return Complex128


# Library private.
def get_structured_type(dtype: numpy.dtype) -> Type[StructuredType]:
"""
Return the NPType that corresponds to dtype of a structured array.
:param dtype: a dtype of a structured NumPy array
:return: a StructuredType type.
"""
return StructuredType[dtype]


# Library private.
def get_subarray_type(dtype: numpy.dtype) -> Type[SubArrayType]:
"""
Return the NPType that corresponds to dtype of a subarray.
:param dtype: a dtype of a NumPy subarray
:return: a SubArrayType type.
"""
return SubArrayType[dtype]


_delegates = [
(NPType, lambda x: x),
(type, _get_type_type),
Expand Down
1 change: 1 addition & 0 deletions nptyping/functions/_py_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def py_type(np_type: Union[np.dtype, type, Literal[Any]]) -> type:

_TYPE_PER_KIND = {
'i': int,
'u': int,
'f': float,
'U': str,
'O': object,
Expand Down
1 change: 1 addition & 0 deletions nptyping/types/_ndarray_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def _is_dtype_eq(cls, nptype: NPType) -> bool:
class _NDArray(NPType, metaclass=_NDArrayMeta):
_shape = (Any, ...) # type: Union[Tuple[int, ...], Tuple[Any, EllipsisType]] # noqa
_type = Any
_special = True # Added to be able to compile types with sphinx.

@classmethod
def _after_subscription(cls, item: Any) -> None:
Expand Down
58 changes: 58 additions & 0 deletions nptyping/types/_structured_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import Any

import numpy as np
from typish import SubscriptableType

from nptyping.types._nptype import NPType


def is_structured_type(dtype: np.dtype) -> bool:
"""Detect if the type is a structured array type."""
return hasattr(dtype, 'fields') and dtype.fields is not None


class _StructuredTypeMeta(SubscriptableType):
def __hash__(cls) -> int:
return hash(cls.fields)

def __repr__(cls) -> str:
if cls.fields is not None:
field_strs = ', '.join([str(f) for f in cls.fields])
return 'StructuredType[{}]'.format(field_strs)
return 'StructuredType'

def __eq__(cls, instance: Any) -> bool:
if hasattr(instance, 'fields'):
return (instance.fields == cls.fields
or tuple(instance.fields) == tuple(cls.fields))
return False

def __instancecheck__(cls, instance: Any) -> bool:
from nptyping.functions._get_type import get_type

return (is_structured_type(instance)
and (cls == instance or cls == get_type(instance)))

__str__ = __repr__
__subclasscheck__ = __eq__


class StructuredType(NPType, metaclass=_StructuredTypeMeta):
"""
Corresponds to the dtype of a structured NumPy array.
"""
fields = None

@classmethod
def _after_subscription(cls, args: Any) -> None:
from nptyping.functions._get_type import get_type

if isinstance(args, np.dtype):
cls.fields = tuple(get_type(args.fields[n][0]) for n in args.names)
elif isinstance(args, tuple):
cls.fields = tuple(get_type(t) for t in args)
elif isinstance(args, type):
cls.fields = (get_type(args),)
else:
raise Exception(
'Incompatible arguments to StructuredType: {}'.format(args))
61 changes: 61 additions & 0 deletions nptyping/types/_subarray_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import Any

import numpy as np
from typish import SubscriptableType

from nptyping.types._nptype import NPType


def is_subarray_type(dtype: np.dtype) -> bool:
"""Detect if the type is a subarray."""
return (hasattr(dtype, 'shape')
and isinstance(dtype.shape, tuple)
and len(dtype.shape) != 0)


class _SubArrayTypeMeta(SubscriptableType):
def __hash__(cls) -> int:
return hash((cls.base, cls.shape))

def __repr__(cls) -> str:
if cls.base is not None:
return 'SubArrayType[{0}, {1}]'.format(cls.base, cls.shape)
return 'SubArrayType'

def __eq__(cls, instance: Any) -> bool:
if hasattr(instance, 'base') and hasattr(instance, 'shape'):
return instance.base == cls.base and instance.shape == cls.shape
return False

def __instancecheck__(cls, instance: Any) -> bool:
from nptyping.functions._get_type import get_type

if is_subarray_type(instance):
if cls == instance:
return True
return cls == get_type(instance)
return False

__str__ = __repr__
__subclasscheck__ = __eq__


class SubArrayType(NPType, metaclass=_SubArrayTypeMeta):
"""
Corresponds to the dtype of a NumPy subarray.
"""
base = None
shape = None

@classmethod
def _after_subscription(cls, args: Any) -> None:
from nptyping.functions._get_type import get_type

if isinstance(args, np.dtype):
cls.base = get_type(args.base)
cls.shape = args.shape
elif isinstance(args, tuple):
cls.base, cls.shape = args
else:
raise Exception(
'Incompatible arguments to SubArrayType: {}'.format(args))
16 changes: 12 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
[pylint]
disable = too-many-instance-attributes,too-many-arguments
,missing-module-docstring,too-few-public-methods,fixme
,invalid-name,import-outside-toplevel,unidiomatic-typecheck
,cyclic-import,duplicate-code
disable =
too-many-instance-attributes,
too-many-arguments,
missing-module-docstring,
too-few-public-methods,
fixme,
invalid-name,
import-outside-toplevel,
unidiomatic-typecheck,
cyclic-import,
duplicate-code,
unsubscriptable-object
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

requirements = [
'numpy',
'typish>=1.5.2',
'typish>=1.7.0',
],

test_requirements = [
Expand Down Expand Up @@ -55,6 +55,7 @@
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8'
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
]
)
8 changes: 8 additions & 0 deletions tests/test_functions/test_get_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
Bool,
Datetime64,
Complex128,
StructuredType,
SubArrayType,
)
from nptyping.types._object import Object
from nptyping.types._timedelta64 import Timedelta64
Expand Down Expand Up @@ -118,3 +120,9 @@ class SomeRandomClass:
...

self.assertEqual(Object, get_type(SomeRandomClass()))

def test_get_type_structured_type(self):
self.assertEqual(StructuredType[Int[32]], get_type(np.dtype([('x', np.int32)])))

def test_get_type_subarray_type(self):
self.assertEqual(SubArrayType[Int[32], (3,)], get_type(np.dtype((np.int32, 3))))
5 changes: 5 additions & 0 deletions tests/test_functions/test_py_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ def test_py_type(self):
self.assertIs(int, py_type(np.int32))
self.assertIs(int, py_type(np.int64))

self.assertIs(int, py_type(np.uint))
self.assertIs(int, py_type(np.uint16))
self.assertIs(int, py_type(np.uint32))
self.assertIs(int, py_type(np.uint64))

self.assertIs(float, py_type(np.float))
self.assertIs(float, py_type(np.float16))
self.assertIs(float, py_type(np.float32))
Expand Down
14 changes: 11 additions & 3 deletions tests/test_types/test_ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
from unittest import TestCase

import numpy as np
from numpy.core.arrayprint import SubArrayFormat

from nptyping import NDArray, DEFAULT_INT_BITS, Int, Bool, Datetime64
from nptyping import NDArray, DEFAULT_INT_BITS, Int, Bool, Datetime64, StructuredType, SubArrayType
from nptyping.types._timedelta64 import Timedelta64


Expand Down Expand Up @@ -104,15 +105,15 @@ def test_instance_check_dimension_any(self):

def test_instance_check_types(self):
arr2x2x2_float = np.array([[[1.0, 2.0], [3.0, 4.0]],
[[5.0, 6.0], [7.0, 8.0]]])
[[5.0, 6.0], [7.0, 8.0]]])
arr2x2x2 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])

self.assertTrue(isinstance(arr2x2x2_float, NDArray[(2, 2, 2), float]))
self.assertTrue(not isinstance(arr2x2x2, NDArray[(2, 2, 2), str]))

def test_instance_check_types_any(self):
arr2x2x2_float = np.array([[[1.0, 2.0], [3.0, 4.0]],
[[5.0, 6.0], [7.0, 8.0]]])
[[5.0, 6.0], [7.0, 8.0]]])
arr2x2x2 = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
arr2x2 = np.array([[1, 2], [3, 4]])

Expand Down Expand Up @@ -202,3 +203,10 @@ def test_instance_check_with_np_types(self):
self.assertNotIsInstance(np.array([[True, False], [True, 42]]), NDArray[(Any, ...), Bool])
self.assertIsInstance(np.array([np.datetime64()]), NDArray[(Any, ...), Datetime64])
self.assertIsInstance(np.array([np.timedelta64()]), NDArray[(Any, ...), Timedelta64])

def test_instance_check_with_structured_types(self):
some_dtype = np.dtype([('x', np.int32), ('y', np.int32, 4)])
some_other_dtype = np.dtype([('x', np.int32), ('y', np.int32, 3)])
self.assertIsInstance(np.zeros((1,), dtype=some_dtype), NDArray[(Any, ...), some_dtype])
self.assertNotIsInstance(np.zeros((1,), dtype=some_dtype), NDArray[(Any, ...), some_other_dtype])
self.assertIsInstance(np.zeros((1,), dtype=some_dtype), NDArray[(Any, ...), StructuredType[Int[32], SubArrayType[Int[32], (4,)]]])
Loading

0 comments on commit bb27a73

Please sign in to comment.