Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update annotations #302

Merged
merged 3 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 73 additions & 35 deletions src/cattrs/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@
from dataclasses import Field
from enum import Enum
from functools import lru_cache
from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar, Union
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
NoReturn,
Optional,
Tuple,
Type,
TypeVar,
Union,
)

from attr import Attribute
from attr import has as attrs_has
Expand All @@ -19,6 +31,7 @@
OriginAbstractSet,
OriginMutableSet,
Sequence,
Set,
fields,
get_newtype_base,
get_origin,
Expand All @@ -43,6 +56,11 @@
from .dispatch import MultiStrategyDispatch
from .gen import (
AttributeOverride,
DictStructureFn,
HeteroTupleUnstructureFn,
IterableUnstructureFn,
MappingStructureFn,
MappingUnstructureFn,
make_dict_structure_fn,
make_dict_unstructure_fn,
make_hetero_tuple_unstructure_fn,
Expand All @@ -63,26 +81,26 @@ class UnstructureStrategy(Enum):
AS_TUPLE = "astuple"


def _subclass(typ):
def _subclass(typ: Type) -> Callable[[Type], bool]:
"""a shortcut"""
return lambda cls: issubclass(cls, typ)


def is_attrs_union(typ):
def is_attrs_union(typ: Type) -> bool:
return is_union_type(typ) and all(has(get_origin(e) or e) for e in typ.__args__)


def is_attrs_union_or_none(typ):
def is_attrs_union_or_none(typ: Type) -> bool:
return is_union_type(typ) and all(
e is NoneType or has(get_origin(e) or e) for e in typ.__args__
)


def is_optional(typ):
def is_optional(typ: Type) -> bool:
return is_union_type(typ) and NoneType in typ.__args__ and len(typ.__args__) == 2


def is_literal_containing_enums(typ):
def is_literal_containing_enums(typ: Type) -> bool:
return is_literal(typ) and any(isinstance(val, Enum) for val in typ.__args__)


Expand Down Expand Up @@ -212,8 +230,8 @@ def register_unstructure_hook(self, cls: Any, func: Callable[[Any], Any]) -> Non
self._unstructure_func.register_cls_list([(cls, func)])

def register_unstructure_hook_func(
self, check_func: Callable[[Any], bool], func: Callable[[T], Any]
):
self, check_func: Callable[[Any], bool], func: Callable[[Any], Any]
) -> None:
"""Register a class-to-primitive converter function for a class, using
a function to check if it's a match.
"""
Expand All @@ -235,7 +253,9 @@ def register_unstructure_hook_factory(
"""
self._unstructure_func.register_func_list([(predicate, factory, True)])

def register_structure_hook(self, cl: Any, func: Callable[[Any, Type[T]], T]):
def register_structure_hook(
self, cl: Any, func: Callable[[Any, Type[T]], T]
) -> None:
"""Register a primitive-to-class converter function for a type.

The converter function should take two arguments:
Expand All @@ -255,7 +275,7 @@ def register_structure_hook(self, cl: Any, func: Callable[[Any, Type[T]], T]):

def register_structure_hook_func(
self, check_func: Callable[[Type[T]], bool], func: Callable[[Any, Type[T]], T]
):
) -> None:
"""Register a class-to-primitive converter function for a class, using
a function to check if it's a match.
"""
Expand Down Expand Up @@ -283,7 +303,7 @@ def structure(self, obj: Any, cl: Type[T]) -> T:
return self._structure_func.dispatch(cl)(obj, cl)

# Classes to Python primitives.
def unstructure_attrs_asdict(self, obj) -> Dict[str, Any]:
def unstructure_attrs_asdict(self, obj: Any) -> Dict[str, Any]:
"""Our version of `attrs.asdict`, so we can call back to us."""
attrs = fields(obj.__class__)
dispatch = self._unstructure_func.dispatch
Expand All @@ -294,7 +314,7 @@ def unstructure_attrs_asdict(self, obj) -> Dict[str, Any]:
rv[name] = dispatch(a.type or v.__class__)(v)
return rv

def unstructure_attrs_astuple(self, obj) -> Tuple[Any, ...]:
def unstructure_attrs_astuple(self, obj: Any) -> Tuple[Any, ...]:
"""Our version of `attrs.astuple`, so we can call back to us."""
attrs = fields(obj.__class__)
dispatch = self._unstructure_func.dispatch
Expand All @@ -305,22 +325,22 @@ def unstructure_attrs_astuple(self, obj) -> Tuple[Any, ...]:
res.append(dispatch(a.type or v.__class__)(v))
return tuple(res)

def _unstructure_enum(self, obj):
def _unstructure_enum(self, obj: Enum) -> Any:
"""Convert an enum to its value."""
return obj.value

@staticmethod
def _unstructure_identity(obj):
def _unstructure_identity(obj: T) -> T:
"""Just pass it through."""
return obj

def _unstructure_seq(self, seq):
def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]:
"""Convert a sequence to primitive equivalents."""
# We can reuse the sequence class, so tuples stay tuples.
dispatch = self._unstructure_func.dispatch
return seq.__class__(dispatch(e.__class__)(e) for e in seq)

def _unstructure_mapping(self, mapping):
def _unstructure_mapping(self, mapping: Mapping[T, V]) -> Mapping[T, V]:
"""Convert a mapping of attr classes to primitive equivalents."""

# We can reuse the mapping class, so dicts stay dicts and OrderedDicts
Expand All @@ -331,7 +351,10 @@ def _unstructure_mapping(self, mapping):
for k, v in mapping.items()
)

def _unstructure_union(self, obj):
# note: Use UnionType when 3.11 is released as
# the behaviour of @final is changed. This would
# affect how we can support UnionType in ._compat.py
def _unstructure_union(self, obj: Any) -> Any:
"""
Unstructure an object as a union.

Expand All @@ -342,19 +365,21 @@ def _unstructure_union(self, obj):
# Python primitives to classes.

@staticmethod
def _structure_error(_, cl):
def _structure_error(_, cl: Type) -> NoReturn:
"""At the bottom of the condition stack, we explode if we can't handle it."""
msg = "Unsupported type: {0!r}. Register a structure hook for " "it.".format(cl)
raise StructureHandlerNotFoundError(msg, type_=cl)

def _gen_structure_generic(self, cl):
def _gen_structure_generic(self, cl: Type[T]) -> DictStructureFn[T]:
"""Create and return a hook for structuring generics."""
fn = make_dict_structure_fn(
cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters
)
return fn

def _gen_attrs_union_structure(self, cl):
def _gen_attrs_union_structure(
self, cl: Any
) -> Callable[[Any, Type[T]], Optional[Type[T]]]:
"""Generate a structuring function for a union of attrs classes (and maybe None)."""
dis_fn = self._get_dis_func(cl)
has_none = NoneType in cl.__args__
Expand All @@ -374,7 +399,7 @@ def structure_attrs_union(obj, _):
return structure_attrs_union

@staticmethod
def _structure_call(obj, cl):
def _structure_call(obj: Any, cl: Type[T]) -> Any:
"""Just call ``cl`` with the given ``obj``.

This is just an optimization on the ``_structure_default`` case, when
Expand Down Expand Up @@ -411,7 +436,7 @@ def structure_attrs_fromtuple(self, obj: Tuple[Any, ...], cl: Type[T]) -> T:
converted = self._structure_attribute(a, value)
conv_obj.append(converted)

return cl(*conv_obj) # type: ignore
return cl(*conv_obj)

def _structure_attribute(self, a: Union[Attribute, Field], value: Any) -> Any:
"""Handle an individual attrs attribute."""
Expand Down Expand Up @@ -440,7 +465,7 @@ def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: Type[T]) -> T:
# For public use.

conv_obj = {} # Start with a fresh dict, to ignore extra keys.
for a in fields(cl): # type: ignore
for a in fields(cl):
name = a.name

try:
Expand All @@ -453,9 +478,9 @@ def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: Type[T]) -> T:

conv_obj[name] = self._structure_attribute(a, val)

return cl(**conv_obj) # type: ignore
return cl(**conv_obj)

def _structure_list(self, obj, cl):
def _structure_list(self, obj: Iterable[T], cl: Any) -> List[T]:
"""Convert an iterable to a potentially generic list."""
if is_bare(cl) or cl.__args__[0] is Any:
res = [e for e in obj]
Expand All @@ -482,7 +507,9 @@ def _structure_list(self, obj, cl):
res = [handler(e, elem_type) for e in obj]
return res

def _structure_set(self, obj, cl, structure_to=set):
def _structure_set(
self, obj: Iterable[T], cl: Any, structure_to: type = set
) -> Set[T]:
"""Convert an iterable into a potentially generic set."""
if is_bare(cl) or cl.__args__[0] is Any:
return structure_to(obj)
Expand All @@ -507,11 +534,13 @@ def _structure_set(self, obj, cl, structure_to=set):
else:
return structure_to([handler(e, elem_type) for e in obj])

def _structure_frozenset(self, obj, cl):
def _structure_frozenset(
self, obj: Iterable[T], cl: Any
) -> FrozenSetSubscriptable[T]:
"""Convert an iterable into a potentially generic frozenset."""
return self._structure_set(obj, cl, structure_to=frozenset)
return self._structure_set(obj, cl, structure_to=frozenset) # type: ignore (incompatible type between frozenset and set)

def _structure_dict(self, obj, cl):
def _structure_dict(self, obj: Mapping[T, V], cl: Any) -> Dict[T, V]:
"""Convert a mapping into a potentially generic dict."""
if is_bare(cl) or cl.__args__ == (Any, Any):
return dict(obj)
Expand Down Expand Up @@ -543,7 +572,7 @@ def _structure_union(self, obj, union):
handler = self._union_struct_registry[union]
return handler(obj, union)

def _structure_tuple(self, obj, tup: Type[T]) -> T:
def _structure_tuple(self, obj: Any, tup: Type[T]) -> T:
"""Deal with structuring into a tuple."""
if tup in (Tuple, tuple):
tup_params = None
Expand Down Expand Up @@ -853,23 +882,32 @@ def gen_structure_attrs_fromdict(
# only direct dispatch so that subclasses get separately generated
return h

def gen_unstructure_iterable(self, cl: Any, unstructure_to=None):
def gen_unstructure_iterable(
self, cl: Any, unstructure_to: Any = None
) -> IterableUnstructureFn:
unstructure_to = self._unstruct_collection_overrides.get(
get_origin(cl) or cl, unstructure_to or list
)
h = make_iterable_unstructure_fn(cl, self, unstructure_to=unstructure_to)
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
return h

def gen_unstructure_hetero_tuple(self, cl: Any, unstructure_to=None):
def gen_unstructure_hetero_tuple(
self, cl: Any, unstructure_to: Any = None
) -> HeteroTupleUnstructureFn:
unstructure_to = self._unstruct_collection_overrides.get(
get_origin(cl) or cl, unstructure_to or list
)
h = make_hetero_tuple_unstructure_fn(cl, self, unstructure_to=unstructure_to)
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
return h

def gen_unstructure_mapping(self, cl: Any, unstructure_to=None, key_handler=None):
def gen_unstructure_mapping(
self,
cl: Any,
unstructure_to: Any = None,
key_handler: Optional[Callable[[Any, Optional[Any]], Any]] = None,
) -> MappingUnstructureFn:
unstructure_to = self._unstruct_collection_overrides.get(
get_origin(cl) or cl, unstructure_to or dict
)
Expand All @@ -879,7 +917,7 @@ def gen_unstructure_mapping(self, cl: Any, unstructure_to=None, key_handler=None
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
return h

def gen_structure_counter(self, cl: Any):
def gen_structure_counter(self, cl: Any) -> MappingStructureFn[T]:
h = make_mapping_structure_fn(
cl,
self,
Expand All @@ -890,7 +928,7 @@ def gen_structure_counter(self, cl: Any):
self._structure_func.register_cls_list([(cl, h)], direct=True)
return h

def gen_structure_mapping(self, cl: Any):
def gen_structure_mapping(self, cl: Any) -> MappingStructureFn[T]:
h = make_mapping_structure_fn(
cl, self, detailed_validation=self.detailed_validation
)
Expand Down
11 changes: 6 additions & 5 deletions src/cattrs/disambiguators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
from collections import OrderedDict
from functools import reduce
from operator import or_
from typing import Callable, Dict, Mapping, Optional, Type
from typing import Any, Callable, Dict, Mapping, Optional, Type

from attr import NOTHING, fields

from cattrs._compat import get_origin


def create_uniq_field_dis_func(*classes: Type) -> Callable:
def create_uniq_field_dis_func(
*classes: Type[Any],
) -> Callable[[Mapping[Any, Any]], Optional[Type[Any]]]:
"""Given attr classes, generate a disambiguation function.

The function is based on unique fields."""
Expand All @@ -22,7 +24,7 @@ def create_uniq_field_dis_func(*classes: Type) -> Callable:
raise ValueError("At least two classes have no attributes.")
# TODO: Deal with a single class having no required attrs.
# For each class, attempt to generate a single unique required field.
uniq_attrs_dict = OrderedDict() # type: Dict[str, Type]
uniq_attrs_dict: Dict[str, Type] = OrderedDict()
cls_and_attrs.sort(key=lambda c_a: -len(c_a[1]))

fallback = None # If none match, try this.
Expand All @@ -46,8 +48,7 @@ def create_uniq_field_dis_func(*classes: Type) -> Callable:
else:
fallback = cl

def dis_func(data):
# type: (Mapping) -> Optional[Type]
def dis_func(data: Mapping[Any, Any]) -> Optional[Type]:
if not isinstance(data, Mapping):
raise ValueError("Only input mappings are supported.")
for k, v in uniq_attrs_dict.items():
Expand Down
Loading