1
+ import linecache
1
2
import re
3
+ import uuid
2
4
from dataclasses import is_dataclass
3
5
from typing import Any , Optional , Type , TypeVar
4
6
@@ -21,7 +23,13 @@ def override(omit_if_default=None, rename=None):
21
23
_neutral = AttributeOverride ()
22
24
23
25
24
- def make_dict_unstructure_fn (cl , converter , omit_if_default = False , ** kwargs ):
26
+ def make_dict_unstructure_fn (
27
+ cl ,
28
+ converter ,
29
+ omit_if_default : bool = False ,
30
+ _cattrs_use_linecache : bool = True ,
31
+ ** kwargs ,
32
+ ):
25
33
"""Generate a specialized dict unstructuring function for an attrs class."""
26
34
cl_name = cl .__name__
27
35
fn_name = "unstructure_" + cl_name
@@ -31,7 +39,7 @@ def make_dict_unstructure_fn(cl, converter, omit_if_default=False, **kwargs):
31
39
32
40
attrs = adapted_fields (cl ) # type: ignore
33
41
34
- lines .append (f"def { fn_name } (i ):" )
42
+ lines .append (f"def { fn_name } (instance ):" )
35
43
lines .append (" res = {" )
36
44
for a in attrs :
37
45
attr_name = a .name
@@ -50,11 +58,11 @@ def make_dict_unstructure_fn(cl, converter, omit_if_default=False, **kwargs):
50
58
is_identity = handler == converter ._unstructure_identity
51
59
52
60
if not is_identity :
53
- unstruct_handler_name = f"__cattr_unstruct_handler_ { attr_name } "
61
+ unstruct_handler_name = f"unstructure_ { attr_name } "
54
62
globs [unstruct_handler_name ] = handler
55
- invoke = f"{ unstruct_handler_name } (i .{ attr_name } )"
63
+ invoke = f"{ unstruct_handler_name } (instance .{ attr_name } )"
56
64
else :
57
- invoke = f"i .{ attr_name } "
65
+ invoke = f"instance .{ attr_name } "
58
66
59
67
if d is not attr .NOTHING and (
60
68
(omit_if_default and override .omit_if_default is not False )
@@ -66,14 +74,18 @@ def make_dict_unstructure_fn(cl, converter, omit_if_default=False, **kwargs):
66
74
globs [def_name ] = d .factory
67
75
if d .takes_self :
68
76
post_lines .append (
69
- f" if i .{ attr_name } != { def_name } (i ):"
77
+ f" if instance .{ attr_name } != { def_name } (instance ):"
70
78
)
71
79
else :
72
- post_lines .append (f" if i.{ attr_name } != { def_name } ():" )
80
+ post_lines .append (
81
+ f" if instance.{ attr_name } != { def_name } ():"
82
+ )
73
83
post_lines .append (f" res['{ kn } '] = { invoke } " )
74
84
else :
75
85
globs [def_name ] = d
76
- post_lines .append (f" if i.{ attr_name } != { def_name } :" )
86
+ post_lines .append (
87
+ f" if instance.{ attr_name } != { def_name } :"
88
+ )
77
89
post_lines .append (f" res['{ kn } '] = { invoke } " )
78
90
79
91
else :
@@ -82,10 +94,17 @@ def make_dict_unstructure_fn(cl, converter, omit_if_default=False, **kwargs):
82
94
lines .append (" }" )
83
95
84
96
total_lines = lines + post_lines + [" return res" ]
97
+ script = "\n " .join (total_lines )
85
98
86
- eval (compile ("\n " .join (total_lines ), "" , "exec" ), globs )
99
+ fname = _generate_unique_filename (
100
+ cl , "unstructure" , reserve = _cattrs_use_linecache
101
+ )
102
+
103
+ eval (compile (script , fname , "exec" ), globs )
87
104
88
105
fn = globs [fn_name ]
106
+ if _cattrs_use_linecache :
107
+ linecache .cache [fname ] = len (script ), None , total_lines , fname
89
108
90
109
return fn
91
110
@@ -110,7 +129,11 @@ def generate_mapping(cl: Type, old_mapping):
110
129
111
130
112
131
def make_dict_structure_fn (
113
- cl : Type , converter , _cattrs_forbid_extra_keys : bool = False , ** kwargs
132
+ cl : Type ,
133
+ converter ,
134
+ _cattrs_forbid_extra_keys : bool = False ,
135
+ _cattrs_use_linecache : bool = True ,
136
+ ** kwargs ,
114
137
):
115
138
"""Generate a specialized dict structuring function for an attrs class."""
116
139
@@ -167,20 +190,20 @@ def make_dict_structure_fn(
167
190
else :
168
191
handler = converter .structure
169
192
170
- struct_handler_name = f"__cattr_struct_handler_ { an } "
193
+ struct_handler_name = f"structure_ { an } "
171
194
globs [struct_handler_name ] = handler
172
195
173
196
ian = an if (is_dc or an [0 ] != "_" ) else an [1 :]
174
197
kn = an if override .rename is None else override .rename
175
- globs [f"__c_t_ { an } " ] = type
198
+ globs [f"type_ { an } " ] = type
176
199
if a .default is NOTHING :
177
200
lines .append (
178
- f" '{ ian } ': { struct_handler_name } (o['{ kn } '], __c_t_ { an } ),"
201
+ f" '{ ian } ': { struct_handler_name } (o['{ kn } '], type_ { an } ),"
179
202
)
180
203
else :
181
204
post_lines .append (f" if '{ kn } ' in o:" )
182
205
post_lines .append (
183
- f" res['{ ian } '] = { struct_handler_name } (o['{ kn } '], __c_t_ { an } )"
206
+ f" res['{ ian } '] = { struct_handler_name } (o['{ kn } '], type_ { an } )"
184
207
)
185
208
lines .append (" }" )
186
209
if _cattrs_forbid_extra_keys :
@@ -196,7 +219,20 @@ def make_dict_structure_fn(
196
219
197
220
total_lines = lines + post_lines + [" return __cl(**res)" ]
198
221
199
- eval (compile ("\n " .join (total_lines ), "" , "exec" ), globs )
222
+ fname = _generate_unique_filename (
223
+ cl , "structure" , reserve = _cattrs_use_linecache
224
+ )
225
+ script = "\n " .join (total_lines )
226
+ eval (
227
+ compile (
228
+ script ,
229
+ fname ,
230
+ "exec" ,
231
+ ),
232
+ globs ,
233
+ )
234
+ if _cattrs_use_linecache :
235
+ linecache .cache [fname ] = len (script ), None , total_lines , fname
200
236
201
237
return globs [fn_name ]
202
238
@@ -396,3 +432,35 @@ def make_mapping_structure_fn(
396
432
fn = globs [fn_name ]
397
433
398
434
return fn
435
+
436
+
437
+ def _generate_unique_filename (cls , func_name , reserve = True ):
438
+ """
439
+ Create a "filename" suitable for a function being generated.
440
+ """
441
+ unique_id = uuid .uuid4 ()
442
+ extra = ""
443
+ count = 1
444
+
445
+ while True :
446
+ unique_filename = "<cattrs generated {0} {1}.{2}{3}>" .format (
447
+ func_name ,
448
+ cls .__module__ ,
449
+ getattr (cls , "__qualname__" , cls .__name__ ),
450
+ extra ,
451
+ )
452
+ if not reserve :
453
+ return unique_filename
454
+ # To handle concurrency we essentially "reserve" our spot in
455
+ # the linecache with a dummy line. The caller can then
456
+ # set this value correctly.
457
+ cache_line = (1 , None , (str (unique_id ),), unique_filename )
458
+ if (
459
+ linecache .cache .setdefault (unique_filename , cache_line )
460
+ == cache_line
461
+ ):
462
+ return unique_filename
463
+
464
+ # Looks like this spot is taken. Try again.
465
+ count += 1
466
+ extra = "-{0}" .format (count )
0 commit comments