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.
Files changed (78) hide show
  1. datamodel/__init__.py +13 -0
  2. datamodel/abstract.py +383 -0
  3. datamodel/adaptive/__init__.py +0 -0
  4. datamodel/adaptive/models.py +598 -0
  5. datamodel/aliases/__init__.py +26 -0
  6. datamodel/base.py +180 -0
  7. datamodel/converters.c +43471 -0
  8. datamodel/converters.cp313-win32.pyd +0 -0
  9. datamodel/converters.html +17387 -0
  10. datamodel/converters.pyx +1489 -0
  11. datamodel/exceptions.c +13455 -0
  12. datamodel/exceptions.cp313-win32.pyd +0 -0
  13. datamodel/exceptions.html +1261 -0
  14. datamodel/exceptions.pxd +13 -0
  15. datamodel/exceptions.pyx +50 -0
  16. datamodel/fields.cp313-win32.pyd +0 -0
  17. datamodel/fields.cpp +17401 -0
  18. datamodel/fields.html +3912 -0
  19. datamodel/fields.pyx +309 -0
  20. datamodel/functions.cp313-win32.pyd +0 -0
  21. datamodel/functions.cpp +9068 -0
  22. datamodel/functions.html +1766 -0
  23. datamodel/functions.pxd +9 -0
  24. datamodel/functions.pyx +82 -0
  25. datamodel/jsonld/__init__.py +45 -0
  26. datamodel/jsonld/models.py +500 -0
  27. datamodel/libs/__init__.py +1 -0
  28. datamodel/libs/mapping.c +15067 -0
  29. datamodel/libs/mapping.cp313-win32.pyd +0 -0
  30. datamodel/libs/mapping.html +2618 -0
  31. datamodel/libs/mapping.pxd +11 -0
  32. datamodel/libs/mapping.pyx +135 -0
  33. datamodel/libs/mutables.py +127 -0
  34. datamodel/models.py +814 -0
  35. datamodel/parsers/__init__.py +0 -0
  36. datamodel/parsers/encoders.py +15 -0
  37. datamodel/parsers/json.cp313-win32.pyd +0 -0
  38. datamodel/parsers/json.cpp +17004 -0
  39. datamodel/parsers/json.html +3365 -0
  40. datamodel/parsers/json.pyx +250 -0
  41. datamodel/profiler.py +21 -0
  42. datamodel/py.typed +0 -0
  43. datamodel/rs_core/Cargo.toml +17 -0
  44. datamodel/rs_core/src/lib.rs +294 -0
  45. datamodel/rs_parsers/Cargo.toml +22 -0
  46. datamodel/rs_parsers/src/lib.rs +571 -0
  47. datamodel/rs_parsers.cp313-win32.pyd +0 -0
  48. datamodel/rs_validators/Cargo.toml +17 -0
  49. datamodel/rs_validators/src/lib.rs +0 -0
  50. datamodel/typedefs/__init__.py +9 -0
  51. datamodel/typedefs/singleton.c +9169 -0
  52. datamodel/typedefs/singleton.cp313-win32.pyd +0 -0
  53. datamodel/typedefs/singleton.html +629 -0
  54. datamodel/typedefs/singleton.pxd +9 -0
  55. datamodel/typedefs/singleton.pyx +24 -0
  56. datamodel/typedefs/types.c +11716 -0
  57. datamodel/typedefs/types.cp313-win32.pyd +0 -0
  58. datamodel/typedefs/types.html +732 -0
  59. datamodel/typedefs/types.pxd +11 -0
  60. datamodel/typedefs/types.pyx +39 -0
  61. datamodel/types.c +7165 -0
  62. datamodel/types.cp313-win32.pyd +0 -0
  63. datamodel/types.html +716 -0
  64. datamodel/types.pyx +100 -0
  65. datamodel/validation.cp313-win32.pyd +0 -0
  66. datamodel/validation.cpp +17085 -0
  67. datamodel/validation.html +4769 -0
  68. datamodel/validation.pyx +315 -0
  69. datamodel/version.py +13 -0
  70. examples/nn/examples.py +311 -0
  71. examples/nn/stores.py +151 -0
  72. examples/tests/sp_types.py +294 -0
  73. examples/tests/speed_dates.py +26 -0
  74. python_datamodel-0.10.1.dist-info/LICENSE +29 -0
  75. python_datamodel-0.10.1.dist-info/METADATA +320 -0
  76. python_datamodel-0.10.1.dist-info/RECORD +78 -0
  77. python_datamodel-0.10.1.dist-info/WHEEL +5 -0
  78. python_datamodel-0.10.1.dist-info/top_level.txt +7 -0
@@ -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)