Skip to content

Commit

Permalink
tests work
Browse files Browse the repository at this point in the history
  • Loading branch information
PrettyWood committed Mar 13, 2021
1 parent 619ff26 commit 9d1b022
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 113 deletions.
32 changes: 17 additions & 15 deletions pydantic/fields.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections import defaultdict, deque
from collections.abc import Iterable as CollectionsIterable
from collections.abc import Callable as CollectionsCallable, Iterable as CollectionsIterable
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -31,7 +31,6 @@
from .types import Json, JsonWrapper
from .typing import (
NONE_TYPES,
Callable,
ForwardRef,
NoArgAnyCallable,
NoneType,
Expand Down Expand Up @@ -524,7 +523,7 @@ def _type_analysis(self) -> None: # noqa: C901 (ignore complexity)
self.type_ = get_args(self.type_)[0]
self._type_analysis()
return
if origin is Callable:
if origin is CollectionsCallable:
return
if origin is Union:
types_ = []
Expand Down Expand Up @@ -574,7 +573,7 @@ def _type_analysis(self) -> None: # noqa: C901 (ignore complexity)
{f'list_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
)

self.type_ = get_args(self.type_)[0]
(self.type_,) = get_args(self.type_) or (Any,)
self.shape = SHAPE_LIST
elif issubclass(origin, Set):
# Create self validators
Expand All @@ -584,33 +583,36 @@ def _type_analysis(self) -> None: # noqa: C901 (ignore complexity)
{f'set_{i}': Validator(validator, pre=True) for i, validator in enumerate(get_validators())}
)

self.type_ = get_args(self.type_)[0]
(self.type_,) = get_args(self.type_) or (Any,)
self.shape = SHAPE_SET
elif issubclass(origin, FrozenSet):
self.type_ = get_args(self.type_)[0]
(self.type_,) = get_args(self.type_) or (Any,)
self.shape = SHAPE_FROZENSET
elif issubclass(origin, Deque):
self.type_ = get_args(self.type_)[0]
(self.type_,) = get_args(self.type_) or (Any,)
self.shape = SHAPE_DEQUE
elif issubclass(origin, Sequence):
self.type_ = get_args(self.type_)[0]
(self.type_,) = get_args(self.type_) or (Any,)
self.shape = SHAPE_SEQUENCE
elif issubclass(origin, DefaultDict):
self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
self.type_ = get_args(self.type_)[1]
key_type, value_type = get_args(self.type_) or (Any, Any)
self.key_field = self._create_sub_type(key_type, 'key_' + self.name, for_keys=True)
self.type_ = value_type
self.shape = SHAPE_DEFAULTDICT
elif issubclass(origin, Dict):
self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
self.type_ = get_args(self.type_)[1]
key_type, value_type = get_args(self.type_) or (Any, Any)
self.key_field = self._create_sub_type(key_type, 'key_' + self.name, for_keys=True)
self.type_ = value_type
self.shape = SHAPE_DICT
elif issubclass(origin, Mapping):
self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
self.type_ = get_args(self.type_)[1]
key_type, value_type = get_args(self.type_) or (Any, Any)
self.key_field = self._create_sub_type(key_type, 'key_' + self.name, for_keys=True)
self.type_ = value_type
self.shape = SHAPE_MAPPING
# Equality check as almost everything inherits form Iterable, including str
# check for Iterable and CollectionsIterable, as it could receive one even when declared with the other
elif origin in {Iterable, CollectionsIterable}:
self.type_ = get_args(self.type_)[0]
(self.type_,) = get_args(self.type_) or (Any,)
self.shape = SHAPE_ITERABLE
self.sub_fields = [self._create_sub_type(self.type_, f'{self.name}_type')]
elif issubclass(origin, Type): # type: ignore
Expand Down
11 changes: 8 additions & 3 deletions pydantic/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,18 +963,23 @@ def go(type_: Any) -> Type[Any]:

if issubclass(origin, List) and (field_info.min_items is not None or field_info.max_items is not None):
used_constraints.update({'min_items', 'max_items'})
return conlist(go(args[0]), min_items=field_info.min_items, max_items=field_info.max_items)
(item_type,) = args or (Any,)
return conlist(go(item_type), min_items=field_info.min_items, max_items=field_info.max_items)

if issubclass(origin, Set) and (field_info.min_items is not None or field_info.max_items is not None):
used_constraints.update({'min_items', 'max_items'})
return conset(go(args[0]), min_items=field_info.min_items, max_items=field_info.max_items)
(item_type,) = args or (Any,)
return conset(go(item_type), min_items=field_info.min_items, max_items=field_info.max_items)

for t in (Tuple, List, Set, FrozenSet, Sequence):
if not args:
args = (Any, ...) if t is Tuple else (Any,)
if issubclass(origin, t): # type: ignore
return t[tuple(go(a) for a in args)] # type: ignore

if issubclass(origin, Dict):
return Dict[args[0], go(args[1])] # type: ignore
key_type, value_type = args or (Any, Any)
return Dict[key_type, go(value_type)] # type: ignore

attrs: Optional[Tuple[str, ...]] = None
constraint_func: Optional[Callable[..., type]] = None
Expand Down
119 changes: 24 additions & 95 deletions pydantic/typing.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import sys
from collections.abc import Callable as CollectionsCallable
from enum import Enum
from typing import ( # type: ignore
TYPE_CHECKING,
AbstractSet,
Any,
Callable,
ClassVar,
Dict,
Generator,
Expand Down Expand Up @@ -70,111 +72,39 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
return cast(Any, type_)._evaluate(globalns, localns, set())


if sys.version_info < (3, 7):
from typing import Callable as Callable

AnyCallable = Callable[..., Any]
NoArgAnyCallable = Callable[[], Any]
else:
from collections.abc import Callable as Callable
from typing import Callable as TypingCallable

AnyCallable = TypingCallable[..., Any]
NoArgAnyCallable = TypingCallable[[], Any]

AnyCallable = Callable[..., Any]
NoArgAnyCallable = Callable[[], Any]

# Annotated[...] is implemented by returning an instance of one of these classes, depending on
# python/typing_extensions version.
AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'}


if sys.version_info < (3, 8):

def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
if type(t).__name__ in AnnotatedTypeNames:
return cast(Type[Any], Annotated) # mypy complains about _SpecialForm in py3.6
return getattr(t, '__origin__', None)


else:
from typing import get_origin as _typing_get_origin

def get_origin(tp: Type[Any]) -> Type[Any]:
"""
We can't directly use `typing.get_origin` since we need a fallback to support
custom generic classes like `ConstrainedList`
It should be useless once https://github.com/cython/cython/issues/3537 is
solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged.
"""
if type(tp).__name__ in AnnotatedTypeNames:
return cast(Type[Any], Annotated) # mypy complains about _SpecialForm
return _typing_get_origin(tp) or getattr(tp, '__origin__', None)


if sys.version_info < (3, 7): # noqa: C901 (ignore complexity)

def get_args(t: Type[Any]) -> Tuple[Any, ...]:
"""Simplest get_args compatibility layer possible.
The Python 3.6 typing module does not have `_GenericAlias` so
this won't work for everything. In particular this will not
support the `generics` module (we don't support generic models in
python 3.6).
"""
if type(t).__name__ in AnnotatedTypeNames:
return t.__args__ + t.__metadata__
return getattr(t, '__args__', ())
def get_origin(tp: Type[Any]) -> Optional[Type[Any]]:
"""
We can't directly use `typingx.get_origin` since we need a fallback to support
custom generic classes like `ConstrainedList`
It should be useless once https://github.com/cython/cython/issues/3537 is
solved and https://github.com/samuelcolvin/pydantic/pull/1753 is merged.
"""
from typingx import get_origin

if type(tp).__name__ in AnnotatedTypeNames:
return cast(Type[Any], Annotated) # mypy complains about _SpecialForm in py3.6

elif sys.version_info < (3, 8): # noqa: C901
from typing import _GenericAlias
return get_origin(tp) or getattr(tp, '__origin__', None)

def get_args(t: Type[Any]) -> Tuple[Any, ...]:
"""Compatibility version of get_args for python 3.7.

Mostly compatible with the python 3.8 `typing` module version
and able to handle almost all use cases.
"""
if type(t).__name__ in AnnotatedTypeNames:
return t.__args__ + t.__metadata__
if isinstance(t, _GenericAlias):
res = t.__args__
if t.__origin__ is Callable and res and res[0] is not Ellipsis:
res = (list(res[:-1]), res[-1])
return res
return getattr(t, '__args__', ())
def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
"""
We can't directly use `typingx.get_args` (same issue as above)
"""
from typingx import get_args

if type(tp).__name__ in AnnotatedTypeNames:
return tp.__args__ + tp.__metadata__

else:
from typing import get_args as _typing_get_args

def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
"""
In python 3.9, `typing.Dict`, `typing.List`, ...
do have an empty `__args__` by default (instead of the generic ~T for example).
In order to still support `Dict` for example and consider it as `Dict[Any, Any]`,
we retrieve the `_nparams` value that tells us how many parameters it needs.
"""
if hasattr(tp, '_nparams'):
return (Any,) * tp._nparams
return ()

def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
"""Get type arguments with all substitutions performed.
For unions, basic simplifications used by Union constructor are performed.
Examples::
get_args(Dict[str, int]) == (str, int)
get_args(int) == ()
get_args(Union[int, Union[T, int], str][int]) == (int, str)
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
get_args(Callable[[], T][int]) == ([], int)
"""
if type(tp).__name__ in AnnotatedTypeNames:
return tp.__args__ + tp.__metadata__
# the fallback is needed for the same reasons as `get_origin` (see above)
return _typing_get_args(tp) or getattr(tp, '__args__', ()) or _generic_get_args(tp)
return get_args(tp) or getattr(tp, '__args__', None) or ()


if TYPE_CHECKING:
Expand All @@ -194,7 +124,6 @@ def get_args(tp: Type[Any]) -> Tuple[Any, ...]:

__all__ = (
'ForwardRef',
'Callable',
'AnyCallable',
'NoArgAnyCallable',
'NoneType',
Expand Down Expand Up @@ -288,7 +217,7 @@ def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Opti


def is_callable_type(type_: Type[Any]) -> bool:
return type_ is Callable or get_origin(type_) is Callable
return type_ in {Callable, CollectionsCallable} or get_origin(type_) in {Callable, CollectionsCallable}


if sys.version_info >= (3, 7):
Expand Down

0 comments on commit 9d1b022

Please sign in to comment.