python-datamodel 0.10.1__cp313-cp313-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- datamodel/__init__.py +13 -0
- datamodel/abstract.py +383 -0
- datamodel/adaptive/__init__.py +0 -0
- datamodel/adaptive/models.py +598 -0
- datamodel/aliases/__init__.py +26 -0
- datamodel/base.py +180 -0
- datamodel/converters.c +43471 -0
- datamodel/converters.cp313-win32.pyd +0 -0
- datamodel/converters.html +17387 -0
- datamodel/converters.pyx +1489 -0
- datamodel/exceptions.c +13455 -0
- datamodel/exceptions.cp313-win32.pyd +0 -0
- datamodel/exceptions.html +1261 -0
- datamodel/exceptions.pxd +13 -0
- datamodel/exceptions.pyx +50 -0
- datamodel/fields.cp313-win32.pyd +0 -0
- datamodel/fields.cpp +17401 -0
- datamodel/fields.html +3912 -0
- datamodel/fields.pyx +309 -0
- datamodel/functions.cp313-win32.pyd +0 -0
- datamodel/functions.cpp +9068 -0
- datamodel/functions.html +1766 -0
- datamodel/functions.pxd +9 -0
- datamodel/functions.pyx +82 -0
- datamodel/jsonld/__init__.py +45 -0
- datamodel/jsonld/models.py +500 -0
- datamodel/libs/__init__.py +1 -0
- datamodel/libs/mapping.c +15067 -0
- datamodel/libs/mapping.cp313-win32.pyd +0 -0
- datamodel/libs/mapping.html +2618 -0
- datamodel/libs/mapping.pxd +11 -0
- datamodel/libs/mapping.pyx +135 -0
- datamodel/libs/mutables.py +127 -0
- datamodel/models.py +814 -0
- datamodel/parsers/__init__.py +0 -0
- datamodel/parsers/encoders.py +15 -0
- datamodel/parsers/json.cp313-win32.pyd +0 -0
- datamodel/parsers/json.cpp +17004 -0
- datamodel/parsers/json.html +3365 -0
- datamodel/parsers/json.pyx +250 -0
- datamodel/profiler.py +21 -0
- datamodel/py.typed +0 -0
- datamodel/rs_core/Cargo.toml +17 -0
- datamodel/rs_core/src/lib.rs +294 -0
- datamodel/rs_parsers/Cargo.toml +22 -0
- datamodel/rs_parsers/src/lib.rs +571 -0
- datamodel/rs_parsers.cp313-win32.pyd +0 -0
- datamodel/rs_validators/Cargo.toml +17 -0
- datamodel/rs_validators/src/lib.rs +0 -0
- datamodel/typedefs/__init__.py +9 -0
- datamodel/typedefs/singleton.c +9169 -0
- datamodel/typedefs/singleton.cp313-win32.pyd +0 -0
- datamodel/typedefs/singleton.html +629 -0
- datamodel/typedefs/singleton.pxd +9 -0
- datamodel/typedefs/singleton.pyx +24 -0
- datamodel/typedefs/types.c +11716 -0
- datamodel/typedefs/types.cp313-win32.pyd +0 -0
- datamodel/typedefs/types.html +732 -0
- datamodel/typedefs/types.pxd +11 -0
- datamodel/typedefs/types.pyx +39 -0
- datamodel/types.c +7165 -0
- datamodel/types.cp313-win32.pyd +0 -0
- datamodel/types.html +716 -0
- datamodel/types.pyx +100 -0
- datamodel/validation.cp313-win32.pyd +0 -0
- datamodel/validation.cpp +17085 -0
- datamodel/validation.html +4769 -0
- datamodel/validation.pyx +315 -0
- datamodel/version.py +13 -0
- examples/nn/examples.py +311 -0
- examples/nn/stores.py +151 -0
- examples/tests/sp_types.py +294 -0
- examples/tests/speed_dates.py +26 -0
- python_datamodel-0.10.1.dist-info/LICENSE +29 -0
- python_datamodel-0.10.1.dist-info/METADATA +320 -0
- python_datamodel-0.10.1.dist-info/RECORD +78 -0
- python_datamodel-0.10.1.dist-info/WHEEL +5 -0
- python_datamodel-0.10.1.dist-info/top_level.txt +7 -0
datamodel/converters.pyx
ADDED
|
@@ -0,0 +1,1489 @@
|
|
|
1
|
+
# cython: language_level=3, embedsignature=True, initializedcheck=False
|
|
2
|
+
# Copyright (C) 2018-present Jesus Lara
|
|
3
|
+
#
|
|
4
|
+
import re
|
|
5
|
+
from typing import get_args, get_origin, Union, Optional, List, NewType
|
|
6
|
+
from collections.abc import Sequence, Mapping, Callable, Awaitable
|
|
7
|
+
import types
|
|
8
|
+
from dataclasses import _MISSING_TYPE, _FIELDS, fields
|
|
9
|
+
import ciso8601
|
|
10
|
+
import orjson
|
|
11
|
+
from decimal import Decimal, InvalidOperation
|
|
12
|
+
from cpython cimport datetime
|
|
13
|
+
from cpython.object cimport (
|
|
14
|
+
PyObject_IsInstance,
|
|
15
|
+
PyObject_IsSubclass,
|
|
16
|
+
PyObject_HasAttr,
|
|
17
|
+
PyObject_GetAttr,
|
|
18
|
+
PyObject_TypeCheck,
|
|
19
|
+
PyCallable_Check
|
|
20
|
+
)
|
|
21
|
+
cimport cython
|
|
22
|
+
from uuid import UUID
|
|
23
|
+
import asyncpg.pgproto.pgproto as pgproto
|
|
24
|
+
from cpython.ref cimport PyObject
|
|
25
|
+
from .functions import is_empty, is_iterable, is_primitive
|
|
26
|
+
from .validation import _validation
|
|
27
|
+
from .fields import Field
|
|
28
|
+
# New converter:
|
|
29
|
+
import rs_parsers as rc
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
cdef bint is_dc(object obj):
|
|
33
|
+
"""Returns True if obj is a dataclass or an instance of a
|
|
34
|
+
dataclass."""
|
|
35
|
+
cls = obj if isinstance(obj, type) and not isinstance(obj, types.GenericAlias) else type(obj)
|
|
36
|
+
return PyObject_HasAttr(cls, '__dataclass_fields__')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
cpdef str to_string(object obj):
|
|
40
|
+
"""
|
|
41
|
+
Returns a string version of an object.
|
|
42
|
+
"""
|
|
43
|
+
if obj is None:
|
|
44
|
+
return None
|
|
45
|
+
if isinstance(obj, str):
|
|
46
|
+
return obj
|
|
47
|
+
if isinstance(obj, bytes):
|
|
48
|
+
try:
|
|
49
|
+
return obj.decode()
|
|
50
|
+
except UnicodeDecodeError as e:
|
|
51
|
+
raise ValueError(f"Cannot decode bytes: {e}") from e
|
|
52
|
+
if callable(obj):
|
|
53
|
+
# its a function callable returning a value
|
|
54
|
+
try:
|
|
55
|
+
return str(obj())
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
return str(obj)
|
|
59
|
+
|
|
60
|
+
cpdef object to_uuid(object obj):
|
|
61
|
+
"""Returns a UUID version of a str column.
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(obj, pgproto.UUID):
|
|
64
|
+
# If it's asyncpg's UUID, convert by casting to string first
|
|
65
|
+
return UUID(str(obj))
|
|
66
|
+
if isinstance(obj, UUID):
|
|
67
|
+
# already an uuid
|
|
68
|
+
return obj
|
|
69
|
+
elif callable(obj):
|
|
70
|
+
# its a function callable returning a value
|
|
71
|
+
try:
|
|
72
|
+
return UUID(obj())
|
|
73
|
+
except:
|
|
74
|
+
pass
|
|
75
|
+
try:
|
|
76
|
+
return UUID(str(obj))
|
|
77
|
+
except ValueError:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
cpdef str slugify_camelcase(str obj):
|
|
82
|
+
"""slugify_camelcase.
|
|
83
|
+
|
|
84
|
+
Converting CamelCase into a spaced version, but don’t double-space
|
|
85
|
+
if the string already contains spaces or uppercase letters follow
|
|
86
|
+
existing spaces.
|
|
87
|
+
"""
|
|
88
|
+
if not obj:
|
|
89
|
+
return obj
|
|
90
|
+
|
|
91
|
+
slugified = [obj[0]]
|
|
92
|
+
for i in range(1, len(obj)):
|
|
93
|
+
c = obj[i]
|
|
94
|
+
# Condition: if c is uppercase AND the previous character isn't a space,
|
|
95
|
+
# insert a space before it.
|
|
96
|
+
if c.isupper() and not slugified[-1].isspace():
|
|
97
|
+
slugified.append(' ')
|
|
98
|
+
slugified.append(c)
|
|
99
|
+
return ''.join(slugified)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
cpdef datetime.date to_date(object obj):
|
|
103
|
+
"""to_date.
|
|
104
|
+
|
|
105
|
+
Returns obj converted to date.
|
|
106
|
+
"""
|
|
107
|
+
if obj is None:
|
|
108
|
+
return None
|
|
109
|
+
elif obj == _MISSING_TYPE:
|
|
110
|
+
return None
|
|
111
|
+
if isinstance(obj, datetime.date):
|
|
112
|
+
return obj
|
|
113
|
+
elif isinstance(obj, (datetime.datetime, datetime.timedelta)):
|
|
114
|
+
return obj.date()
|
|
115
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
116
|
+
obj = obj.decode("ascii")
|
|
117
|
+
# Handle Unix epoch via Rust's `to_timestamp`
|
|
118
|
+
if isinstance(obj, (int, float)):
|
|
119
|
+
try:
|
|
120
|
+
return rc.to_timestamp(obj).date()
|
|
121
|
+
except ValueError:
|
|
122
|
+
pass
|
|
123
|
+
# using rust todate function
|
|
124
|
+
try:
|
|
125
|
+
return rc.to_date(obj)
|
|
126
|
+
except ValueError:
|
|
127
|
+
pass
|
|
128
|
+
# Fallback to Cython-native ciso8601 parsing
|
|
129
|
+
try:
|
|
130
|
+
return ciso8601.parse_datetime(obj).date()
|
|
131
|
+
except ValueError:
|
|
132
|
+
pass
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"Can't convert invalid data *{obj}* to date"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
cpdef datetime.datetime to_datetime(object obj):
|
|
139
|
+
"""to_datetime.
|
|
140
|
+
|
|
141
|
+
Returns obj converted to datetime.
|
|
142
|
+
"""
|
|
143
|
+
if obj is None:
|
|
144
|
+
return None
|
|
145
|
+
elif obj == _MISSING_TYPE:
|
|
146
|
+
return None
|
|
147
|
+
if isinstance(obj, datetime.datetime):
|
|
148
|
+
return obj
|
|
149
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
150
|
+
obj = obj.decode("ascii")
|
|
151
|
+
# Handle Unix epoch via Rust's `to_timestamp`
|
|
152
|
+
if isinstance(obj, (int, float)):
|
|
153
|
+
try:
|
|
154
|
+
return rc.to_timestamp(obj)
|
|
155
|
+
except ValueError:
|
|
156
|
+
pass
|
|
157
|
+
try:
|
|
158
|
+
return rc.to_datetime(obj)
|
|
159
|
+
except ValueError:
|
|
160
|
+
pass
|
|
161
|
+
try:
|
|
162
|
+
return ciso8601.parse_datetime(obj)
|
|
163
|
+
except ValueError:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
f"Can't convert invalid data *{obj}* to datetime"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
cpdef object to_integer(object obj):
|
|
169
|
+
"""to_integer.
|
|
170
|
+
|
|
171
|
+
Returns object converted to integer.
|
|
172
|
+
"""
|
|
173
|
+
if obj is None:
|
|
174
|
+
return None
|
|
175
|
+
if isinstance(obj, int):
|
|
176
|
+
return obj
|
|
177
|
+
if isinstance(obj, unicode):
|
|
178
|
+
obj = obj.encode("ascii")
|
|
179
|
+
if isinstance(obj, bytes):
|
|
180
|
+
try:
|
|
181
|
+
return int(obj)
|
|
182
|
+
except (TypeError, ValueError) as e:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"Invalid conversion to Integer of {obj}"
|
|
185
|
+
) from e
|
|
186
|
+
elif callable(obj):
|
|
187
|
+
# its a function callable returning a value
|
|
188
|
+
try:
|
|
189
|
+
return obj()
|
|
190
|
+
except:
|
|
191
|
+
pass
|
|
192
|
+
else:
|
|
193
|
+
try:
|
|
194
|
+
return int(obj)
|
|
195
|
+
except (TypeError, ValueError) as e:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"Invalid conversion to Integer of {obj}"
|
|
198
|
+
) from e
|
|
199
|
+
|
|
200
|
+
cpdef object to_float(object obj):
|
|
201
|
+
"""to_float.
|
|
202
|
+
|
|
203
|
+
Returns object converted to float.
|
|
204
|
+
"""
|
|
205
|
+
if isinstance(obj, (float, Decimal)):
|
|
206
|
+
return obj
|
|
207
|
+
elif isinstance(obj, _MISSING_TYPE):
|
|
208
|
+
return None
|
|
209
|
+
elif callable(obj):
|
|
210
|
+
# its a function callable returning a value
|
|
211
|
+
try:
|
|
212
|
+
return obj()
|
|
213
|
+
except:
|
|
214
|
+
pass
|
|
215
|
+
else:
|
|
216
|
+
try:
|
|
217
|
+
return float(obj)
|
|
218
|
+
except (TypeError, ValueError):
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
cpdef object to_decimal(object obj):
|
|
222
|
+
"""to_decimal.
|
|
223
|
+
|
|
224
|
+
Returns a Decimal version of object.
|
|
225
|
+
"""
|
|
226
|
+
if obj is None:
|
|
227
|
+
return None
|
|
228
|
+
if isinstance(obj, Decimal):
|
|
229
|
+
return obj
|
|
230
|
+
elif callable(obj):
|
|
231
|
+
# its a function callable returning a value
|
|
232
|
+
try:
|
|
233
|
+
return obj()
|
|
234
|
+
except:
|
|
235
|
+
pass
|
|
236
|
+
else:
|
|
237
|
+
try:
|
|
238
|
+
return Decimal(obj)
|
|
239
|
+
except InvalidOperation as ex:
|
|
240
|
+
raise ValueError(
|
|
241
|
+
f"Invalid Decimal conversion of {obj}"
|
|
242
|
+
) from ex
|
|
243
|
+
except (TypeError, ValueError):
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
TIMEDELTA_RE = re.compile(r"(-)?(\d{1,3}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?")
|
|
247
|
+
|
|
248
|
+
cdef int _convert_second_fraction(s):
|
|
249
|
+
if not s:
|
|
250
|
+
return 0
|
|
251
|
+
# Pad zeros to ensure the fraction length in microseconds
|
|
252
|
+
s = s.ljust(6, "0")
|
|
253
|
+
return int(s[:6])
|
|
254
|
+
|
|
255
|
+
cpdef object to_timedelta(object obj):
|
|
256
|
+
|
|
257
|
+
if obj is None:
|
|
258
|
+
return None
|
|
259
|
+
if isinstance(obj, datetime.timedelta):
|
|
260
|
+
return obj
|
|
261
|
+
|
|
262
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
263
|
+
obj = obj.decode("ascii")
|
|
264
|
+
|
|
265
|
+
m = TIMEDELTA_RE.match(obj)
|
|
266
|
+
if not m:
|
|
267
|
+
return obj
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
groups = list(m.groups())
|
|
271
|
+
groups[-1] = _convert_second_fraction(groups[-1])
|
|
272
|
+
negate = -1 if groups[0] else 1
|
|
273
|
+
hours, minutes, seconds, microseconds = groups[1:]
|
|
274
|
+
tdelta = (
|
|
275
|
+
datetime.timedelta(
|
|
276
|
+
hours=int(hours),
|
|
277
|
+
minutes=int(minutes),
|
|
278
|
+
seconds=int(seconds),
|
|
279
|
+
microseconds=int(microseconds),
|
|
280
|
+
)
|
|
281
|
+
* negate
|
|
282
|
+
)
|
|
283
|
+
return tdelta
|
|
284
|
+
except ValueError:
|
|
285
|
+
raise ValueError(
|
|
286
|
+
f"Invalid timedelta Object: {obj}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})(?:.(\d{1,6}))?")
|
|
290
|
+
|
|
291
|
+
cpdef object to_time(object obj):
|
|
292
|
+
"""to_time.
|
|
293
|
+
|
|
294
|
+
Returns obj converted to datetime.time.
|
|
295
|
+
"""
|
|
296
|
+
if obj is None:
|
|
297
|
+
return None
|
|
298
|
+
if isinstance(obj, datetime.time):
|
|
299
|
+
return obj
|
|
300
|
+
elif callable(obj):
|
|
301
|
+
# its a function callable returning a value
|
|
302
|
+
try:
|
|
303
|
+
return obj()
|
|
304
|
+
except:
|
|
305
|
+
pass
|
|
306
|
+
else:
|
|
307
|
+
try:
|
|
308
|
+
return datetime.time(*map(int, obj.split(':')))
|
|
309
|
+
except (ValueError, TypeError):
|
|
310
|
+
pass
|
|
311
|
+
m = TIME_RE.match(obj)
|
|
312
|
+
if not m:
|
|
313
|
+
return obj
|
|
314
|
+
try:
|
|
315
|
+
groups = list(m.groups())
|
|
316
|
+
groups[-1] = _convert_second_fraction(groups[-1])
|
|
317
|
+
hours, minutes, seconds, microseconds = groups
|
|
318
|
+
return datetime.time(
|
|
319
|
+
hour=int(hours),
|
|
320
|
+
minute=int(minutes),
|
|
321
|
+
second=int(seconds),
|
|
322
|
+
microsecond=int(microseconds),
|
|
323
|
+
)
|
|
324
|
+
except (TypeError, ValueError) as e:
|
|
325
|
+
raise ValueError(
|
|
326
|
+
f"Invalid Time/Timestamp Object {obj}: {e}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
cpdef object strtobool(str val):
|
|
331
|
+
"""Convert a string representation of truth to true (1) or false (0).
|
|
332
|
+
|
|
333
|
+
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
|
|
334
|
+
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
|
|
335
|
+
'val' is anything else.
|
|
336
|
+
"""
|
|
337
|
+
val = val.lower()
|
|
338
|
+
if val in ('y', 'yes', 't', 'true', 'on', '1', 'T'):
|
|
339
|
+
return True
|
|
340
|
+
elif val in ('n', 'no', 'f', 'false', 'off', '0', 'none', 'null'):
|
|
341
|
+
return False
|
|
342
|
+
else:
|
|
343
|
+
raise ValueError(
|
|
344
|
+
f"Invalid truth value for **'{val}'**"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
cpdef object to_boolean(object obj):
|
|
348
|
+
"""to_boolean.
|
|
349
|
+
|
|
350
|
+
Convert and returns any object value to boolean version.
|
|
351
|
+
"""
|
|
352
|
+
if obj is None:
|
|
353
|
+
return None
|
|
354
|
+
if isinstance(obj, bool):
|
|
355
|
+
return obj
|
|
356
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
357
|
+
obj = obj.decode("ascii")
|
|
358
|
+
if isinstance(obj, str):
|
|
359
|
+
return strtobool(obj)
|
|
360
|
+
elif callable(obj):
|
|
361
|
+
# its a function callable returning a value
|
|
362
|
+
try:
|
|
363
|
+
return obj()
|
|
364
|
+
except:
|
|
365
|
+
pass
|
|
366
|
+
else:
|
|
367
|
+
return bool(obj)
|
|
368
|
+
|
|
369
|
+
cpdef object to_object(object obj):
|
|
370
|
+
if obj is None:
|
|
371
|
+
return None
|
|
372
|
+
if isinstance(obj, (list, dict,tuple)):
|
|
373
|
+
return obj
|
|
374
|
+
elif callable(obj):
|
|
375
|
+
# its a function callable returning a value
|
|
376
|
+
try:
|
|
377
|
+
return obj()
|
|
378
|
+
except:
|
|
379
|
+
pass
|
|
380
|
+
elif isinstance(obj, str):
|
|
381
|
+
try:
|
|
382
|
+
return orjson.loads(obj)
|
|
383
|
+
except (TypeError, ValueError):
|
|
384
|
+
return None
|
|
385
|
+
else:
|
|
386
|
+
raise ValueError(
|
|
387
|
+
f"Can't convert invalid data {obj} to Object"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
cpdef bytes to_bytes(object obj):
|
|
391
|
+
"""
|
|
392
|
+
Convert the given object to bytes.
|
|
393
|
+
|
|
394
|
+
- If the object is already bytes, return it directly.
|
|
395
|
+
- If the object is a string, encode it (using UTF-8).
|
|
396
|
+
- If the object is callable, call it and convert its result.
|
|
397
|
+
- Otherwise, attempt to convert the object to bytes.
|
|
398
|
+
If conversion fails, raise a ValueError.
|
|
399
|
+
"""
|
|
400
|
+
if obj is None:
|
|
401
|
+
raise ValueError("Cannot convert None to bytes")
|
|
402
|
+
|
|
403
|
+
# 1. If already bytes, return as is.
|
|
404
|
+
if isinstance(obj, bytes):
|
|
405
|
+
return obj
|
|
406
|
+
|
|
407
|
+
# 2. If it's a string, encode it to bytes.
|
|
408
|
+
elif isinstance(obj, str):
|
|
409
|
+
return obj.encode("utf-8")
|
|
410
|
+
|
|
411
|
+
# 3. If it's callable, attempt to call it and convert its result.
|
|
412
|
+
elif callable(obj):
|
|
413
|
+
try:
|
|
414
|
+
result = obj()
|
|
415
|
+
# Recursively convert the result.
|
|
416
|
+
return to_bytes(result)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
raise ValueError("Failed to convert callable to bytes: %s" % e)
|
|
419
|
+
|
|
420
|
+
# 4. Try converting the object into bytes using Python's built-in conversion.
|
|
421
|
+
try:
|
|
422
|
+
return bytes(obj)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
raise ValueError("Invalid conversion to bytes: %s" % e)
|
|
425
|
+
|
|
426
|
+
cdef bint is_callable(object value) nogil:
|
|
427
|
+
"""
|
|
428
|
+
Check if `value` is callable by calling Python's callable(...)
|
|
429
|
+
but reacquire the GIL inside.
|
|
430
|
+
"""
|
|
431
|
+
with gil:
|
|
432
|
+
return callable(value)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# Encoder List:
|
|
436
|
+
encoders = {
|
|
437
|
+
str: to_string,
|
|
438
|
+
UUID: to_uuid,
|
|
439
|
+
pgproto.UUID: to_uuid,
|
|
440
|
+
bool: to_boolean,
|
|
441
|
+
int: to_integer,
|
|
442
|
+
float: to_float,
|
|
443
|
+
datetime.date: to_date,
|
|
444
|
+
datetime.datetime: to_datetime,
|
|
445
|
+
datetime.timedelta: to_timedelta,
|
|
446
|
+
datetime.time: to_time,
|
|
447
|
+
Decimal: to_decimal,
|
|
448
|
+
dict: to_object,
|
|
449
|
+
list: to_object,
|
|
450
|
+
tuple: to_object,
|
|
451
|
+
bytes: to_bytes,
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# Maps a type to a conversion callable
|
|
456
|
+
cdef dict TYPE_PARSERS = {}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
cpdef object register_parser(object _type, object parser_func):
|
|
460
|
+
"""register_parser.
|
|
461
|
+
|
|
462
|
+
Register a new Parser function for a given type.
|
|
463
|
+
|
|
464
|
+
Parameters:
|
|
465
|
+
_type (type): The type for which the parser function is registered.
|
|
466
|
+
parser_func (function): The parser function to convert the given type.
|
|
467
|
+
"""
|
|
468
|
+
TYPE_PARSERS[_type] = parser_func
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
## Parsing Functions
|
|
472
|
+
|
|
473
|
+
cdef object _parse_dict_type(
|
|
474
|
+
object field,
|
|
475
|
+
object T,
|
|
476
|
+
object data,
|
|
477
|
+
object encoder,
|
|
478
|
+
object args
|
|
479
|
+
):
|
|
480
|
+
cdef object val_type = args[1]
|
|
481
|
+
cdef dict new_dict = {}
|
|
482
|
+
for k, v in data.items():
|
|
483
|
+
new_dict[k] = parse_typing(field, val_type, v, encoder, False)
|
|
484
|
+
return new_dict
|
|
485
|
+
|
|
486
|
+
cdef object _parse_list_type(
|
|
487
|
+
object field,
|
|
488
|
+
object T,
|
|
489
|
+
object data,
|
|
490
|
+
object encoder,
|
|
491
|
+
object args,
|
|
492
|
+
object _parent = None
|
|
493
|
+
):
|
|
494
|
+
"""
|
|
495
|
+
Parse a list of items to a typing type.
|
|
496
|
+
"""
|
|
497
|
+
cdef object arg_type = args[0]
|
|
498
|
+
cdef list result = []
|
|
499
|
+
cdef tuple key = (arg_type, field.name)
|
|
500
|
+
cdef object converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(arg_type)
|
|
501
|
+
cdef object inner_type = field._inner_type or arg_type
|
|
502
|
+
|
|
503
|
+
if data is None:
|
|
504
|
+
return [] # short-circuit
|
|
505
|
+
|
|
506
|
+
if not isinstance(data, (list, tuple)):
|
|
507
|
+
data = [data]
|
|
508
|
+
|
|
509
|
+
# If it's a dataclass
|
|
510
|
+
if is_dc(inner_type):
|
|
511
|
+
for d in data:
|
|
512
|
+
if is_dc(d):
|
|
513
|
+
result.append(d)
|
|
514
|
+
if converter:
|
|
515
|
+
result.append(
|
|
516
|
+
converter(field.name, d, inner_type, _parent)
|
|
517
|
+
)
|
|
518
|
+
else:
|
|
519
|
+
if isinstance(d, dict):
|
|
520
|
+
result.append(inner_type(**d))
|
|
521
|
+
elif isinstance(d, (list, tuple)):
|
|
522
|
+
result.append(inner_type(*d))
|
|
523
|
+
else:
|
|
524
|
+
result.append(inner_type(d))
|
|
525
|
+
return result
|
|
526
|
+
else:
|
|
527
|
+
# General conversion
|
|
528
|
+
for item in data:
|
|
529
|
+
if converter:
|
|
530
|
+
result.append(
|
|
531
|
+
converter(field.name, item, inner_type, _parent)
|
|
532
|
+
)
|
|
533
|
+
else:
|
|
534
|
+
result.append(
|
|
535
|
+
parse_typing(field, inner_type, item, encoder, False)
|
|
536
|
+
)
|
|
537
|
+
return result
|
|
538
|
+
|
|
539
|
+
cdef object _parse_dataclass_type(object T, object data):
|
|
540
|
+
if isinstance(data, dict):
|
|
541
|
+
return T(**data)
|
|
542
|
+
elif isinstance(data, (list, tuple)):
|
|
543
|
+
return T(*data)
|
|
544
|
+
else:
|
|
545
|
+
return T(data)
|
|
546
|
+
|
|
547
|
+
cdef object _parse_builtin_type(object field, object T, object data, object encoder):
|
|
548
|
+
if encoder is not None:
|
|
549
|
+
try:
|
|
550
|
+
return encoder(data)
|
|
551
|
+
except ValueError as e:
|
|
552
|
+
raise ValueError(f"Error parsing type {T}, {e}")
|
|
553
|
+
elif T == str:
|
|
554
|
+
return to_string(data)
|
|
555
|
+
elif T == UUID:
|
|
556
|
+
return to_uuid(data)
|
|
557
|
+
elif is_dc(T):
|
|
558
|
+
return _parse_dataclass_type(T, data)
|
|
559
|
+
elif T == datetime.date:
|
|
560
|
+
return to_date(data)
|
|
561
|
+
elif T == datetime.datetime:
|
|
562
|
+
return to_datetime(data)
|
|
563
|
+
else:
|
|
564
|
+
# Try encoders dict:
|
|
565
|
+
try:
|
|
566
|
+
if field._encoder_fn is None:
|
|
567
|
+
field._encoder_fn = encoders[T]
|
|
568
|
+
return field._encoder_fn(data)
|
|
569
|
+
except KeyError:
|
|
570
|
+
# attempt direct construction:
|
|
571
|
+
if isinstance(T, type):
|
|
572
|
+
try:
|
|
573
|
+
if isinstance(data, dict):
|
|
574
|
+
return T(**data)
|
|
575
|
+
elif isinstance(data, (list, tuple)):
|
|
576
|
+
return T(*data)
|
|
577
|
+
elif isinstance(data, str):
|
|
578
|
+
return T(data)
|
|
579
|
+
except (TypeError, ValueError):
|
|
580
|
+
pass
|
|
581
|
+
return data
|
|
582
|
+
except (TypeError) as e:
|
|
583
|
+
raise TypeError(f"Error type {T}: {e}") from e
|
|
584
|
+
except (ValueError) as e:
|
|
585
|
+
raise ValueError(
|
|
586
|
+
f"Error parsing type {T}: {e}"
|
|
587
|
+
) from e
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
cpdef object parse_basic(object T, object data, object encoder = None):
|
|
591
|
+
"""parse_basic.
|
|
592
|
+
|
|
593
|
+
Parse a value to primitive types as str or int.
|
|
594
|
+
--- (int, float, str, bool, bytes)
|
|
595
|
+
"""
|
|
596
|
+
if T == str:
|
|
597
|
+
if isinstance(data, str):
|
|
598
|
+
return data
|
|
599
|
+
elif data is not None:
|
|
600
|
+
return str(data)
|
|
601
|
+
if T == int:
|
|
602
|
+
if isinstance(data, int):
|
|
603
|
+
return data
|
|
604
|
+
elif data is not None:
|
|
605
|
+
return int(data)
|
|
606
|
+
if T == bytes:
|
|
607
|
+
if data is not None:
|
|
608
|
+
return bytes(data)
|
|
609
|
+
if T == UUID or T == pgproto.UUID:
|
|
610
|
+
return to_uuid(data)
|
|
611
|
+
if T == bool:
|
|
612
|
+
if isinstance(data, bool):
|
|
613
|
+
return data
|
|
614
|
+
# function encoder:
|
|
615
|
+
if encoder:
|
|
616
|
+
if is_callable(encoder):
|
|
617
|
+
# using a function encoder:
|
|
618
|
+
try:
|
|
619
|
+
return encoder(data)
|
|
620
|
+
except TypeError as e:
|
|
621
|
+
raise TypeError(
|
|
622
|
+
f"Type Error for Encoder {encoder!s} for type {T}: {e}"
|
|
623
|
+
) from e
|
|
624
|
+
except ValueError as e:
|
|
625
|
+
raise ValueError(
|
|
626
|
+
f"Error parsing type {T}, {e}"
|
|
627
|
+
)
|
|
628
|
+
# Using the encoders for basic types:
|
|
629
|
+
try:
|
|
630
|
+
return encoders[T](data)
|
|
631
|
+
except KeyError:
|
|
632
|
+
pass
|
|
633
|
+
except TypeError as e:
|
|
634
|
+
raise TypeError(f"Error type {T}: {e}") from e
|
|
635
|
+
except ValueError as e:
|
|
636
|
+
raise ValueError(
|
|
637
|
+
f"Error parsing type {T}: {e}"
|
|
638
|
+
) from e
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
cdef object _parse_typing_type(
|
|
642
|
+
object field,
|
|
643
|
+
object T,
|
|
644
|
+
object name,
|
|
645
|
+
object data,
|
|
646
|
+
object encoder,
|
|
647
|
+
object origin,
|
|
648
|
+
object args,
|
|
649
|
+
object as_objects=False
|
|
650
|
+
):
|
|
651
|
+
"""
|
|
652
|
+
Handle field_type='typing' scenario.
|
|
653
|
+
"""
|
|
654
|
+
cdef tuple type_args = getattr(T, '__args__', ())
|
|
655
|
+
|
|
656
|
+
# print('FIELD > ', field)
|
|
657
|
+
# print('T > ', T)
|
|
658
|
+
# print('NAME > ', name)
|
|
659
|
+
# print('DATA > ', data)
|
|
660
|
+
# print('TYPE > ', type_args)
|
|
661
|
+
|
|
662
|
+
if field.origin in {dict, Mapping} or name in {'Dict', 'Mapping'}:
|
|
663
|
+
if isinstance(data, dict):
|
|
664
|
+
if type_args:
|
|
665
|
+
# e.g. Dict[K, V]
|
|
666
|
+
return {k: _parse_type(field, type_args[1], v, None, False) for k, v in data.items()}
|
|
667
|
+
return data
|
|
668
|
+
|
|
669
|
+
if name == 'Tuple' or field.origin == tuple:
|
|
670
|
+
if isinstance(data, (list, tuple)):
|
|
671
|
+
if len(data) == len(type_args):
|
|
672
|
+
return tuple(
|
|
673
|
+
_parse_type(field, typ, datum, encoder, False)
|
|
674
|
+
for typ, datum in zip(type_args, data)
|
|
675
|
+
)
|
|
676
|
+
else:
|
|
677
|
+
if len(type_args) == 2 and type_args[1] is Ellipsis:
|
|
678
|
+
# e.g. Tuple[str, ...]
|
|
679
|
+
return tuple(
|
|
680
|
+
_parse_type(field, type_args[0], datum, None, False)
|
|
681
|
+
for datum in data
|
|
682
|
+
)
|
|
683
|
+
return tuple(data)
|
|
684
|
+
|
|
685
|
+
if name in {'List', 'Sequence'} or field.origin in {list, Sequence}:
|
|
686
|
+
if not isinstance(data, (list, tuple)):
|
|
687
|
+
data = [data]
|
|
688
|
+
return _parse_list_typing(
|
|
689
|
+
field,
|
|
690
|
+
type_args,
|
|
691
|
+
data,
|
|
692
|
+
encoder,
|
|
693
|
+
origin,
|
|
694
|
+
args,
|
|
695
|
+
as_objects=as_objects
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
# handle None, Optional, Union, etc.
|
|
699
|
+
if name is None or name in ('Optional', 'Union'):
|
|
700
|
+
return _parse_optional_union(field, T, data, encoder, origin, args)
|
|
701
|
+
|
|
702
|
+
return data
|
|
703
|
+
|
|
704
|
+
cdef object _parse_list_typing(
|
|
705
|
+
object field,
|
|
706
|
+
tuple type_args,
|
|
707
|
+
object data,
|
|
708
|
+
object encoder,
|
|
709
|
+
object origin,
|
|
710
|
+
object args,
|
|
711
|
+
object as_objects=False,
|
|
712
|
+
dict typeinfo=None
|
|
713
|
+
):
|
|
714
|
+
"""
|
|
715
|
+
Handle List[T] logic, trying to reduce overhead from repeated lookups.
|
|
716
|
+
"""
|
|
717
|
+
cdef list result = []
|
|
718
|
+
cdef list out = []
|
|
719
|
+
cdef object arg_type = type_args[0] if type_args else None
|
|
720
|
+
cdef object arg_module = getattr(arg_type, '__module__', None)
|
|
721
|
+
cdef bint is_nested_typing = (arg_module == 'typing')
|
|
722
|
+
|
|
723
|
+
# If no type args, we can't proceed with further logic
|
|
724
|
+
if not type_args:
|
|
725
|
+
return data
|
|
726
|
+
|
|
727
|
+
if is_nested_typing:
|
|
728
|
+
# nested typing: e.g. List[List[Foo]] or List[Optional[Foo]] etc.
|
|
729
|
+
try:
|
|
730
|
+
subT = arg_type.__args__[0]
|
|
731
|
+
if is_dc(subT):
|
|
732
|
+
for x in data:
|
|
733
|
+
result.append(_instantiate_dataclass(subT, x))
|
|
734
|
+
return result
|
|
735
|
+
else:
|
|
736
|
+
# fallback
|
|
737
|
+
return data
|
|
738
|
+
except AttributeError:
|
|
739
|
+
return data
|
|
740
|
+
elif arg_type is not None and is_dc(arg_type):
|
|
741
|
+
# build list of dataclasses
|
|
742
|
+
for d in data:
|
|
743
|
+
result.append(_instantiate_dataclass(arg_type, d))
|
|
744
|
+
return result
|
|
745
|
+
else:
|
|
746
|
+
# parse each item
|
|
747
|
+
for item in data:
|
|
748
|
+
result.append(_parse_type(field, arg_type, item, encoder, False))
|
|
749
|
+
return result
|
|
750
|
+
|
|
751
|
+
cdef object _instantiate_dataclass(object cls, object val):
|
|
752
|
+
"""
|
|
753
|
+
Helper for instantiating a dataclass.
|
|
754
|
+
"""
|
|
755
|
+
if is_dc(val):
|
|
756
|
+
return val
|
|
757
|
+
if isinstance(val, dict):
|
|
758
|
+
return cls(**val)
|
|
759
|
+
elif isinstance(val, (list, tuple)):
|
|
760
|
+
return cls(*val)
|
|
761
|
+
else:
|
|
762
|
+
return cls(val)
|
|
763
|
+
|
|
764
|
+
cdef object _parse_optional_union(
|
|
765
|
+
object field,
|
|
766
|
+
object T,
|
|
767
|
+
object data,
|
|
768
|
+
object encoder,
|
|
769
|
+
object origin,
|
|
770
|
+
object args
|
|
771
|
+
):
|
|
772
|
+
"""
|
|
773
|
+
Handle Optional or Union logic.
|
|
774
|
+
"""
|
|
775
|
+
cdef object non_none_arg
|
|
776
|
+
cdef object t = args[0] if args else None
|
|
777
|
+
cdef bint matched = False
|
|
778
|
+
|
|
779
|
+
# e.g. Optional[T] is Union[T, NoneType]
|
|
780
|
+
if origin == Union and type(None) in args:
|
|
781
|
+
if data is None:
|
|
782
|
+
return None
|
|
783
|
+
# Pick the non-None type (assumes only two types in the Union)
|
|
784
|
+
non_none_arg = args[0] if args[1] is type(None) else args[1]
|
|
785
|
+
return _parse_type(
|
|
786
|
+
field,
|
|
787
|
+
T=non_none_arg,
|
|
788
|
+
data=data,
|
|
789
|
+
encoder=encoder,
|
|
790
|
+
as_objects=False
|
|
791
|
+
)
|
|
792
|
+
# Remove None from args.
|
|
793
|
+
args = tuple(t for t in args if t is not type(None))
|
|
794
|
+
# If there are no non-None types left, simply return data.
|
|
795
|
+
if not args:
|
|
796
|
+
return data
|
|
797
|
+
|
|
798
|
+
for t in args:
|
|
799
|
+
if isinstance(data, t):
|
|
800
|
+
matched = True
|
|
801
|
+
break
|
|
802
|
+
if not matched:
|
|
803
|
+
raise ValueError(f"Invalid type for *{field.name}* with {type(data)}, expected {T}")
|
|
804
|
+
try:
|
|
805
|
+
if is_dc(t):
|
|
806
|
+
if isinstance(data, dict):
|
|
807
|
+
return t(**data)
|
|
808
|
+
elif isinstance(data, (list, tuple)):
|
|
809
|
+
return t(*data)
|
|
810
|
+
else:
|
|
811
|
+
return data
|
|
812
|
+
elif callable(t):
|
|
813
|
+
return data
|
|
814
|
+
return data
|
|
815
|
+
except KeyError:
|
|
816
|
+
pass
|
|
817
|
+
return data
|
|
818
|
+
|
|
819
|
+
cdef object _parse_union_type(
|
|
820
|
+
object field,
|
|
821
|
+
object T,
|
|
822
|
+
object name,
|
|
823
|
+
object data,
|
|
824
|
+
object encoder,
|
|
825
|
+
object origin,
|
|
826
|
+
object targs
|
|
827
|
+
):
|
|
828
|
+
"""
|
|
829
|
+
Attempt each type in the Union until one parses successfully
|
|
830
|
+
or raise an error if all fail.
|
|
831
|
+
If T is Optional[...] (i.e. a Union with NoneType), unwrap it.
|
|
832
|
+
"""
|
|
833
|
+
cdef object errors = []
|
|
834
|
+
cdef object non_none_arg = None
|
|
835
|
+
cdef tuple inner_targs = None
|
|
836
|
+
cdef bint is_typing = False
|
|
837
|
+
# If the union includes NoneType, unwrap it and use only the non-None type.
|
|
838
|
+
if origin == Union and type(None) in targs:
|
|
839
|
+
for arg in targs:
|
|
840
|
+
if arg is not type(None):
|
|
841
|
+
non_none_arg = arg
|
|
842
|
+
break
|
|
843
|
+
is_typing = hasattr(non_none_arg, '__module__') and non_none_arg.__module__ == 'typing'
|
|
844
|
+
if non_none_arg is not None and is_typing is True:
|
|
845
|
+
# Invoke the parse_typing_type
|
|
846
|
+
field.args = get_args(non_none_arg)
|
|
847
|
+
field.origin = get_origin(non_none_arg)
|
|
848
|
+
if isinstance(data, list):
|
|
849
|
+
return parse_typing(
|
|
850
|
+
field,
|
|
851
|
+
non_none_arg,
|
|
852
|
+
data,
|
|
853
|
+
encoder,
|
|
854
|
+
False
|
|
855
|
+
)
|
|
856
|
+
else:
|
|
857
|
+
pass
|
|
858
|
+
for arg_type in targs:
|
|
859
|
+
try:
|
|
860
|
+
if isinstance(data, list):
|
|
861
|
+
result = _parse_list_type(field, arg_type, data, encoder, targs)
|
|
862
|
+
else:
|
|
863
|
+
# fallback to builtin parse
|
|
864
|
+
result = parse_typing(
|
|
865
|
+
field,
|
|
866
|
+
arg_type,
|
|
867
|
+
data,
|
|
868
|
+
encoder,
|
|
869
|
+
False
|
|
870
|
+
)
|
|
871
|
+
return result
|
|
872
|
+
except Exception as exc:
|
|
873
|
+
errors.append(str(exc))
|
|
874
|
+
|
|
875
|
+
# If we get here, all union attempts failed
|
|
876
|
+
raise ValueError(f"Union parse failed for data={data}, errors={errors}")
|
|
877
|
+
|
|
878
|
+
cdef object _parse_type(
|
|
879
|
+
object field,
|
|
880
|
+
object T,
|
|
881
|
+
object data,
|
|
882
|
+
object encoder=None,
|
|
883
|
+
object as_objects=False,
|
|
884
|
+
):
|
|
885
|
+
"""
|
|
886
|
+
Parse a value to a typing type.
|
|
887
|
+
"""
|
|
888
|
+
# local cdef variables:
|
|
889
|
+
cdef object origin = get_origin(T)
|
|
890
|
+
cdef object targs = get_args(T)
|
|
891
|
+
cdef object name = getattr(T, '_name', None) # T._name or None if not present
|
|
892
|
+
cdef object sub = None # for subtypes, local cache
|
|
893
|
+
cdef object result = None
|
|
894
|
+
cdef object isdc = is_dc(T)
|
|
895
|
+
|
|
896
|
+
if data is None:
|
|
897
|
+
return None
|
|
898
|
+
|
|
899
|
+
if isdc:
|
|
900
|
+
result = _handle_dataclass_type(None, name, data, T, as_objects, None)
|
|
901
|
+
# Field type shortcuts
|
|
902
|
+
elif origin is dict and isinstance(data, dict):
|
|
903
|
+
result = _parse_dict_type(field, T, data, encoder, targs)
|
|
904
|
+
elif origin is list:
|
|
905
|
+
result = _parse_list_type(field, T, data, encoder, targs)
|
|
906
|
+
elif origin is not None:
|
|
907
|
+
# other advanced generics
|
|
908
|
+
result = data
|
|
909
|
+
else:
|
|
910
|
+
# fallback to builtin parse
|
|
911
|
+
result = _parse_builtin_type(field, T, data, encoder)
|
|
912
|
+
return result
|
|
913
|
+
|
|
914
|
+
cdef object parse_typing(
|
|
915
|
+
object field,
|
|
916
|
+
object T,
|
|
917
|
+
object data,
|
|
918
|
+
object encoder=None,
|
|
919
|
+
object as_objects=False,
|
|
920
|
+
object parent=None,
|
|
921
|
+
):
|
|
922
|
+
"""
|
|
923
|
+
Parse a value to a typing type.
|
|
924
|
+
"""
|
|
925
|
+
# local cdef variables:
|
|
926
|
+
cdef object origin, targs
|
|
927
|
+
cdef object name = getattr(T, '_name', None) # T._name or None if not present
|
|
928
|
+
cdef object sub = None # for subtypes, local cache
|
|
929
|
+
cdef object result = None
|
|
930
|
+
cdef object isdc = field.is_dc # is_dataclass(T)
|
|
931
|
+
cdef object inner_type = None
|
|
932
|
+
cdef bint inner_is_dc = 0 # field._inner_is_dc or is_dataclass(inner_type)
|
|
933
|
+
|
|
934
|
+
# Use cached values only if T is exactly the field's declared type.
|
|
935
|
+
if T == field.type:
|
|
936
|
+
origin = field.origin
|
|
937
|
+
targs = field.args
|
|
938
|
+
elif field._inner_type and field._inner_type == inner_type:
|
|
939
|
+
origin = field._inner_origin
|
|
940
|
+
targs = field._inner_args
|
|
941
|
+
else:
|
|
942
|
+
origin = get_origin(T)
|
|
943
|
+
targs = get_args(T)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
# For generic (typing) fields, reuse cached inner type info if available.
|
|
947
|
+
if origin is list and targs:
|
|
948
|
+
if field._inner_type is not None:
|
|
949
|
+
inner_type = field._inner_type
|
|
950
|
+
inner_origin = field._inner_origin
|
|
951
|
+
# Optionally, also use cached type arguments for the inner type.
|
|
952
|
+
else:
|
|
953
|
+
inner_type = targs[0]
|
|
954
|
+
inner_origin = get_origin(inner_type)
|
|
955
|
+
else:
|
|
956
|
+
inner_type = None
|
|
957
|
+
inner_origin = None
|
|
958
|
+
|
|
959
|
+
inner_is_dc = field._inner_is_dc or is_dc(inner_type)
|
|
960
|
+
|
|
961
|
+
if data is None:
|
|
962
|
+
return None
|
|
963
|
+
|
|
964
|
+
# If the field is a Union and data is a list, use _parse_union_type.
|
|
965
|
+
if origin is Union and isinstance(data, list):
|
|
966
|
+
return _parse_union_type(
|
|
967
|
+
field,
|
|
968
|
+
T,
|
|
969
|
+
name,
|
|
970
|
+
data,
|
|
971
|
+
encoder,
|
|
972
|
+
origin,
|
|
973
|
+
targs
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
# if is_dc(T):
|
|
977
|
+
if isdc:
|
|
978
|
+
result = _handle_dataclass_type(None, name, data, T, as_objects, None)
|
|
979
|
+
# Field type shortcuts
|
|
980
|
+
elif field._type_category == 'typing':
|
|
981
|
+
# For example, if the origin is list and the inner type is a dataclass,
|
|
982
|
+
# use _handle_dataclass_type on each element.
|
|
983
|
+
if origin is list:
|
|
984
|
+
# Use the cached inner type info if available.
|
|
985
|
+
if inner_is_dc:
|
|
986
|
+
result = _parse_list_type(field, T, data, encoder, targs, parent)
|
|
987
|
+
else:
|
|
988
|
+
result = _parse_typing_type(
|
|
989
|
+
field, T, name, data, encoder, origin, targs, as_objects
|
|
990
|
+
)
|
|
991
|
+
else:
|
|
992
|
+
result = _parse_typing_type(
|
|
993
|
+
field, T, name, data, encoder, origin, targs, as_objects
|
|
994
|
+
)
|
|
995
|
+
elif origin is dict and isinstance(data, dict):
|
|
996
|
+
result = _parse_dict_type(field, T, data, encoder, targs)
|
|
997
|
+
elif origin is list:
|
|
998
|
+
result = _parse_list_type(field, T, data, encoder, targs, parent)
|
|
999
|
+
elif origin is not None:
|
|
1000
|
+
# other advanced generics
|
|
1001
|
+
result = data
|
|
1002
|
+
else:
|
|
1003
|
+
# fallback to builtin parse
|
|
1004
|
+
result = _parse_builtin_type(field, T, data, encoder)
|
|
1005
|
+
return result
|
|
1006
|
+
|
|
1007
|
+
cdef object _handle_dataclass_type(
|
|
1008
|
+
object field,
|
|
1009
|
+
str name,
|
|
1010
|
+
object value,
|
|
1011
|
+
object _type,
|
|
1012
|
+
object as_objects = False,
|
|
1013
|
+
object parent = None
|
|
1014
|
+
):
|
|
1015
|
+
"""
|
|
1016
|
+
_handle_dataclass_type.
|
|
1017
|
+
|
|
1018
|
+
Process a field that is annotated as SomeDataclass.
|
|
1019
|
+
If there's a registered converter for the dataclass, call it;
|
|
1020
|
+
otherwise, build the dataclass using default logic.
|
|
1021
|
+
"""
|
|
1022
|
+
cdef tuple key = (_type, name)
|
|
1023
|
+
cdef object converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(_type)
|
|
1024
|
+
cdef bint isdc = field.is_dc if field else is_dc(_type)
|
|
1025
|
+
cdef object field_metadata = field.metadata if field else {}
|
|
1026
|
+
cdef str alias = field_metadata.get('alias')
|
|
1027
|
+
|
|
1028
|
+
if value is None or is_dc(value):
|
|
1029
|
+
return value
|
|
1030
|
+
if PyObject_IsInstance(value, dict):
|
|
1031
|
+
try:
|
|
1032
|
+
# If alias exists, adjust the key passed to the dataclass
|
|
1033
|
+
if alias:
|
|
1034
|
+
# if alias exists on type, preserve the alias:
|
|
1035
|
+
if alias not in value and name in value:
|
|
1036
|
+
value = value.copy()
|
|
1037
|
+
value[alias] = value.pop(name)
|
|
1038
|
+
# convert the dictionary to the dataclass
|
|
1039
|
+
return _type(**value)
|
|
1040
|
+
except TypeError:
|
|
1041
|
+
# Ensure keys are strings
|
|
1042
|
+
value = {str(k): v for k, v in value.items()}
|
|
1043
|
+
if alias:
|
|
1044
|
+
value = value.copy()
|
|
1045
|
+
value[name] = value.pop(alias, None)
|
|
1046
|
+
return _type(**value)
|
|
1047
|
+
except ValueError:
|
|
1048
|
+
# replace in "value" dictionary the current "name" for "alias"
|
|
1049
|
+
if alias:
|
|
1050
|
+
value = value.copy()
|
|
1051
|
+
value[alias] = value.pop(name, None)
|
|
1052
|
+
return _type(**value)
|
|
1053
|
+
except Exception as exc:
|
|
1054
|
+
raise ValueError(
|
|
1055
|
+
f"Invalid value for {name}:{_type} == {value}, error: {exc}"
|
|
1056
|
+
)
|
|
1057
|
+
try:
|
|
1058
|
+
if isinstance(value, (list, tuple)):
|
|
1059
|
+
return _type(*value)
|
|
1060
|
+
else:
|
|
1061
|
+
# If a converter exists for this type, use it:
|
|
1062
|
+
if converter:
|
|
1063
|
+
return converter(name, value, _type, parent)
|
|
1064
|
+
if as_objects:
|
|
1065
|
+
# If alias exists, adjust the key passed to the dataclass
|
|
1066
|
+
if not alias:
|
|
1067
|
+
alias = name
|
|
1068
|
+
# convert the list to the dataclass
|
|
1069
|
+
return _type(**{alias: value})
|
|
1070
|
+
if isinstance(value, (int, str, UUID)):
|
|
1071
|
+
return value
|
|
1072
|
+
if isdc:
|
|
1073
|
+
if not alias:
|
|
1074
|
+
alias = name
|
|
1075
|
+
return _type(**{alias: value})
|
|
1076
|
+
else:
|
|
1077
|
+
return _type(value)
|
|
1078
|
+
except Exception as exc:
|
|
1079
|
+
raise ValueError(
|
|
1080
|
+
f"Invalid value for {name}:{_type} == {value}, error: {exc}"
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
cdef object _handle_list_of_dataclasses(
|
|
1084
|
+
object field,
|
|
1085
|
+
str name,
|
|
1086
|
+
object value,
|
|
1087
|
+
object _type,
|
|
1088
|
+
object parent = None
|
|
1089
|
+
):
|
|
1090
|
+
"""
|
|
1091
|
+
_handle_list_of_dataclasses.
|
|
1092
|
+
|
|
1093
|
+
Process a list field that is annotated as List[SomeDataclass].
|
|
1094
|
+
If there's a registered converter for the sub-dataclass, call it;
|
|
1095
|
+
otherwise, build the sub-dataclass using default logic.
|
|
1096
|
+
"""
|
|
1097
|
+
try:
|
|
1098
|
+
sub_type = _type.__args__[0]
|
|
1099
|
+
if is_dc(sub_type):
|
|
1100
|
+
key = (sub_type, name)
|
|
1101
|
+
converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(_type)
|
|
1102
|
+
new_list = []
|
|
1103
|
+
for item in value:
|
|
1104
|
+
if converter:
|
|
1105
|
+
new_list.append(converter(name, item, sub_type, parent))
|
|
1106
|
+
elif isinstance(item, dict):
|
|
1107
|
+
new_list.append(sub_type(**item))
|
|
1108
|
+
else:
|
|
1109
|
+
new_list.append(item)
|
|
1110
|
+
return new_list
|
|
1111
|
+
except AttributeError:
|
|
1112
|
+
pass
|
|
1113
|
+
return value
|
|
1114
|
+
|
|
1115
|
+
cdef object _handle_default_value(
|
|
1116
|
+
object obj,
|
|
1117
|
+
str name,
|
|
1118
|
+
object value,
|
|
1119
|
+
object default_func,
|
|
1120
|
+
object default_is_callable
|
|
1121
|
+
):
|
|
1122
|
+
"""Handle default value of fields."""
|
|
1123
|
+
# If value is callable, try calling it directly
|
|
1124
|
+
if PyCallable_Check(value):
|
|
1125
|
+
try:
|
|
1126
|
+
new_val = value()
|
|
1127
|
+
except TypeError:
|
|
1128
|
+
try:
|
|
1129
|
+
new_val = default_func()
|
|
1130
|
+
except TypeError:
|
|
1131
|
+
new_val = None
|
|
1132
|
+
setattr(obj, name, new_val)
|
|
1133
|
+
return new_val
|
|
1134
|
+
|
|
1135
|
+
# If f.default is callable and value is None
|
|
1136
|
+
if default_is_callable and value is None:
|
|
1137
|
+
try:
|
|
1138
|
+
new_val = default_func()
|
|
1139
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
1140
|
+
new_val = None
|
|
1141
|
+
setattr(obj, name, new_val)
|
|
1142
|
+
return new_val
|
|
1143
|
+
|
|
1144
|
+
# If there's a non-missing default and no value
|
|
1145
|
+
if not isinstance(default_func, _MISSING_TYPE) and value is None:
|
|
1146
|
+
setattr(obj, name, default_func)
|
|
1147
|
+
return default_func
|
|
1148
|
+
|
|
1149
|
+
# Otherwise, return value as-is
|
|
1150
|
+
return value
|
|
1151
|
+
|
|
1152
|
+
cpdef dict processing_fields(object obj, list columns):
|
|
1153
|
+
"""
|
|
1154
|
+
Process the fields (columns) of a dataclass object.
|
|
1155
|
+
|
|
1156
|
+
For each field, if a custom parser is attached (i.e. f.parser is not None),
|
|
1157
|
+
it is used to convert the value. Otherwise, the standard conversion logic
|
|
1158
|
+
(parse_basic, parse_typing, etc.) is applied.
|
|
1159
|
+
"""
|
|
1160
|
+
cdef object new_val
|
|
1161
|
+
cdef object _encoder = None
|
|
1162
|
+
cdef object _default = None
|
|
1163
|
+
cdef object _type = None
|
|
1164
|
+
cdef object meta = obj.Meta
|
|
1165
|
+
cdef bint as_objects = meta.as_objects
|
|
1166
|
+
cdef bint no_nesting = meta.no_nesting
|
|
1167
|
+
cdef dict errors = {}
|
|
1168
|
+
cdef dict _typeinfo = {}
|
|
1169
|
+
|
|
1170
|
+
for name, f in columns:
|
|
1171
|
+
value = getattr(obj, name)
|
|
1172
|
+
# Use the precomputed field type category:
|
|
1173
|
+
field_category = f._type_category
|
|
1174
|
+
|
|
1175
|
+
if field_category == 'descriptor':
|
|
1176
|
+
# Handle descriptor-specific logic
|
|
1177
|
+
try:
|
|
1178
|
+
value = f.__get__(obj, type(obj)) # Get the descriptor value
|
|
1179
|
+
setattr(obj, name, value)
|
|
1180
|
+
except Exception as e:
|
|
1181
|
+
errors[name] = f"Descriptor error in {name}: {e}"
|
|
1182
|
+
continue
|
|
1183
|
+
|
|
1184
|
+
# get type and default:
|
|
1185
|
+
_type = f.type
|
|
1186
|
+
_default = f.default
|
|
1187
|
+
typeinfo = f.typeinfo # cached info (e.g., type_args, default_callable)
|
|
1188
|
+
metadata = PyObject_GetAttr(f, "metadata")
|
|
1189
|
+
_encoder = metadata.get('encoder')
|
|
1190
|
+
_default_callable = typeinfo.get('default_callable', False)
|
|
1191
|
+
|
|
1192
|
+
if isinstance(_type, NewType):
|
|
1193
|
+
_type = _type.__supertype__
|
|
1194
|
+
|
|
1195
|
+
try:
|
|
1196
|
+
# Check if object is empty
|
|
1197
|
+
if is_empty(value) and not isinstance(value, list):
|
|
1198
|
+
if _type == str and value is not "":
|
|
1199
|
+
value = f.default_factory if isinstance(_default, (_MISSING_TYPE)) else _default
|
|
1200
|
+
setattr(obj, name, value)
|
|
1201
|
+
if _default is not None:
|
|
1202
|
+
value = _handle_default_value(obj, name, value, _default, _default_callable)
|
|
1203
|
+
|
|
1204
|
+
if f.parser is not None:
|
|
1205
|
+
# If a custom parser is attached to Field, use it
|
|
1206
|
+
try:
|
|
1207
|
+
value = f.parser(value)
|
|
1208
|
+
setattr(obj, name, value)
|
|
1209
|
+
except Exception as ex:
|
|
1210
|
+
errors[name] = f"Error parsing *{name}* = *{value}*, error: {ex}"
|
|
1211
|
+
continue
|
|
1212
|
+
|
|
1213
|
+
elif field_category == 'primitive':
|
|
1214
|
+
try:
|
|
1215
|
+
value = parse_basic(_type, value, _encoder)
|
|
1216
|
+
setattr(obj, name, value)
|
|
1217
|
+
except ValueError as ex:
|
|
1218
|
+
errors[name] = f"Error parsing {name}: {ex}"
|
|
1219
|
+
continue
|
|
1220
|
+
elif field_category == 'type':
|
|
1221
|
+
# TODO: support multiple types
|
|
1222
|
+
pass
|
|
1223
|
+
elif field_category == 'dataclass':
|
|
1224
|
+
if no_nesting is False:
|
|
1225
|
+
if as_objects is True:
|
|
1226
|
+
value = _handle_dataclass_type(
|
|
1227
|
+
f, name, value, _type, as_objects, obj
|
|
1228
|
+
)
|
|
1229
|
+
else:
|
|
1230
|
+
value = _handle_dataclass_type(
|
|
1231
|
+
f, name, value, _type, as_objects, None
|
|
1232
|
+
)
|
|
1233
|
+
setattr(obj, name, value)
|
|
1234
|
+
elif f.origin in (list, 'list') and f._inner_is_dc:
|
|
1235
|
+
if as_objects is True:
|
|
1236
|
+
value = _handle_list_of_dataclasses(f, name, value, _type, obj)
|
|
1237
|
+
else:
|
|
1238
|
+
value = _handle_list_of_dataclasses(f, name, value, _type, None)
|
|
1239
|
+
setattr(obj, name, value)
|
|
1240
|
+
elif isinstance(value, list) and typeinfo.get('type_args'):
|
|
1241
|
+
if as_objects is True:
|
|
1242
|
+
value = _handle_list_of_dataclasses(f, name, value, _type, obj)
|
|
1243
|
+
else:
|
|
1244
|
+
value = _handle_list_of_dataclasses(f, name, value, _type, None)
|
|
1245
|
+
setattr(obj, name, value)
|
|
1246
|
+
elif field_category == 'typing':
|
|
1247
|
+
value = parse_typing(
|
|
1248
|
+
f,
|
|
1249
|
+
_type,
|
|
1250
|
+
value,
|
|
1251
|
+
_encoder,
|
|
1252
|
+
as_objects,
|
|
1253
|
+
obj
|
|
1254
|
+
)
|
|
1255
|
+
setattr(obj, name, value)
|
|
1256
|
+
else:
|
|
1257
|
+
value = parse_typing(
|
|
1258
|
+
f,
|
|
1259
|
+
_type,
|
|
1260
|
+
value,
|
|
1261
|
+
_encoder,
|
|
1262
|
+
as_objects,
|
|
1263
|
+
obj
|
|
1264
|
+
)
|
|
1265
|
+
setattr(obj, name, value)
|
|
1266
|
+
# then, call the validation process:
|
|
1267
|
+
if (error := _validation_(name, value, f, _type, meta, field_category, as_objects)):
|
|
1268
|
+
errors[name] = error
|
|
1269
|
+
except ValueError as ex:
|
|
1270
|
+
if meta.strict is True:
|
|
1271
|
+
raise
|
|
1272
|
+
else:
|
|
1273
|
+
errors[name] = f"Wrong Value for {f.name}: {f.type}, error: {ex}"
|
|
1274
|
+
continue
|
|
1275
|
+
except (TypeError, RuntimeError) as ex:
|
|
1276
|
+
errors[name] = f"Wrong Type for {f.name}: {f.type}, error: {ex}"
|
|
1277
|
+
continue
|
|
1278
|
+
# Return Errors (if any)
|
|
1279
|
+
return errors
|
|
1280
|
+
|
|
1281
|
+
cdef dict _validation_(
|
|
1282
|
+
str name,
|
|
1283
|
+
object value,
|
|
1284
|
+
object f,
|
|
1285
|
+
object _type,
|
|
1286
|
+
object meta,
|
|
1287
|
+
str field_category,
|
|
1288
|
+
bint as_objects = False
|
|
1289
|
+
):
|
|
1290
|
+
"""
|
|
1291
|
+
_validation_.
|
|
1292
|
+
TODO: cover validations as length, not_null, required, max, min, etc
|
|
1293
|
+
"""
|
|
1294
|
+
cdef object val_type = type(value)
|
|
1295
|
+
if val_type == type or value == _type or is_empty(value):
|
|
1296
|
+
try:
|
|
1297
|
+
_field_checks_(f, name, value, meta)
|
|
1298
|
+
return {}
|
|
1299
|
+
except (ValueError, TypeError):
|
|
1300
|
+
raise
|
|
1301
|
+
# If the field has a cached validator, use it.
|
|
1302
|
+
if f.validator is not None:
|
|
1303
|
+
try:
|
|
1304
|
+
return f.validator(f, name, value, _type)
|
|
1305
|
+
except ValueError:
|
|
1306
|
+
raise
|
|
1307
|
+
else:
|
|
1308
|
+
# capturing other errors from validator:
|
|
1309
|
+
return _validation(f, name, value, _type, val_type, field_category, as_objects)
|
|
1310
|
+
|
|
1311
|
+
cdef object _field_checks_(object f, str name, object value, object meta):
|
|
1312
|
+
# Validate Primary Key
|
|
1313
|
+
cdef object metadata = f.metadata
|
|
1314
|
+
try:
|
|
1315
|
+
if metadata.get('primary', False) is True:
|
|
1316
|
+
if 'db_default' in metadata:
|
|
1317
|
+
pass
|
|
1318
|
+
else:
|
|
1319
|
+
raise ValueError(
|
|
1320
|
+
f":: Missing Primary Key *{name}*"
|
|
1321
|
+
)
|
|
1322
|
+
except KeyError:
|
|
1323
|
+
pass
|
|
1324
|
+
# Validate Required
|
|
1325
|
+
try:
|
|
1326
|
+
if metadata.get('required', False) is True and meta.strict is True:
|
|
1327
|
+
if 'db_default' in metadata:
|
|
1328
|
+
return
|
|
1329
|
+
if value is not None:
|
|
1330
|
+
return # If default value is set, no need to raise an error
|
|
1331
|
+
raise ValueError(
|
|
1332
|
+
f":: Missing Required Field *{name}*"
|
|
1333
|
+
)
|
|
1334
|
+
except ValueError:
|
|
1335
|
+
raise
|
|
1336
|
+
except KeyError:
|
|
1337
|
+
return
|
|
1338
|
+
# Nullable:
|
|
1339
|
+
try:
|
|
1340
|
+
if metadata.get('nullable', True) is False and meta.strict is True:
|
|
1341
|
+
raise ValueError(
|
|
1342
|
+
f":: *{name}* Cannot be null."
|
|
1343
|
+
)
|
|
1344
|
+
except ValueError:
|
|
1345
|
+
raise
|
|
1346
|
+
except KeyError:
|
|
1347
|
+
return
|
|
1348
|
+
return
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
cpdef parse_type(object field, object T, object data, object encoder = None):
|
|
1352
|
+
cdef object origin = get_origin(T)
|
|
1353
|
+
cdef tuple args = None
|
|
1354
|
+
cdef str type_name = getattr(T, '_name', None)
|
|
1355
|
+
cdef object type_args = getattr(T, '__args__', None)
|
|
1356
|
+
cdef dict typeinfo = getattr(T, '_typeinfo_', None)
|
|
1357
|
+
|
|
1358
|
+
if isinstance(T, NewType):
|
|
1359
|
+
# change type if is a NewType object.
|
|
1360
|
+
T = T.__supertype__
|
|
1361
|
+
|
|
1362
|
+
# Check if the data is already of the correct type
|
|
1363
|
+
if isinstance(data, T):
|
|
1364
|
+
return data
|
|
1365
|
+
|
|
1366
|
+
if field._type_category == 'typing':
|
|
1367
|
+
args = type_args or ()
|
|
1368
|
+
if type_name == 'Dict' and isinstance(data, dict):
|
|
1369
|
+
if args:
|
|
1370
|
+
return {k: parse_type(field, type_args[1], v) for k, v in data.items()}
|
|
1371
|
+
|
|
1372
|
+
elif type_name == 'List':
|
|
1373
|
+
if not isinstance(data, (list, tuple)):
|
|
1374
|
+
data = [data]
|
|
1375
|
+
arg_type = args[0]
|
|
1376
|
+
if arg_type.__module__ == 'typing': # nested typing
|
|
1377
|
+
try:
|
|
1378
|
+
t = arg_type.__args__[0]
|
|
1379
|
+
if is_dc(t):
|
|
1380
|
+
result = []
|
|
1381
|
+
for x in data:
|
|
1382
|
+
if isinstance(x, dict):
|
|
1383
|
+
result.append(t(**x))
|
|
1384
|
+
elif isinstance(x, (list, tuple)):
|
|
1385
|
+
result.append(t(*x))
|
|
1386
|
+
else:
|
|
1387
|
+
result.append(t())
|
|
1388
|
+
return result
|
|
1389
|
+
else:
|
|
1390
|
+
return data
|
|
1391
|
+
except AttributeError:
|
|
1392
|
+
return data # data -as is-
|
|
1393
|
+
elif is_dc(arg_type):
|
|
1394
|
+
if isinstance(data, list):
|
|
1395
|
+
result = []
|
|
1396
|
+
for d in data:
|
|
1397
|
+
# is already a dataclass:
|
|
1398
|
+
if is_dc(d):
|
|
1399
|
+
result.append(d)
|
|
1400
|
+
elif isinstance(d, list):
|
|
1401
|
+
result.append(arg_type(*d))
|
|
1402
|
+
elif isinstance(d, dict):
|
|
1403
|
+
result.append(arg_type(**d))
|
|
1404
|
+
else:
|
|
1405
|
+
result.append(arg_type(d))
|
|
1406
|
+
return result
|
|
1407
|
+
else:
|
|
1408
|
+
result = []
|
|
1409
|
+
if is_iterable(data):
|
|
1410
|
+
for item in data:
|
|
1411
|
+
# escalar value:
|
|
1412
|
+
converted_item = parse_type(field, arg_type, item, encoder)
|
|
1413
|
+
result.append(converted_item)
|
|
1414
|
+
return result
|
|
1415
|
+
return data
|
|
1416
|
+
elif type_name is None or type_name in ('Optional', 'Union'):
|
|
1417
|
+
args = get_args(T)
|
|
1418
|
+
# Handling Optional types
|
|
1419
|
+
if origin == Union and type(None) in args:
|
|
1420
|
+
if data is None:
|
|
1421
|
+
return None
|
|
1422
|
+
# Determine the non-None type.
|
|
1423
|
+
non_none_arg = args[0] if args[1] is type(None) else args[1]
|
|
1424
|
+
if non_none_arg == list:
|
|
1425
|
+
field.args = args
|
|
1426
|
+
field.origin = get_origin(non_none_arg)
|
|
1427
|
+
if isinstance(data, (list, str, dict)):
|
|
1428
|
+
return _parse_builtin_type(field, non_none_arg, data, encoder)
|
|
1429
|
+
else:
|
|
1430
|
+
raise ValueError(f"Unsupported type for List in Optional: {type(data)}")
|
|
1431
|
+
# If the non-None type is exactly dict, return the dict as is.
|
|
1432
|
+
if non_none_arg is dict:
|
|
1433
|
+
return data
|
|
1434
|
+
# Otherwise, recursively parse using the non-None type.
|
|
1435
|
+
field.args = args
|
|
1436
|
+
field.origin = get_origin(non_none_arg)
|
|
1437
|
+
return parse_type(field, non_none_arg, data, encoder)
|
|
1438
|
+
try:
|
|
1439
|
+
t = args[0]
|
|
1440
|
+
if is_dc(t):
|
|
1441
|
+
if isinstance(data, dict):
|
|
1442
|
+
data = t(**data)
|
|
1443
|
+
elif isinstance(data, (list, tuple)):
|
|
1444
|
+
data = t(*data)
|
|
1445
|
+
else:
|
|
1446
|
+
## is already a dataclass, returning
|
|
1447
|
+
return data
|
|
1448
|
+
elif callable(t):
|
|
1449
|
+
if t.__module__ == 'typing': # nested typing
|
|
1450
|
+
# there is also a nested typing:
|
|
1451
|
+
if t._name == 'List' and isinstance(data, list):
|
|
1452
|
+
arg = t.__args__[0]
|
|
1453
|
+
if is_dc(arg):
|
|
1454
|
+
result = []
|
|
1455
|
+
for x in data:
|
|
1456
|
+
if isinstance(x, dict):
|
|
1457
|
+
result.append(arg(**x))
|
|
1458
|
+
else:
|
|
1459
|
+
result.append(arg(*x))
|
|
1460
|
+
return result
|
|
1461
|
+
return data
|
|
1462
|
+
else:
|
|
1463
|
+
try:
|
|
1464
|
+
if t == str:
|
|
1465
|
+
return data
|
|
1466
|
+
fn = encoders[t]
|
|
1467
|
+
try:
|
|
1468
|
+
if data is not None:
|
|
1469
|
+
data = fn(data)
|
|
1470
|
+
except TypeError as ex:
|
|
1471
|
+
pass
|
|
1472
|
+
except (ValueError, RuntimeError) as exc:
|
|
1473
|
+
raise ValueError(
|
|
1474
|
+
f"Model: Error parsing {T}, {exc}"
|
|
1475
|
+
)
|
|
1476
|
+
except KeyError:
|
|
1477
|
+
pass
|
|
1478
|
+
return data
|
|
1479
|
+
except KeyError:
|
|
1480
|
+
pass
|
|
1481
|
+
elif origin is dict and isinstance(data, dict):
|
|
1482
|
+
return _parse_dict_type(field, T, data, encoder, args)
|
|
1483
|
+
elif origin is list:
|
|
1484
|
+
return _parse_list_type(field, T, data, encoder, args)
|
|
1485
|
+
elif origin is not None:
|
|
1486
|
+
# Other typing constructs can be handled here
|
|
1487
|
+
return data
|
|
1488
|
+
else:
|
|
1489
|
+
return _parse_builtin_type(field, T, data, encoder)
|