2
2
# Copyright (C) 2018-present Jesus Lara
3
3
#
4
4
import re
5
- from typing import get_args, get_origin, Union, Optional, List, NewType, Literal, Any
5
+ from typing import get_args, get_origin, Union, Optional, List, NewType, Literal, Any, Set
6
6
from collections.abc import Sequence, Mapping, Callable, Awaitable
7
7
import types
8
8
from dataclasses import _MISSING_TYPE, _FIELDS, fields
@@ -45,6 +45,9 @@ cdef bint is_dc(object obj):
45
45
cls = obj if isinstance (obj, type ) and not isinstance (obj, types.GenericAlias) else type (obj)
46
46
return PyObject_HasAttr(cls , ' __dataclass_fields__' )
47
47
48
+ cdef bint is_typing(object obj):
49
+ return PyObject_HasAttr(obj, ' __module__' ) and obj.__module__ == ' typing'
50
+
48
51
cdef bint is_empty(object value):
49
52
"""
50
53
Determines if a value should be considered empty.
@@ -555,6 +558,7 @@ cdef object _parse_set_type(
555
558
cdef tuple key = (arg_type, field.name)
556
559
cdef object converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(arg_type)
557
560
cdef object inner_type = field._inner_type if hasattr (field, ' _inner_type' ) else arg_type
561
+ cdef bint is_typing_set = hasattr (inner_type, ' __origin__' ) and inner_type.__origin__ is set
558
562
559
563
if data is None :
560
564
return set () # short-circuit
@@ -579,6 +583,31 @@ cdef object _parse_set_type(
579
583
else :
580
584
result.add(inner_type(d))
581
585
return result
586
+ elif is_typing_set:
587
+ # If we're dealing with typing.Set[str] or similar
588
+ inner_element_type = get_args(inner_type)[0 ] if get_args(inner_type) else Any
589
+ # If the inner type is a set, we need to process it differently
590
+ try :
591
+ for item in data:
592
+ if isinstance (item, str ):
593
+ # String items are individual elements
594
+ result.add(item)
595
+ elif isinstance (item, (list , tuple , set )):
596
+ # Process each element in collections
597
+ for element in item:
598
+ # Convert the element to the inner type if needed
599
+ if inner_element_type in encoders and not isinstance (element, inner_element_type):
600
+ converted = encoders[inner_element_type](element)
601
+ result.add(converted)
602
+ else :
603
+ result.add(element)
604
+ else :
605
+ # Single non-string item
606
+ result.add(item)
607
+ except Exception as e:
608
+ raise ValueError (
609
+ f" Error parsing set item of {inner_type}: {e}"
610
+ ) from e
582
611
elif converter:
583
612
for item in data:
584
613
result.add(
@@ -689,16 +718,42 @@ cdef object _parse_list_type(
689
718
cdef object arg_type = args[0 ]
690
719
cdef list result = []
691
720
cdef tuple key = (arg_type, field.name)
692
- cdef object converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(arg_type)
721
+ cdef object converter
693
722
cdef object inner_type = field._inner_type or arg_type
694
723
cdef bint is_optional = False
724
+ cdef object origin = field._inner_origin or get_origin(T)
725
+ cdef tuple type_args = field._typing_args or get_args(T)
726
+
727
+ # Debug information if needed
728
+ # print(f"_parse_list_type: field={field.name}, T={T}, data={data}, args={args}")
695
729
696
730
if data is None :
697
731
return [] # short-circuit
698
732
733
+ # Compute the Inner Type:
734
+ if hasattr (field, ' _inner_type' ) and field._inner_type is not None :
735
+ inner_type = field._inner_type
736
+ # Then try to get it from the type's args
737
+ elif type_args and len (type_args) > 0 :
738
+ inner_type = type_args[0 ]
739
+ # Finally try to get it from args parameter
740
+ elif args and isinstance (args, (list , tuple )) and len (args) > 0 :
741
+ if hasattr (args[0 ], ' __origin__' ) and args[0 ].__origin__ is list and hasattr (args[0 ], ' __args__' ):
742
+ # This is typing.List[T]
743
+ arg_type = args[0 ]
744
+ inner_type = arg_type.__args__[0 ] if arg_type.__args__ else Any
745
+ else :
746
+ inner_type = args[0 ]
747
+ else :
748
+ inner_type = Any
749
+
699
750
if not isinstance (data, (list , tuple )):
700
751
data = [data]
701
752
753
+ # Get the converter if available
754
+ key = (inner_type, field.name)
755
+ converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(arg_type)
756
+
702
757
# If it's a dataclass
703
758
if is_dc(inner_type):
704
759
for d in data:
@@ -721,6 +776,17 @@ cdef object _parse_list_type(
721
776
f" Error creating {inner_type.__name__} from {d}: {e}"
722
777
) from e
723
778
return result
779
+ elif is_typing(inner_type):
780
+ if isinstance (data, list ):
781
+ result = _parse_list_typing(
782
+ field,
783
+ type_args,
784
+ data,
785
+ encoder,
786
+ origin,
787
+ args,
788
+ None
789
+ )
724
790
elif converter:
725
791
for item in data:
726
792
result.append(
@@ -754,6 +820,7 @@ cdef object _parse_list_type(
754
820
# If no type is specified, return the list as-is
755
821
return data
756
822
else :
823
+ # Default: process each item with _parse_typing
757
824
for item in data:
758
825
result.append(
759
826
_parse_typing(field, T = inner_type, data = item, encoder = encoder, as_objects = False )
@@ -1244,6 +1311,13 @@ cdef object _parse_union_type(
1244
1311
cdef object non_none_arg = None
1245
1312
cdef tuple inner_targs = None
1246
1313
cdef bint is_typing = False
1314
+ cdef bint has_list_type = False
1315
+ cdef list errors = [] # Collect all errors to report if all types fail
1316
+
1317
+
1318
+ # First, check for None in Optional types
1319
+ if type (None ) in targs and data is None :
1320
+ return None
1247
1321
1248
1322
# If the union includes NoneType, unwrap it and use only the non-None type.
1249
1323
if origin == Union and type (None ) in targs:
@@ -1265,52 +1339,72 @@ cdef object _parse_union_type(
1265
1339
encoder,
1266
1340
False
1267
1341
)
1268
- else :
1269
- pass
1270
1342
1271
1343
# First check for dataclasses in the union
1272
1344
for arg_type in targs:
1345
+ subtype_origin = field._inner_origin or get_origin(arg_type)
1273
1346
if is_dc(arg_type):
1274
1347
if isinstance (data, dict ):
1275
1348
try :
1276
1349
return arg_type(** data)
1277
1350
except Exception as exc:
1278
- error = f" Failed to create dataclass {arg_type.__name__} from dict: {exc}"
1351
+ errors.append( f" Failed to create dataclass {arg_type.__name__} from dict: {exc}" )
1279
1352
continue
1280
1353
elif isinstance (data, arg_type):
1281
1354
return data
1282
1355
else :
1283
1356
# For string inputs, don't accept them as dataclasses
1284
1357
if isinstance (data, (str , int , float , bool )):
1285
- error = f" Cannot convert {type(data).__name__} to {arg_type.__name__}"
1358
+ errors.append( f" Cannot convert {type(data).__name__} to {arg_type.__name__}" )
1286
1359
continue
1287
1360
try :
1288
1361
return arg_type(data)
1289
1362
except Exception as exc:
1290
- error = f" Failed to create {arg_type.__name__} from {type(data).__name__}: {exc}"
1363
+ errors.append( f" Failed to create {arg_type.__name__} from {type(data).__name__}: {exc}" )
1291
1364
continue
1292
- if error:
1293
- raise ValueError (
1294
- f" Invalid type for {field.name} with data={data}, error = {error}"
1295
- )
1296
-
1297
- for arg_type in targs:
1298
- # Iterate over all subtypes of Union:
1299
- subtype_origin = get_origin(arg_type)
1300
- try :
1301
- if subtype_origin is list or subtype_origin is tuple :
1365
+ else :
1366
+ if is_primitive(arg_type):
1367
+ if isinstance (data, arg_type):
1368
+ return data
1369
+ continue
1370
+ elif subtype_origin is list or subtype_origin is tuple :
1302
1371
if isinstance (data, list ):
1303
- return _parse_list_type(field, arg_type, data, encoder, targs)
1372
+ try :
1373
+ return _parse_list_type(field, arg_type, data, encoder, targs)
1374
+ except ValueError as exc:
1375
+ errors.append(str (exc))
1376
+ continue
1304
1377
else :
1305
- error = f" Invalid type for {field_name}: Expected a list, got {type(data).__name__}"
1378
+ errors.append( f" Invalid type for {field_name}: Expected a list, got {type(data).__name__}" )
1306
1379
continue
1307
- if subtype_origin is list and field._inner_is_dc == True and isinstance (data, list ):
1380
+ elif subtype_origin is set :
1381
+ if isinstance (data, (list , tuple )):
1382
+ return set (data)
1383
+ elif isinstance (data, set ):
1384
+ return data
1385
+ else :
1386
+ try :
1387
+ return _parse_set_type(field, T, data, encoder, targs, None )
1388
+ except Exception as exc:
1389
+ errors.append(f" Invalid type for {field_name}: Expected a set, got {type(data).__name__}" )
1390
+ continue
1391
+ elif subtype_origin is frozenset :
1392
+ if isinstance (data, (list , tuple )):
1393
+ return frozenset (data)
1394
+ elif isinstance (data, frozenset ):
1395
+ return data
1396
+ else :
1397
+ errors.append(f" Invalid type for {field_name}: Expected a frozenset, got {type(data).__name__}" )
1398
+ continue
1399
+ elif subtype_origin is list and field._inner_is_dc == True and isinstance (data, list ):
1308
1400
return _handle_list_of_dataclasses(field, field_name, data, T, None )
1309
1401
elif subtype_origin is dict :
1310
1402
if isinstance (data, dict ):
1311
1403
return _parse_dict_type(field, arg_type, data, encoder, targs)
1312
1404
else :
1313
- error = f" Invalid type for {field_name} Expected a dict, got {type(data).__name__}"
1405
+ errors.append(
1406
+ f" Invalid type for {field_name} Expected a dict, got {type(data).__name__}"
1407
+ )
1314
1408
continue
1315
1409
elif arg_type is list :
1316
1410
if isinstance (data, list ):
@@ -1321,43 +1415,60 @@ cdef object _parse_union_type(
1321
1415
else :
1322
1416
return _parse_list_type(field, arg_type, data, encoder, targs)
1323
1417
else :
1324
- error = f" Invalid type for {field_name}: Expected a list, got {type(data).__name__}"
1418
+ errors.append(
1419
+ f" Invalid type for {field_name}: Expected a list, got {type(data).__name__}"
1420
+ )
1325
1421
continue
1326
1422
elif arg_type is dict :
1327
1423
if isinstance (data, dict ):
1328
1424
return _parse_dict_type(field, arg_type, data, encoder, targs)
1329
1425
else :
1330
- error = f" Invalid type for {field_name} Expected a dict, got {type(data).__name__}"
1426
+ errors.append(
1427
+ f" Invalid type for {field_name} Expected a dict, got {type(data).__name__}"
1428
+ )
1331
1429
continue
1332
1430
elif subtype_origin is None :
1333
- if is_dc(arg_type):
1334
- return _handle_dataclass_type(field, name, data, arg_type, False , None )
1335
- elif arg_type in encoders:
1336
- return _parse_builtin_type(field, arg_type, data, encoder)
1337
- elif isinstance (data, arg_type):
1338
- return data
1339
- else :
1340
- # Not matching => record an error
1341
- error = f" Invalid type for {field_name}, Data {data!r} is not an instance of {arg_type}"
1431
+ try :
1432
+ if is_dc(arg_type):
1433
+ return _handle_dataclass_type(field, name, data, arg_type, False , None )
1434
+ elif arg_type in encoders:
1435
+ return _parse_builtin_type(field, arg_type, data, encoder)
1436
+ elif isinstance (data, arg_type):
1437
+ return data
1438
+ else :
1439
+ # Not matching => record an error
1440
+ errors.append(
1441
+ f" Invalid type for {field_name}, Data {data!r} is not an instance of {arg_type}"
1442
+ )
1443
+ continue
1444
+ except ValueError as exc:
1445
+ errors.append(f" {field.name}: {exc}" )
1342
1446
continue
1343
1447
else :
1344
- # fallback to builtin parse
1345
- return _parse_typing(
1346
- field,
1347
- arg_type,
1348
- data,
1349
- encoder,
1350
- False
1351
- )
1352
- except ValueError as exc:
1353
- error = f" {field.name}: {exc}"
1354
- except Exception as exc:
1355
- error = f" Parse Error on {field.name}, {arg_type}: {exc}"
1448
+ try :
1449
+ # fallback to builtin parse
1450
+ return _parse_typing(
1451
+ field,
1452
+ arg_type,
1453
+ data,
1454
+ encoder,
1455
+ False
1456
+ )
1457
+ except ValueError as exc:
1458
+ errors.append(f" {field.name}: {exc}" )
1459
+ continue
1460
+ except Exception as exc:
1461
+ errors.append(f" Parse Error on {field.name}, {arg_type}: {exc}" )
1462
+ continue
1356
1463
1357
1464
# If we get here, all union attempts failed
1358
- raise ValueError (
1359
- f" Invalid type for {field.name} with data={data}, error = {error}"
1360
- )
1465
+ if errors:
1466
+ error_msg = f" All Union types failed for {field_name}. Errors: " + " ; " .join(errors)
1467
+ raise ValueError (error_msg)
1468
+ else :
1469
+ raise ValueError (
1470
+ f" Invalid type for {field_name} with data={data}, no matching type found"
1471
+ )
1361
1472
1362
1473
cdef object _parse_type(
1363
1474
object field,
0 commit comments