5
5
from dataclasses import Field
6
6
from enum import Enum
7
7
from functools import partial
8
+ from inspect import Signature
8
9
from pathlib import Path
9
10
from typing import Any , Callable , Iterable , Optional , Tuple , TypeVar , overload
10
11
55
56
MultiStrategyDispatch ,
56
57
StructuredValue ,
57
58
StructureHook ,
59
+ TargetType ,
58
60
UnstructuredValue ,
59
61
UnstructureHook ,
60
62
)
85
87
86
88
T = TypeVar ("T" )
87
89
V = TypeVar ("V" )
90
+
88
91
UnstructureHookFactory = TypeVar (
89
92
"UnstructureHookFactory" , bound = HookFactory [UnstructureHook ]
90
93
)
94
+
95
+ # The Extended factory also takes a converter.
96
+ ExtendedUnstructureHookFactory = TypeVar (
97
+ "ExtendedUnstructureHookFactory" ,
98
+ bound = Callable [[TargetType , "BaseConverter" ], UnstructureHook ],
99
+ )
100
+
91
101
StructureHookFactory = TypeVar ("StructureHookFactory" , bound = HookFactory [StructureHook ])
92
102
103
+ # The Extended factory also takes a converter.
104
+ ExtendedStructureHookFactory = TypeVar (
105
+ "ExtendedStructureHookFactory" ,
106
+ bound = Callable [[TargetType , "BaseConverter" ], StructureHook ],
107
+ )
108
+
93
109
94
110
class UnstructureStrategy (Enum ):
95
111
"""`attrs` classes unstructuring strategies."""
@@ -151,7 +167,9 @@ def __init__(
151
167
self ._unstructure_attrs = self .unstructure_attrs_astuple
152
168
self ._structure_attrs = self .structure_attrs_fromtuple
153
169
154
- self ._unstructure_func = MultiStrategyDispatch (unstructure_fallback_factory )
170
+ self ._unstructure_func = MultiStrategyDispatch (
171
+ unstructure_fallback_factory , self
172
+ )
155
173
self ._unstructure_func .register_cls_list (
156
174
[(bytes , identity ), (str , identity ), (Path , str )]
157
175
)
@@ -163,12 +181,12 @@ def __init__(
163
181
),
164
182
(
165
183
lambda t : get_final_base (t ) is not None ,
166
- lambda t : self ._unstructure_func . dispatch (get_final_base (t )),
184
+ lambda t : self .get_unstructure_hook (get_final_base (t )),
167
185
True ,
168
186
),
169
187
(
170
188
is_type_alias ,
171
- lambda t : self ._unstructure_func . dispatch (get_type_alias_base (t )),
189
+ lambda t : self .get_unstructure_hook (get_type_alias_base (t )),
172
190
True ,
173
191
),
174
192
(is_mapping , self ._unstructure_mapping ),
@@ -185,7 +203,7 @@ def __init__(
185
203
# Per-instance register of to-attrs converters.
186
204
# Singledispatch dispatches based on the first argument, so we
187
205
# store the function and switch the arguments in self.loads.
188
- self ._structure_func = MultiStrategyDispatch (structure_fallback_factory )
206
+ self ._structure_func = MultiStrategyDispatch (structure_fallback_factory , self )
189
207
self ._structure_func .register_func_list (
190
208
[
191
209
(
@@ -308,6 +326,12 @@ def register_unstructure_hook_factory(
308
326
) -> Callable [[UnstructureHookFactory ], UnstructureHookFactory ]:
309
327
...
310
328
329
+ @overload
330
+ def register_unstructure_hook_factory (
331
+ self , predicate : Callable [[Any ], bool ]
332
+ ) -> Callable [[ExtendedUnstructureHookFactory ], ExtendedUnstructureHookFactory ]:
333
+ ...
334
+
311
335
@overload
312
336
def register_unstructure_hook_factory (
313
337
self , predicate : Callable [[Any ], bool ], factory : UnstructureHookFactory
@@ -325,7 +349,10 @@ def register_unstructure_hook_factory(
325
349
"""
326
350
Register a hook factory for a given predicate.
327
351
328
- May also be used as a decorator.
352
+ May also be used as a decorator. When used as a decorator, the hook
353
+ factory may expose an additional required parameter. In this case,
354
+ the current converter will be provided to the hook factory as that
355
+ parameter.
329
356
330
357
:param predicate: A function that, given a type, returns whether the factory
331
358
can produce a hook for that type.
@@ -336,7 +363,23 @@ def register_unstructure_hook_factory(
336
363
This method may now be used as a decorator.
337
364
"""
338
365
if factory is None :
339
- return partial (self .register_unstructure_hook_factory , predicate )
366
+
367
+ def decorator (factory ):
368
+ # Is this an extended factory (takes a converter too)?
369
+ sig = signature (factory )
370
+ if (
371
+ len (sig .parameters ) >= 2
372
+ and (list (sig .parameters .values ())[1 ]).default is Signature .empty
373
+ ):
374
+ self ._unstructure_func .register_func_list (
375
+ [(predicate , factory , "extended" )]
376
+ )
377
+ else :
378
+ self ._unstructure_func .register_func_list (
379
+ [(predicate , factory , True )]
380
+ )
381
+
382
+ return decorator
340
383
self ._unstructure_func .register_func_list ([(predicate , factory , True )])
341
384
return factory
342
385
@@ -420,6 +463,12 @@ def register_structure_hook_factory(
420
463
) -> Callable [[StructureHookFactory , StructureHookFactory ]]:
421
464
...
422
465
466
+ @overload
467
+ def register_structure_hook_factory (
468
+ self , predicate : Callable [[Any , bool ]]
469
+ ) -> Callable [[ExtendedStructureHookFactory , ExtendedStructureHookFactory ]]:
470
+ ...
471
+
423
472
@overload
424
473
def register_structure_hook_factory (
425
474
self , predicate : Callable [[Any ], bool ], factory : StructureHookFactory
@@ -434,7 +483,10 @@ def register_structure_hook_factory(
434
483
"""
435
484
Register a hook factory for a given predicate.
436
485
437
- May also be used as a decorator.
486
+ May also be used as a decorator. When used as a decorator, the hook
487
+ factory may expose an additional required parameter. In this case,
488
+ the current converter will be provided to the hook factory as that
489
+ parameter.
438
490
439
491
:param predicate: A function that, given a type, returns whether the factory
440
492
can produce a hook for that type.
@@ -445,7 +497,23 @@ def register_structure_hook_factory(
445
497
This method may now be used as a decorator.
446
498
"""
447
499
if factory is None :
448
- return partial (self .register_structure_hook_factory , predicate )
500
+ # Decorator use.
501
+ def decorator (factory ):
502
+ # Is this an extended factory (takes a converter too)?
503
+ sig = signature (factory )
504
+ if (
505
+ len (sig .parameters ) >= 2
506
+ and (list (sig .parameters .values ())[1 ]).default is Signature .empty
507
+ ):
508
+ self ._structure_func .register_func_list (
509
+ [(predicate , factory , "extended" )]
510
+ )
511
+ else :
512
+ self ._structure_func .register_func_list (
513
+ [(predicate , factory , True )]
514
+ )
515
+
516
+ return decorator
449
517
self ._structure_func .register_func_list ([(predicate , factory , True )])
450
518
return factory
451
519
@@ -684,7 +752,7 @@ def _structure_list(self, obj: Iterable[T], cl: Any) -> list[T]:
684
752
def _structure_deque (self , obj : Iterable [T ], cl : Any ) -> deque [T ]:
685
753
"""Convert an iterable to a potentially generic deque."""
686
754
if is_bare (cl ) or cl .__args__ [0 ] in ANIES :
687
- res = deque (e for e in obj )
755
+ res = deque (obj )
688
756
else :
689
757
elem_type = cl .__args__ [0 ]
690
758
handler = self ._structure_func .dispatch (elem_type )
@@ -1048,7 +1116,7 @@ def __init__(
1048
1116
)
1049
1117
self .register_unstructure_hook_factory (
1050
1118
lambda t : get_newtype_base (t ) is not None ,
1051
- lambda t : self ._unstructure_func . dispatch (get_newtype_base (t )),
1119
+ lambda t : self .get_unstructure_hook (get_newtype_base (t )),
1052
1120
)
1053
1121
1054
1122
self .register_structure_hook_factory (is_annotated , self .gen_structure_annotated )
@@ -1070,7 +1138,7 @@ def get_structure_newtype(self, type: type[T]) -> Callable[[Any, Any], T]:
1070
1138
1071
1139
def gen_unstructure_annotated (self , type ):
1072
1140
origin = type .__origin__
1073
- return self ._unstructure_func . dispatch (origin )
1141
+ return self .get_unstructure_hook (origin )
1074
1142
1075
1143
def gen_structure_annotated (self , type ) -> Callable :
1076
1144
"""A hook factory for annotated types."""
@@ -1111,7 +1179,7 @@ def gen_unstructure_optional(self, cl: type[T]) -> Callable[[T], Any]:
1111
1179
if isinstance (other , TypeVar ):
1112
1180
handler = self .unstructure
1113
1181
else :
1114
- handler = self ._unstructure_func . dispatch (other )
1182
+ handler = self .get_unstructure_hook (other )
1115
1183
1116
1184
def unstructure_optional (val , _handler = handler ):
1117
1185
return None if val is None else _handler (val )
0 commit comments