1
1
from collections import Counter
2
2
from collections .abc import MutableSet as AbcMutableSet
3
+ from dataclasses import Field
3
4
from enum import Enum
4
5
from functools import lru_cache
5
- from typing import Any , Callable , Dict , Optional , Tuple , Type , TypeVar
6
+ from typing import Any , Callable , Dict , Optional , Tuple , Type , TypeVar , Union
6
7
8
+ from attr import Attribute
7
9
from attr import has as attrs_has
8
10
from attr import resolve_types
9
11
33
35
)
34
36
from .disambiguators import create_uniq_field_dis_func
35
37
from .dispatch import MultiStrategyDispatch
38
+ from .errors import StructureHandlerNotFoundError
36
39
from .gen import (
37
40
AttributeOverride ,
38
41
make_dict_structure_fn ,
@@ -71,14 +74,17 @@ class Converter(object):
71
74
"_dict_factory" ,
72
75
"_union_struct_registry" ,
73
76
"_structure_func" ,
77
+ "_prefer_attrib_converters" ,
74
78
)
75
79
76
80
def __init__ (
77
81
self ,
78
82
dict_factory : Callable [[], Any ] = dict ,
79
83
unstruct_strat : UnstructureStrategy = UnstructureStrategy .AS_DICT ,
84
+ prefer_attrib_converters : bool = False ,
80
85
) -> None :
81
86
unstruct_strat = UnstructureStrategy (unstruct_strat )
87
+ self ._prefer_attrib_converters = prefer_attrib_converters
82
88
83
89
# Create a per-instance cache.
84
90
if unstruct_strat is UnstructureStrategy .AS_DICT :
@@ -299,7 +305,7 @@ def _structure_default(self, obj, cl):
299
305
"Unsupported type: {0}. Register a structure hook for "
300
306
"it." .format (cl )
301
307
)
302
- raise ValueError (msg )
308
+ raise StructureHandlerNotFoundError (msg )
303
309
304
310
@staticmethod
305
311
def _structure_call (obj , cl ):
@@ -320,18 +326,34 @@ def structure_attrs_fromtuple(
320
326
conv_obj = [] # A list of converter parameters.
321
327
for a , value in zip (fields (cl ), obj ): # type: ignore
322
328
# We detect the type by the metadata.
323
- converted = self ._structure_attr_from_tuple ( a , a . name , value )
329
+ converted = self ._structure_attribute ( a , value )
324
330
conv_obj .append (converted )
325
331
326
332
return cl (* conv_obj ) # type: ignore
327
333
328
- def _structure_attr_from_tuple (self , a , _ , value ):
334
+ def _structure_attribute (
335
+ self , a : Union [Attribute , Field ], value : Any
336
+ ) -> Any :
329
337
"""Handle an individual attrs attribute."""
330
338
type_ = a .type
339
+ attrib_converter = getattr (a , "converter" , None )
340
+ if self ._prefer_attrib_converters and attrib_converter :
341
+ # A attrib converter is defined on this attribute, and prefer_attrib_converters is set
342
+ # to give these priority over registered structure hooks. So, pass through the raw
343
+ # value, which attrs will flow into the converter
344
+ return value
331
345
if type_ is None :
332
346
# No type metadata.
333
347
return value
334
- return self ._structure_func .dispatch (type_ )(value , type_ )
348
+
349
+ try :
350
+ return self ._structure_func .dispatch (type_ )(value , type_ )
351
+ except StructureHandlerNotFoundError :
352
+ if attrib_converter :
353
+ # Return the original value and fallback to using an attrib converter.
354
+ return value
355
+ else :
356
+ raise
335
357
336
358
def structure_attrs_fromdict (
337
359
self , obj : Mapping [str , Any ], cl : Type [T ]
@@ -340,10 +362,7 @@ def structure_attrs_fromdict(
340
362
# For public use.
341
363
342
364
conv_obj = {} # Start with a fresh dict, to ignore extra keys.
343
- dispatch = self ._structure_func .dispatch
344
365
for a in fields (cl ): # type: ignore
345
- # We detect the type by metadata.
346
- type_ = a .type
347
366
name = a .name
348
367
349
368
try :
@@ -354,9 +373,7 @@ def structure_attrs_fromdict(
354
373
if name [0 ] == "_" :
355
374
name = name [1 :]
356
375
357
- conv_obj [name ] = (
358
- dispatch (type_ )(val , type_ ) if type_ is not None else val
359
- )
376
+ conv_obj [name ] = self ._structure_attribute (a , val )
360
377
361
378
return cl (** conv_obj ) # type: ignore
362
379
@@ -476,7 +493,7 @@ def _get_dis_func(union):
476
493
)
477
494
478
495
if not all (has (get_origin (e ) or e ) for e in union_types ):
479
- raise ValueError (
496
+ raise StructureHandlerNotFoundError (
480
497
"Only unions of attr classes supported "
481
498
"currently. Register a loads hook manually."
482
499
)
@@ -501,9 +518,12 @@ def __init__(
501
518
forbid_extra_keys : bool = False ,
502
519
type_overrides : Mapping [Type , AttributeOverride ] = {},
503
520
unstruct_collection_overrides : Mapping [Type , Callable ] = {},
521
+ prefer_attrib_converters : bool = False ,
504
522
):
505
523
super ().__init__ (
506
- dict_factory = dict_factory , unstruct_strat = unstruct_strat
524
+ dict_factory = dict_factory ,
525
+ unstruct_strat = unstruct_strat ,
526
+ prefer_attrib_converters = prefer_attrib_converters ,
507
527
)
508
528
self .omit_if_default = omit_if_default
509
529
self .forbid_extra_keys = forbid_extra_keys
0 commit comments