diff --git a/docs/api/typing_validation.rst b/docs/api/typing_validation.rst index 1db0a5e..43d88c9 100644 --- a/docs/api/typing_validation.rst +++ b/docs/api/typing_validation.rst @@ -12,6 +12,7 @@ The following members were explicitly reexported using ``__all__``: - :py:class:`typing_validation.inspector.UnsupportedType` - :py:func:`typing_validation.validation.can_validate` - :py:func:`typing_validation.validation_failure.get_validation_failure` + - :py:func:`typing_validation.validation.inspect_type` - :py:func:`typing_validation.validation.is_valid` - :py:func:`typing_validation.validation_failure.latest_validation_failure` - :py:func:`typing_validation.validation.validate` diff --git a/docs/conf.py b/docs/conf.py index ad34077..9743b04 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,9 +33,9 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = "1.2.10" +release = "1.2.11" # The short X.Y version. -version = "1.2.10" +version = "1.2.11" # -- General configuration --------------------------------------------------- diff --git a/run-black.bat b/run-black.bat index 4fc5475..1eb7c1b 100644 --- a/run-black.bat +++ b/run-black.bat @@ -1,2 +1,3 @@ black -l 80 -t py38 -t py39 -t py310 -t py311 -t py312 .\typing_validation +black -l 80 -t py38 -t py39 -t py310 -t py311 -t py312 .\test @pause diff --git a/test/test_00_validate.py b/test/test_00_validate.py index aa0e52e..721b191 100644 --- a/test/test_00_validate.py +++ b/test/test_00_validate.py @@ -4,22 +4,16 @@ import collections.abc as collections_abc import sys import typing +from typing_extensions import Literal, TypedDict, Required, NotRequired import pytest from typing_validation import validate, validation_aliases -from typing_validation.validation import _pseudotypes, _pseudotypes_dict, _is_typed_dict - - -if sys.version_info[1] >= 8: - from typing import Literal -else: - from typing_extensions import Literal - -if sys.version_info[1] >= 9: - from typing import TypedDict -else: - from typing_extensions import TypedDict +from typing_validation.validation import ( + _pseudotypes, + _pseudotypes_dict, + _is_typed_dict, +) if sys.version_info[1] >= 10: from types import UnionType @@ -28,168 +22,413 @@ _basic_types = [ - bool, int, float, complex, str, bytes, bytearray, - list, tuple, set, frozenset, dict, type(None) + bool, + int, + float, + complex, + str, + bytes, + bytearray, + list, + tuple, + set, + frozenset, + dict, + type(None), ] -_all_types = _basic_types+sorted(_pseudotypes, key=repr) +_all_types = _basic_types + sorted(_pseudotypes, key=repr) _basic_cases = ( (True, [bool, int, typing.Hashable]), (12, [int, typing.Hashable]), (13.5, [float, typing.Hashable]), - (1+2j, [complex, typing.Hashable]), + (1 + 2j, [complex, typing.Hashable]), (None, [None, type(None), typing.Hashable]), ) -_collection_cases: typing.Tuple[typing.Tuple[typing.Any, typing.List[typing.Any]], ...] = ( - ("hello", [str, typing.Collection, typing.Collection[str], - typing.Sequence, typing.Sequence[str], - typing.Iterable, typing.Sized, typing.Hashable, typing.Container]), - (b"hello", [bytes, typing.Collection, typing.Collection[int], - typing.Sequence, typing.Sequence[int], typing.Iterable, - typing.Container, typing.Sized, typing.Hashable, - *( - [typing.ByteString] - if sys.version_info[1] <= 11 - else [typing.cast(typing.Any, collections_abc.Buffer)] - )]), - ([0, 1, 2], [list, typing.List, typing.List[int], - typing.Collection, typing.Collection[int], - typing.Sequence, typing.Sequence[int], - typing.MutableSequence, typing.MutableSequence[int], - typing.Iterable, typing.Iterable[int], typing.Sized, typing.Container]), - ((0, 1, 2), [tuple, typing.Tuple, typing.Tuple[int, int, int], typing.Tuple[int, ...], - typing.Collection, typing.Collection[int], - typing.Sequence, typing.Sequence[int], - typing.Iterable, typing.Iterable[int], typing.Sized, typing.Hashable, typing.Container]), - ({0, 1, 2}, [set, typing.Set, typing.Set[int], - typing.Collection, typing.Collection[int], - typing.AbstractSet, typing.AbstractSet[int], - typing.MutableSet, typing.MutableSet[int], - typing.Iterable, typing.Iterable[int], typing.Sized, typing.Container]), - (frozenset({0, 1, 2}), [frozenset, typing.FrozenSet, typing.FrozenSet[int], - typing.Collection, typing.Collection[int], - typing.AbstractSet, typing.AbstractSet[int], - typing.Iterable, typing.Iterable[int], typing.Sized, typing.Hashable, - typing.Container]), - (deque([0, 1, 2]), [deque, typing.Deque, typing.Deque[int], - typing.Collection, typing.Collection[int], - typing.Sequence, typing.Sequence[int], - typing.MutableSequence, typing.MutableSequence[int], - typing.Iterable, typing.Iterable[int], typing.Sized, typing.Container]), +_collection_cases: typing.Tuple[ + typing.Tuple[typing.Any, typing.List[typing.Any]], ... +] = ( + ( + "hello", + [ + str, + typing.Collection, + typing.Collection[str], + typing.Sequence, + typing.Sequence[str], + typing.Iterable, + typing.Sized, + typing.Hashable, + typing.Container, + ], + ), + ( + b"hello", + [ + bytes, + typing.Collection, + typing.Collection[int], + typing.Sequence, + typing.Sequence[int], + typing.Iterable, + typing.Container, + typing.Sized, + typing.Hashable, + *( + [typing.ByteString] + if sys.version_info[1] <= 11 + else [typing.cast(typing.Any, collections_abc.Buffer)] + ), + ], + ), + ( + [0, 1, 2], + [ + list, + typing.List, + typing.List[int], + typing.Collection, + typing.Collection[int], + typing.Sequence, + typing.Sequence[int], + typing.MutableSequence, + typing.MutableSequence[int], + typing.Iterable, + typing.Iterable[int], + typing.Sized, + typing.Container, + ], + ), + ( + (0, 1, 2), + [ + tuple, + typing.Tuple, + typing.Tuple[int, int, int], + typing.Tuple[int, ...], + typing.Collection, + typing.Collection[int], + typing.Sequence, + typing.Sequence[int], + typing.Iterable, + typing.Iterable[int], + typing.Sized, + typing.Hashable, + typing.Container, + ], + ), + ( + {0, 1, 2}, + [ + set, + typing.Set, + typing.Set[int], + typing.Collection, + typing.Collection[int], + typing.AbstractSet, + typing.AbstractSet[int], + typing.MutableSet, + typing.MutableSet[int], + typing.Iterable, + typing.Iterable[int], + typing.Sized, + typing.Container, + ], + ), + ( + frozenset({0, 1, 2}), + [ + frozenset, + typing.FrozenSet, + typing.FrozenSet[int], + typing.Collection, + typing.Collection[int], + typing.AbstractSet, + typing.AbstractSet[int], + typing.Iterable, + typing.Iterable[int], + typing.Sized, + typing.Hashable, + typing.Container, + ], + ), + ( + deque([0, 1, 2]), + [ + deque, + typing.Deque, + typing.Deque[int], + typing.Collection, + typing.Collection[int], + typing.Sequence, + typing.Sequence[int], + typing.MutableSequence, + typing.MutableSequence[int], + typing.Iterable, + typing.Iterable[int], + typing.Sized, + typing.Container, + ], + ), ) _mapping_cases = ( - ({"a": 0, "b": 1}, [dict, typing.Collection, typing.Collection[str], - typing.Dict, typing.Dict[str, int], - typing.Mapping, typing.Mapping[str, int], - typing.MutableMapping, typing.MutableMapping[str, int], - typing.Iterable, typing.Iterable[str], typing.Sized, typing.Container]), - (defaultdict(lambda: 0, {"a": 0, "b": 1}), - [defaultdict, typing.Collection, typing.Collection[str], - typing.Dict, typing.Dict[str, int], - typing.DefaultDict, typing.DefaultDict[str, int], - typing.Mapping, typing.Mapping[str, int], - typing.MutableMapping, typing.MutableMapping[str, int], - typing.Iterable, typing.Iterable[str], typing.Sized, typing.Container,]), + ( + {"a": 0, "b": 1}, + [ + dict, + typing.Collection, + typing.Collection[str], + typing.Dict, + typing.Dict[str, int], + typing.Mapping, + typing.Mapping[str, int], + typing.MutableMapping, + typing.MutableMapping[str, int], + typing.Iterable, + typing.Iterable[str], + typing.Sized, + typing.Container, + ], + ), + ( + defaultdict(lambda: 0, {"a": 0, "b": 1}), + [ + defaultdict, + typing.Collection, + typing.Collection[str], + typing.Dict, + typing.Dict[str, int], + typing.DefaultDict, + typing.DefaultDict[str, int], + typing.Mapping, + typing.Mapping[str, int], + typing.MutableMapping, + typing.MutableMapping[str, int], + typing.Iterable, + typing.Iterable[str], + typing.Sized, + typing.Container, + ], + ), ) -_test_cases = _basic_cases+_collection_cases+_mapping_cases +_test_cases = _basic_cases + _collection_cases + _mapping_cases + @pytest.mark.parametrize("val, ts", _test_cases) def test_valid_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: - ts = ts+[_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] + ts = ts + [_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] for t in ts: validate(val, t) validate(val, typing.Optional[t]) validate(val, typing.Any) + @pytest.mark.parametrize("val, ts", _test_cases) def test_invalid_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: - ts = ts+[_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] + ts = ts + [_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] for t in [t for t in _all_types if t not in ts]: try: validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass + _specific_invalid_cases = ( - ("hello", [typing.Collection[int], typing.Sequence[int], typing.List[str], typing.Deque[str], - typing.Tuple[str, ...], typing.Tuple[str, str, str, str, str], typing.Dict[int, str]]), - (b"hello", [typing.Collection[str], typing.Sequence[str], typing.List[int], typing.Deque[int], - typing.Tuple[int, ...], typing.Dict[int, int]]), - ([0, 1, 2], [typing.List[str], typing.Collection[str], typing.Sequence[str], typing.MutableSequence[str],]), - ((0, 1, 2), [typing.Tuple[str, int, int], typing.Tuple[int, str, int], typing.Tuple[int, int, str], - typing.Tuple[int, int], typing.Tuple[int, int, int, int], - typing.Tuple[str, ...], typing.Collection[str], typing.Sequence[str], - typing.List[int], typing.Deque[int], typing.Dict[int, int]]), - ({0, 1, 2}, [typing.Set[str], typing.Collection[str], typing.Sequence[str], typing.MutableSet[str], - typing.List[int], typing.Deque[int], typing.Dict[int, int], typing.FrozenSet[int]]), - (frozenset({0, 1, 2}), [typing.FrozenSet[str], typing.Collection[str], typing.Sequence[str], - typing.List[int], typing.Deque[int], typing.Dict[int, int], typing.Set[int],]), - (deque([0, 1, 2]), [typing.Deque[str], typing.Collection[str], typing.Sequence[str], typing.MutableSequence[str], - typing.List[int], typing.Dict[int, int], typing.Set[int], typing.FrozenSet[int],]), - ({"a": 0, "b": 1}, [typing.Collection[int], - typing.Dict[str, str], typing.Dict[int, int], - typing.Mapping[str, str], typing.Mapping[int, int], - typing.MutableMapping[str, str], typing.MutableMapping[int, int], - typing.List[str], typing.Deque[str], typing.Set[str], typing.FrozenSet[str]]), - (defaultdict(lambda: 0, {"a": 0, "b": 1}), [typing.Collection[int], - typing.Dict[str, str], typing.Dict[int, int], - typing.DefaultDict[str, str], typing.DefaultDict[int, int], - typing.Mapping[str, str], typing.Mapping[int, int], - typing.MutableMapping[str, str], typing.MutableMapping[int, int], - typing.List[str], typing.Deque[str], typing.Set[str], typing.FrozenSet[str]]), + ( + "hello", + [ + typing.Collection[int], + typing.Sequence[int], + typing.List[str], + typing.Deque[str], + typing.Tuple[str, ...], + typing.Tuple[str, str, str, str, str], + typing.Dict[int, str], + ], + ), + ( + b"hello", + [ + typing.Collection[str], + typing.Sequence[str], + typing.List[int], + typing.Deque[int], + typing.Tuple[int, ...], + typing.Dict[int, int], + ], + ), + ( + [0, 1, 2], + [ + typing.List[str], + typing.Collection[str], + typing.Sequence[str], + typing.MutableSequence[str], + ], + ), + ( + (0, 1, 2), + [ + typing.Tuple[str, int, int], + typing.Tuple[int, str, int], + typing.Tuple[int, int, str], + typing.Tuple[int, int], + typing.Tuple[int, int, int, int], + typing.Tuple[str, ...], + typing.Collection[str], + typing.Sequence[str], + typing.List[int], + typing.Deque[int], + typing.Dict[int, int], + ], + ), + ( + {0, 1, 2}, + [ + typing.Set[str], + typing.Collection[str], + typing.Sequence[str], + typing.MutableSet[str], + typing.List[int], + typing.Deque[int], + typing.Dict[int, int], + typing.FrozenSet[int], + ], + ), + ( + frozenset({0, 1, 2}), + [ + typing.FrozenSet[str], + typing.Collection[str], + typing.Sequence[str], + typing.List[int], + typing.Deque[int], + typing.Dict[int, int], + typing.Set[int], + ], + ), + ( + deque([0, 1, 2]), + [ + typing.Deque[str], + typing.Collection[str], + typing.Sequence[str], + typing.MutableSequence[str], + typing.List[int], + typing.Dict[int, int], + typing.Set[int], + typing.FrozenSet[int], + ], + ), + ( + {"a": 0, "b": 1}, + [ + typing.Collection[int], + typing.Dict[str, str], + typing.Dict[int, int], + typing.Mapping[str, str], + typing.Mapping[int, int], + typing.MutableMapping[str, str], + typing.MutableMapping[int, int], + typing.List[str], + typing.Deque[str], + typing.Set[str], + typing.FrozenSet[str], + ], + ), + ( + defaultdict(lambda: 0, {"a": 0, "b": 1}), + [ + typing.Collection[int], + typing.Dict[str, str], + typing.Dict[int, int], + typing.DefaultDict[str, str], + typing.DefaultDict[int, int], + typing.Mapping[str, str], + typing.Mapping[int, int], + typing.MutableMapping[str, str], + typing.MutableMapping[int, int], + typing.List[str], + typing.Deque[str], + typing.Set[str], + typing.FrozenSet[str], + ], + ), ) + @pytest.mark.parametrize("val, ts", _specific_invalid_cases) -def test_specific_invalid_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: - ts = ts+[_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] +def test_specific_invalid_cases( + val: typing.Any, ts: typing.List[typing.Any] +) -> None: + ts = ts + [_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] for t in ts: try: validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass -_union_cases: typing.Tuple[typing.Tuple[typing.Any, typing.List[typing.Any]], ...] + +_union_cases: typing.Tuple[ + typing.Tuple[typing.Any, typing.List[typing.Any]], ... +] _union_cases = ( (0, [typing.Union[str, int], typing.Union[int, str], typing.Optional[int]]), - ("hello", [typing.Union[str, int], typing.Union[int, str], typing.Optional[str]]), + ( + "hello", + [typing.Union[str, int], typing.Union[int, str], typing.Optional[str]], + ), ) if sys.version_info[1] >= 10: _union_cases += ( - (0, [str|int, int|str, int|None]), - ("hello", [str|int, int|str, str|None]), + (0, [str | int, int | str, int | None]), + ("hello", [str | int, int | str, str | None]), ) + @pytest.mark.parametrize("val, ts", _union_cases) def test_union_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: for t in ts: validate(val, t) + _invalid_union_cases = ( (0, [typing.Union[str, bool], typing.Optional[str]]), ("hello", [typing.Union[bool, int], typing.Optional[int]]), ) + @pytest.mark.parametrize("val, ts", _invalid_union_cases) -def test_invalid_union_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: +def test_invalid_union_cases( + val: typing.Any, ts: typing.List[typing.Any] +) -> None: for t in ts: try: validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass + _literal_cases = ( ("0", [Literal["0", "1", 2], Literal["0"], Literal["hello", 0, "0"]]), (0, [Literal[0, "1", 2], Literal[0], Literal["hello", 0, "0"]]), ) + @pytest.mark.parametrize("val, ts", _literal_cases) def test_literal_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: for t in ts: @@ -201,32 +440,46 @@ def test_literal_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: (0, [Literal["0", "1", 2], Literal["0"], Literal["hello", 1, "1"]]), ) + @pytest.mark.parametrize("val, ts", _invalid_literal_cases) -def test_invalid_literal_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: +def test_invalid_literal_cases( + val: typing.Any, ts: typing.List[typing.Any] +) -> None: for t in ts: try: validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass -JSON = typing.Union[int, float, bool, None, str, typing.List["JSON"], typing.Dict[str, "JSON"]] -_validation_aliases = { - "JSON": JSON -} + +JSON = typing.Union[ + int, float, bool, None, str, typing.List["JSON"], typing.Dict[str, "JSON"] +] +_validation_aliases = {"JSON": JSON} _alias_cases = ( - ([1, 2.2,], {"JSON"}), + ( + [ + 1, + 2.2, + ], + {"JSON"}, + ), ([1, 2.2, {"a": False}], {"JSON"}), ([1, 2.2, {"a": ["1", None, {"b": False}]}], {"JSON"}), ) + @pytest.mark.parametrize("val, ts", _alias_cases) def test_alias_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: for t in ts: with validation_aliases(**_validation_aliases): validate(val, t) + _invalid_alias_cases = ( (b"Hello", {"JSON"}), ([1, None, b"Hello", 2.2], {"JSON"}), @@ -235,13 +488,18 @@ def test_alias_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: ([1, None, {"a": b"Hello"}, 2.2], {"JSON"}), ) + @pytest.mark.parametrize("val, ts", _invalid_alias_cases) -def test_invalid_alias_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: +def test_invalid_alias_cases( + val: typing.Any, ts: typing.List[typing.Any] +) -> None: for t in ts: try: with validation_aliases(**_validation_aliases): validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass @@ -250,18 +508,33 @@ class TD1(TypedDict, total=True): x: int y: float + class TD1a(TD1, total=False): z: typing.List[str] + class TD1b(TD1, total=True): z: typing.List[str] + + class TD2(TypedDict, total=False): x: str w: typing.List[str] + +class TD3(TypedDict, total=False): + x: Required[str] + w: typing.List[str] + + +class TD4(TypedDict): + x: str + w: NotRequired[typing.List[str]] + + _typed_dict_cases: typing.Dict[typing.Any, typing.List[typing.Any]] = {} _typed_dict_cases[TD1b] = [ - {"x": 1, "y": 1.5, "z": ["hello", "bye bye"]}, + {"x": 1, "y": 1.5, "z": ["hello", "bye bye"]}, ] _typed_dict_cases[TD1a] = [ *_typed_dict_cases[TD1b], @@ -277,6 +550,15 @@ class TD2(TypedDict, total=False): {"w": ["hello", "bye bye"]}, {}, ] +_typed_dict_cases[TD3] = [ + {"x": "hello", "w": ["hello", "bye bye"]}, + {"x": "hello"}, +] +_typed_dict_cases[TD4] = [ + {"x": "hello", "w": ["hello", "bye bye"]}, + {"x": "hello"}, +] + @pytest.mark.parametrize("t, vals", _typed_dict_cases.items()) def test_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: @@ -284,6 +566,7 @@ def test_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: for val in vals: validate(val, t) + _invalid_typed_dict_cases: typing.Dict[typing.Any, typing.List[typing.Any]] = {} _invalid_typed_dict_cases[TD1] = [ {"x": 1, "y": "invalid"}, @@ -306,14 +589,29 @@ def test_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: {"w": 0}, {"x": 0}, ] +_invalid_typed_dict_cases[TD3] = [ + *_invalid_typed_dict_cases[TD2], + {"w": ["hello", "bye bye"]}, + {}, +] +_invalid_typed_dict_cases[TD4] = [ + *_invalid_typed_dict_cases[TD2], + {"w": ["hello", "bye bye"]}, + {}, +] + @pytest.mark.parametrize("t, vals", _invalid_typed_dict_cases.items()) -def test_invalid_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: +def test_invalid_typed_dict_cases( + t: typing.Any, vals: typing.List[typing.Any] +) -> None: assert _is_typed_dict(t), t for val in vals: try: validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass @@ -322,15 +620,17 @@ def test_invalid_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) T = typing.TypeVar("T") U_co = typing.TypeVar("U_co", covariant=True) -class A: - ... + +class A: ... + + class B(typing.Generic[T]): - def __init__(self, t: T) -> None: - ... + def __init__(self, t: T) -> None: ... + class C(typing.Generic[S, T, U_co]): - def __init__(self, s: S, t: T, u: U_co) -> None: - ... + def __init__(self, s: S, t: T, u: U_co) -> None: ... + _user_class_cases = ( (A(), [A]), @@ -338,42 +638,47 @@ def __init__(self, s: S, t: T, u: U_co) -> None: (C("hello", 20, 30), [C, C[str, int, typing.Union[int, str]]]), ) + @pytest.mark.parametrize("val, ts", _user_class_cases) def test_user_class_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: for t in ts: validate(val, t) + def test_numpy_array() -> None: # pylint: disable = import-outside-toplevel import numpy as np import numpy.typing as npt + val = np.zeros(5, dtype=np.uint8) validate(val, npt.NDArray[np.uint8]) validate(val, npt.NDArray[typing.Union[np.uint8, np.float32]]) validate(val, npt.NDArray[typing.Union[typing.Any, np.float32]]) validate(val, npt.NDArray[typing.Any]) - validate(val, npt.NDArray[ - typing.Union[ - np.float32, + validate( + val, + npt.NDArray[ typing.Union[ - np.uint16, - typing.Union[np.int8,typing.Any] + np.float32, + typing.Union[np.uint16, typing.Union[np.int8, typing.Any]], ] - ] - ]) + ], + ) if sys.version_info[1] >= 9: validate(val, npt.NDArray[np.number[typing.Any]]) validate(val, npt.NDArray[np.integer[typing.Any]]) validate(val, npt.NDArray[np.unsignedinteger[typing.Any]]) validate(val, npt.NDArray[np.generic]) + def test_numpy_array_error() -> None: # pylint: disable = import-outside-toplevel import numpy as np import numpy.typing as npt + val = np.zeros(5, dtype=np.uint8) with pytest.raises(TypeError): - validate(val, npt.NDArray[typing.Union[np.uint16,np.float32]]) + validate(val, npt.NDArray[typing.Union[np.uint16, np.float32]]) with pytest.raises(TypeError): validate(val, npt.NDArray[np.str_]) @@ -389,19 +694,22 @@ def test_typevar() -> None: validate(None, IntT) with pytest.raises(TypeError): validate([0, 1], IntT) - IntStrSeqT = typing.TypeVar("IntStrSeqT", bound=typing.Sequence[typing.Union[int,str]]) + IntStrSeqT = typing.TypeVar( + "IntStrSeqT", bound=typing.Sequence[typing.Union[int, str]] + ) validate([0, "hello"], IntStrSeqT) validate("Hello", IntStrSeqT) with pytest.raises(TypeError): validate(0, IntStrSeqT) + def test_subtype() -> None: validate(int, type) validate(int, typing.Type) validate(int, typing.Type[int]) validate(int, typing.Type[typing.Any]) - validate(int, typing.Type[typing.Union[float,str,typing.Any]]) - validate(int, typing.Type[typing.Union[int,str]]) + validate(int, typing.Type[typing.Union[float, str, typing.Any]]) + validate(int, typing.Type[typing.Union[int, str]]) with pytest.raises(TypeError): validate(int, typing.Type[typing.Union[str, float]]) with pytest.raises(TypeError): @@ -409,6 +717,7 @@ def test_subtype() -> None: with pytest.raises(TypeError): validate(10, typing.Type[typing.Union[str, float]]) + @pytest.mark.parametrize("val, ts", _union_cases) def test_union_type_cases(val: typing.Any, ts: typing.List[typing.Any]) -> None: if UnionType is not None: diff --git a/test/test_01_can_validate.py b/test/test_01_can_validate.py index b6c315a..37b1dec 100644 --- a/test/test_01_can_validate.py +++ b/test/test_01_can_validate.py @@ -13,13 +13,26 @@ from typing_validation.inspector import _typing_equiv from typing_validation.validation import _pseudotypes_dict -from .test_00_validate import _test_cases, _union_cases, _literal_cases, _alias_cases,_typed_dict_cases, _user_class_cases, _validation_aliases +from .test_00_validate import ( + _test_cases, + _union_cases, + _literal_cases, + _alias_cases, + _typed_dict_cases, + _user_class_cases, + _validation_aliases, +) + def assert_recorded_type(t: typing.Any) -> None: _t = can_validate(t).recorded_type if t is type(None): assert None is _t - elif hasattr(t, "__origin__") and hasattr(t, "__args__") and t.__module__ == "typing": + elif ( + hasattr(t, "__origin__") + and hasattr(t, "__args__") + and t.__module__ == "typing" + ): if sys.version_info[1] <= 8 and t.__origin__ in _typing_equiv: t_origin = _typing_equiv[t.__origin__] else: @@ -33,101 +46,130 @@ def assert_recorded_type(t: typing.Any) -> None: t = _typing_equiv[t] assert t == _t -_valid_cases_ts = sorted({ - t for _, ts in _test_cases for t in ts -}|{ - _pseudotypes_dict[t] for _, ts in _test_cases for t in ts if t in _pseudotypes_dict -}|{typing.Any}, key=repr) + +_valid_cases_ts = sorted( + {t for _, ts in _test_cases for t in ts} + | { + _pseudotypes_dict[t] + for _, ts in _test_cases + for t in ts + if t in _pseudotypes_dict + } + | {typing.Any}, + key=repr, +) + @pytest.mark.parametrize("t", _valid_cases_ts) def test_valid_cases(t: typing.Any) -> None: # ts = ts+[_pseudotypes_dict[t] for t in ts if t in _pseudotypes_dict] assert can_validate(t), f"Should be able to validate {t}" - assert can_validate(typing.Optional[t]), f"Should be able to validate {typing.Optional[t]}" + assert can_validate( + typing.Optional[t] + ), f"Should be able to validate {typing.Optional[t]}" str(can_validate(t)) assert_recorded_type(t) -_other_cases_ts = sorted({ - t for _, ts in _union_cases+_literal_cases for t in ts -}, key=repr) + +_other_cases_ts = sorted( + {t for _, ts in _union_cases + _literal_cases for t in ts}, key=repr +) + @pytest.mark.parametrize("t", _other_cases_ts) def test_other_cases(t: typing.Any) -> None: assert can_validate(t), f"Should be able to validate {t}" - assert can_validate(typing.Optional[t]), f"Should be able to validate {typing.Optional[t]}" + assert can_validate( + typing.Optional[t] + ), f"Should be able to validate {typing.Optional[t]}" str(can_validate(t)) assert_recorded_type(t) -_alias_cases_ts = sorted({ - t for _, ts in _alias_cases for t in ts -}, key=repr) + +_alias_cases_ts = sorted({t for _, ts in _alias_cases for t in ts}, key=repr) + @pytest.mark.parametrize("t", _alias_cases_ts) def test_alias_cases(t: typing.Any) -> None: with validation_aliases(**_validation_aliases): assert can_validate(t), f"Should be able to validate {t}" - assert can_validate(typing.Optional[t]), f"Should be able to validate {typing.Optional[t]}" + assert can_validate( + typing.Optional[t] + ), f"Should be able to validate {typing.Optional[t]}" str(can_validate(t)) assert_recorded_type(t) + _typed_dict_cases_ts = sorted(_typed_dict_cases.keys(), key=repr) + @pytest.mark.parametrize("t", _typed_dict_cases_ts) def test_typed_dict_cases(t: typing.Any) -> None: with validation_aliases(**_validation_aliases): assert can_validate(t), f"Should be able to validate {t}" - assert can_validate(typing.Optional[t]), f"Should be able to validate {typing.Optional[t]}" + assert can_validate( + typing.Optional[t] + ), f"Should be able to validate {typing.Optional[t]}" str(can_validate(t)) assert_recorded_type(t) -_user_class_cases_ts = sorted({ - t for _, ts in _user_class_cases for t in typing.cast(typing.Any, ts) -}, key=repr) + +_user_class_cases_ts = sorted( + {t for _, ts in _user_class_cases for t in typing.cast(typing.Any, ts)}, + key=repr, +) + @pytest.mark.parametrize("t", _user_class_cases_ts) def test_user_class_cases(t: typing.Any) -> None: assert can_validate(t), f"Should be able to validate {t}" - assert can_validate(typing.Optional[t]), f"Should be able to validate {typing.Optional[t]}" + assert can_validate( + typing.Optional[t] + ), f"Should be able to validate {typing.Optional[t]}" str(can_validate(t)) assert_recorded_type(t) + def test_numpy_array() -> None: # pylint: disable = import-outside-toplevel import numpy as np import numpy.typing as npt + assert can_validate(npt.NDArray[np.uint8]) assert can_validate(npt.NDArray[typing.Union[np.uint8, np.float32]]) assert can_validate(npt.NDArray[typing.Union[typing.Any, np.float32]]) assert can_validate(npt.NDArray[typing.Any]) - assert can_validate(npt.NDArray[ - typing.Union[ - np.float32, + assert can_validate( + npt.NDArray[ typing.Union[ - np.uint16, - typing.Union[np.int8,typing.Any] + np.float32, + typing.Union[np.uint16, typing.Union[np.int8, typing.Any]], ] ] - ]) + ) + def test_typevar() -> None: T = typing.TypeVar("T") assert can_validate(T) IntT = typing.TypeVar("IntT", bound=int) assert can_validate(IntT) - IntStrSeqT = typing.TypeVar("IntStrSeqT", bound=typing.Sequence[typing.Union[int,str]]) + IntStrSeqT = typing.TypeVar( + "IntStrSeqT", bound=typing.Sequence[typing.Union[int, str]] + ) assert can_validate(IntStrSeqT) + def test_subtype() -> None: assert can_validate(type) assert can_validate(typing.Type) assert can_validate(typing.Type[int]) - assert can_validate(typing.Type[typing.Union[int,str]]) + assert can_validate(typing.Type[typing.Union[int, str]]) assert can_validate(typing.Type[typing.Any]) assert can_validate(typing.Type[typing.Union[typing.Any, str, int]]) -_union_cases_ts = sorted({ - t for _, ts in _union_cases for t in ts -}, key=repr) + +_union_cases_ts = sorted({t for _, ts in _union_cases for t in ts}, key=repr) @pytest.mark.parametrize("t", _union_cases_ts) diff --git a/test/test_03_validate_annotations.py b/test/test_03_validate_annotations.py index 4e02619..323418e 100644 --- a/test/test_03_validate_annotations.py +++ b/test/test_03_validate_annotations.py @@ -15,22 +15,28 @@ else: from typing_extensions import TypedDict + class TD1(TypedDict, total=True): x: int y: float + class TD1a(TD1, total=False): z: typing.List[str] + class TD1b(TD1, total=True): z: typing.List[str] + + class TD2(TypedDict, total=False): x: str w: typing.List[str] + _typed_dict_cases: typing.Dict[typing.Any, typing.List[typing.Any]] = {} _typed_dict_cases[TD1b] = [ - {"x": 1, "y": 1.5, "z": ["hello", "bye bye"]}, + {"x": 1, "y": 1.5, "z": ["hello", "bye bye"]}, ] _typed_dict_cases[TD1a] = [ *_typed_dict_cases[TD1b], @@ -47,12 +53,14 @@ class TD2(TypedDict, total=False): {}, ] + @pytest.mark.parametrize("t, vals", _typed_dict_cases.items()) def test_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: assert _is_typed_dict(t), t for val in vals: validate(val, t) + _invalid_typed_dict_cases: typing.Dict[typing.Any, typing.List[typing.Any]] = {} _invalid_typed_dict_cases[TD1] = [ {"x": 1, "y": "invalid"}, @@ -76,12 +84,17 @@ def test_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: {"x": 0}, ] + @pytest.mark.parametrize("t, vals", _invalid_typed_dict_cases.items()) -def test_invalid_typed_dict_cases(t: typing.Any, vals: typing.List[typing.Any]) -> None: +def test_invalid_typed_dict_cases( + t: typing.Any, vals: typing.List[typing.Any] +) -> None: assert _is_typed_dict(t), t for val in vals: try: validate(val, t) - assert False, f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" + assert ( + False + ), f"For type {repr(t)}, the following value shouldn't have been an instance: {repr(val)}" except TypeError: pass diff --git a/test/test_04_validated_iter.py b/test/test_04_validated_iter.py index 935735d..e0e83fc 100644 --- a/test/test_04_validated_iter.py +++ b/test/test_04_validated_iter.py @@ -7,24 +7,37 @@ from typing_validation.validation import validated_iter _iter_cases = [ - ((2*x for x in range(5)), typing.Iterable), - ((2*x for x in range(5)), typing.Iterator), - ((2*x for x in range(5)), typing.Iterable[int]), - ((2*x for x in range(5)), typing.Iterator[int]), + ((2 * x for x in range(5)), typing.Iterable), + ((2 * x for x in range(5)), typing.Iterator), + ((2 * x for x in range(5)), typing.Iterable[int]), + ((2 * x for x in range(5)), typing.Iterator[int]), ] + @pytest.mark.parametrize("it, t", _iter_cases) def test_iter_cases(it: typing.Any, t: typing.Any) -> None: _it = validated_iter(it, t) list(_it) + _invalid_iter_cases = [ - ((typing.cast(typing.Union[int, str], x)*2 for x in ["a", "b", 0, "c"]), - typing.Iterable[str]), - ((typing.cast(typing.Union[int, str], x)*2 for x in ["a", "b", 0, "c"]), - typing.Iterator[str]), + ( + ( + typing.cast(typing.Union[int, str], x) * 2 + for x in ["a", "b", 0, "c"] + ), + typing.Iterable[str], + ), + ( + ( + typing.cast(typing.Union[int, str], x) * 2 + for x in ["a", "b", 0, "c"] + ), + typing.Iterator[str], + ), ] + @pytest.mark.parametrize("it, t", _invalid_iter_cases) def test_invalid_typed_dict_cases(it: typing.Any, t: typing.Any) -> None: _it = validated_iter(it, t) diff --git a/typing_validation/__init__.py b/typing_validation/__init__.py index 97dcd87..5def9ff 100644 --- a/typing_validation/__init__.py +++ b/typing_validation/__init__.py @@ -2,7 +2,7 @@ Runtime validation using type hints. """ -__version__ = "1.2.10" +__version__ = "1.2.11" from .inspector import TypeInspector, UnsupportedType from .validation import ( diff --git a/typing_validation/inspector.py b/typing_validation/inspector.py index 444939d..7c1d6b4 100644 --- a/typing_validation/inspector.py +++ b/typing_validation/inspector.py @@ -314,11 +314,15 @@ def _record_collection(self, item_t: Any) -> None: def _record_mapping(self, key_t: Any, value_t: Any) -> None: self._append_constructor_args(("mapping", None)) - def _record_union(self, *member_ts: Any, use_UnionType: bool = False) -> None: + def _record_union( + self, *member_ts: Any, use_UnionType: bool = False + ) -> None: if use_UnionType: assert member_ts, "Cannot use UnionType with empty members." assert UnionType is not None, "Cannot use UnionType, version <= 3.9" - self._append_constructor_args(("union", (len(member_ts), use_UnionType))) + self._append_constructor_args( + ("union", (len(member_ts), use_UnionType)) + ) def _record_variadic_tuple(self, item_t: Any) -> None: self._append_constructor_args(("tuple", None))