Skip to content

Commit 5b3fb4d

Browse files
aarnphmTinche
andauthored
chore: update annotations (#302)
* chore: improve annotations Signed-off-by: Aaron Pham <[email protected]> * type: supports converters and gen type hint A few notes: - Use UnionType when cattrs decides to support 3.11 since in 3.11 @Final has a different implementation, which inherently affects UnionType support in _compat.py For cattrs to support it we might need to update typing_extensions version - Making generated function a generic type alias now. This should make it easiert to type hint any future works that involves the given make and gen function. revert: changes from upstream Signed-off-by: Aaron Pham <[email protected]> Signed-off-by: Aaron Pham <[email protected]> Co-authored-by: Tin Tvrtković <[email protected]>
1 parent c3faf86 commit 5b3fb4d

File tree

3 files changed

+122
-50
lines changed

3 files changed

+122
-50
lines changed

src/cattrs/converters.py

+73-35
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33
from dataclasses import Field
44
from enum import Enum
55
from functools import lru_cache
6-
from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar, Union
6+
from typing import (
7+
Any,
8+
Callable,
9+
Dict,
10+
Iterable,
11+
List,
12+
NoReturn,
13+
Optional,
14+
Tuple,
15+
Type,
16+
TypeVar,
17+
Union,
18+
)
719

820
from attr import Attribute
921
from attr import has as attrs_has
@@ -19,6 +31,7 @@
1931
OriginAbstractSet,
2032
OriginMutableSet,
2133
Sequence,
34+
Set,
2235
fields,
2336
get_newtype_base,
2437
get_origin,
@@ -43,6 +56,11 @@
4356
from .dispatch import MultiStrategyDispatch
4457
from .gen import (
4558
AttributeOverride,
59+
DictStructureFn,
60+
HeteroTupleUnstructureFn,
61+
IterableUnstructureFn,
62+
MappingStructureFn,
63+
MappingUnstructureFn,
4664
make_dict_structure_fn,
4765
make_dict_unstructure_fn,
4866
make_hetero_tuple_unstructure_fn,
@@ -63,26 +81,26 @@ class UnstructureStrategy(Enum):
6381
AS_TUPLE = "astuple"
6482

6583

66-
def _subclass(typ):
84+
def _subclass(typ: Type) -> Callable[[Type], bool]:
6785
"""a shortcut"""
6886
return lambda cls: issubclass(cls, typ)
6987

7088

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

7492

75-
def is_attrs_union_or_none(typ):
93+
def is_attrs_union_or_none(typ: Type) -> bool:
7694
return is_union_type(typ) and all(
7795
e is NoneType or has(get_origin(e) or e) for e in typ.__args__
7896
)
7997

8098

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

84102

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

88106

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

214232
def register_unstructure_hook_func(
215-
self, check_func: Callable[[Any], bool], func: Callable[[T], Any]
216-
):
233+
self, check_func: Callable[[Any], bool], func: Callable[[Any], Any]
234+
) -> None:
217235
"""Register a class-to-primitive converter function for a class, using
218236
a function to check if it's a match.
219237
"""
@@ -235,7 +253,9 @@ def register_unstructure_hook_factory(
235253
"""
236254
self._unstructure_func.register_func_list([(predicate, factory, True)])
237255

238-
def register_structure_hook(self, cl: Any, func: Callable[[Any, Type[T]], T]):
256+
def register_structure_hook(
257+
self, cl: Any, func: Callable[[Any, Type[T]], T]
258+
) -> None:
239259
"""Register a primitive-to-class converter function for a type.
240260
241261
The converter function should take two arguments:
@@ -255,7 +275,7 @@ def register_structure_hook(self, cl: Any, func: Callable[[Any, Type[T]], T]):
255275

256276
def register_structure_hook_func(
257277
self, check_func: Callable[[Type[T]], bool], func: Callable[[Any, Type[T]], T]
258-
):
278+
) -> None:
259279
"""Register a class-to-primitive converter function for a class, using
260280
a function to check if it's a match.
261281
"""
@@ -283,7 +303,7 @@ def structure(self, obj: Any, cl: Type[T]) -> T:
283303
return self._structure_func.dispatch(cl)(obj, cl)
284304

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

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

308-
def _unstructure_enum(self, obj):
328+
def _unstructure_enum(self, obj: Enum) -> Any:
309329
"""Convert an enum to its value."""
310330
return obj.value
311331

312332
@staticmethod
313-
def _unstructure_identity(obj):
333+
def _unstructure_identity(obj: T) -> T:
314334
"""Just pass it through."""
315335
return obj
316336

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

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

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

334-
def _unstructure_union(self, obj):
354+
# note: Use UnionType when 3.11 is released as
355+
# the behaviour of @final is changed. This would
356+
# affect how we can support UnionType in ._compat.py
357+
def _unstructure_union(self, obj: Any) -> Any:
335358
"""
336359
Unstructure an object as a union.
337360
@@ -342,19 +365,21 @@ def _unstructure_union(self, obj):
342365
# Python primitives to classes.
343366

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

350-
def _gen_structure_generic(self, cl):
373+
def _gen_structure_generic(self, cl: Type[T]) -> DictStructureFn[T]:
351374
"""Create and return a hook for structuring generics."""
352375
fn = make_dict_structure_fn(
353376
cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters
354377
)
355378
return fn
356379

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

376401
@staticmethod
377-
def _structure_call(obj, cl):
402+
def _structure_call(obj: Any, cl: Type[T]) -> Any:
378403
"""Just call ``cl`` with the given ``obj``.
379404
380405
This is just an optimization on the ``_structure_default`` case, when
@@ -411,7 +436,7 @@ def structure_attrs_fromtuple(self, obj: Tuple[Any, ...], cl: Type[T]) -> T:
411436
converted = self._structure_attribute(a, value)
412437
conv_obj.append(converted)
413438

414-
return cl(*conv_obj) # type: ignore
439+
return cl(*conv_obj)
415440

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

442467
conv_obj = {} # Start with a fresh dict, to ignore extra keys.
443-
for a in fields(cl): # type: ignore
468+
for a in fields(cl):
444469
name = a.name
445470

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

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

456-
return cl(**conv_obj) # type: ignore
481+
return cl(**conv_obj)
457482

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

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

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

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

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

856-
def gen_unstructure_iterable(self, cl: Any, unstructure_to=None):
885+
def gen_unstructure_iterable(
886+
self, cl: Any, unstructure_to: Any = None
887+
) -> IterableUnstructureFn:
857888
unstructure_to = self._unstruct_collection_overrides.get(
858889
get_origin(cl) or cl, unstructure_to or list
859890
)
860891
h = make_iterable_unstructure_fn(cl, self, unstructure_to=unstructure_to)
861892
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
862893
return h
863894

864-
def gen_unstructure_hetero_tuple(self, cl: Any, unstructure_to=None):
895+
def gen_unstructure_hetero_tuple(
896+
self, cl: Any, unstructure_to: Any = None
897+
) -> HeteroTupleUnstructureFn:
865898
unstructure_to = self._unstruct_collection_overrides.get(
866899
get_origin(cl) or cl, unstructure_to or list
867900
)
868901
h = make_hetero_tuple_unstructure_fn(cl, self, unstructure_to=unstructure_to)
869902
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
870903
return h
871904

872-
def gen_unstructure_mapping(self, cl: Any, unstructure_to=None, key_handler=None):
905+
def gen_unstructure_mapping(
906+
self,
907+
cl: Any,
908+
unstructure_to: Any = None,
909+
key_handler: Optional[Callable[[Any, Optional[Any]], Any]] = None,
910+
) -> MappingUnstructureFn:
873911
unstructure_to = self._unstruct_collection_overrides.get(
874912
get_origin(cl) or cl, unstructure_to or dict
875913
)
@@ -879,7 +917,7 @@ def gen_unstructure_mapping(self, cl: Any, unstructure_to=None, key_handler=None
879917
self._unstructure_func.register_cls_list([(cl, h)], direct=True)
880918
return h
881919

882-
def gen_structure_counter(self, cl: Any):
920+
def gen_structure_counter(self, cl: Any) -> MappingStructureFn[T]:
883921
h = make_mapping_structure_fn(
884922
cl,
885923
self,
@@ -890,7 +928,7 @@ def gen_structure_counter(self, cl: Any):
890928
self._structure_func.register_cls_list([(cl, h)], direct=True)
891929
return h
892930

893-
def gen_structure_mapping(self, cl: Any):
931+
def gen_structure_mapping(self, cl: Any) -> MappingStructureFn[T]:
894932
h = make_mapping_structure_fn(
895933
cl, self, detailed_validation=self.detailed_validation
896934
)

src/cattrs/disambiguators.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
from collections import OrderedDict
33
from functools import reduce
44
from operator import or_
5-
from typing import Callable, Dict, Mapping, Optional, Type
5+
from typing import Any, Callable, Dict, Mapping, Optional, Type
66

77
from attr import NOTHING, fields
88

99
from cattrs._compat import get_origin
1010

1111

12-
def create_uniq_field_dis_func(*classes: Type) -> Callable:
12+
def create_uniq_field_dis_func(
13+
*classes: Type[Any],
14+
) -> Callable[[Mapping[Any, Any]], Optional[Type[Any]]]:
1315
"""Given attr classes, generate a disambiguation function.
1416
1517
The function is based on unique fields."""
@@ -22,7 +24,7 @@ def create_uniq_field_dis_func(*classes: Type) -> Callable:
2224
raise ValueError("At least two classes have no attributes.")
2325
# TODO: Deal with a single class having no required attrs.
2426
# For each class, attempt to generate a single unique required field.
25-
uniq_attrs_dict = OrderedDict() # type: Dict[str, Type]
27+
uniq_attrs_dict: Dict[str, Type] = OrderedDict()
2628
cls_and_attrs.sort(key=lambda c_a: -len(c_a[1]))
2729

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

49-
def dis_func(data):
50-
# type: (Mapping) -> Optional[Type]
51+
def dis_func(data: Mapping[Any, Any]) -> Optional[Type]:
5152
if not isinstance(data, Mapping):
5253
raise ValueError("Only input mappings are supported.")
5354
for k, v in uniq_attrs_dict.items():

0 commit comments

Comments
 (0)