22# Copyright (C) 2018-present Jesus Lara
33#
44import 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
66from collections.abc import Sequence, Mapping, Callable, Awaitable
77import types
88from dataclasses import _MISSING_TYPE, _FIELDS, fields
@@ -45,6 +45,9 @@ cdef bint is_dc(object obj):
4545 cls = obj if isinstance (obj, type ) and not isinstance (obj, types.GenericAlias) else type (obj)
4646 return PyObject_HasAttr(cls , ' __dataclass_fields__' )
4747
48+ cdef bint is_typing(object obj):
49+ return PyObject_HasAttr(obj, ' __module__' ) and obj.__module__ == ' typing'
50+
4851cdef bint is_empty(object value):
4952 """
5053 Determines if a value should be considered empty.
@@ -555,6 +558,7 @@ cdef object _parse_set_type(
555558 cdef tuple key = (arg_type, field.name)
556559 cdef object converter = TYPE_PARSERS.get(key) or TYPE_PARSERS.get(arg_type)
557560 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
558562
559563 if data is None :
560564 return set () # short-circuit
@@ -579,6 +583,31 @@ cdef object _parse_set_type(
579583 else :
580584 result.add(inner_type(d))
581585 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
582611 elif converter:
583612 for item in data:
584613 result.add(
@@ -689,16 +718,42 @@ cdef object _parse_list_type(
689718 cdef object arg_type = args[0 ]
690719 cdef list result = []
691720 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
693722 cdef object inner_type = field._inner_type or arg_type
694723 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}")
695729
696730 if data is None :
697731 return [] # short-circuit
698732
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+
699750 if not isinstance (data, (list , tuple )):
700751 data = [data]
701752
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+
702757 # If it's a dataclass
703758 if is_dc(inner_type):
704759 for d in data:
@@ -721,6 +776,17 @@ cdef object _parse_list_type(
721776 f" Error creating {inner_type.__name__} from {d}: {e}"
722777 ) from e
723778 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+ )
724790 elif converter:
725791 for item in data:
726792 result.append(
@@ -754,6 +820,7 @@ cdef object _parse_list_type(
754820 # If no type is specified, return the list as-is
755821 return data
756822 else :
823+ # Default: process each item with _parse_typing
757824 for item in data:
758825 result.append(
759826 _parse_typing(field, T = inner_type, data = item, encoder = encoder, as_objects = False )
@@ -1244,6 +1311,13 @@ cdef object _parse_union_type(
12441311 cdef object non_none_arg = None
12451312 cdef tuple inner_targs = None
12461313 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
12471321
12481322 # If the union includes NoneType, unwrap it and use only the non-None type.
12491323 if origin == Union and type (None ) in targs:
@@ -1265,52 +1339,72 @@ cdef object _parse_union_type(
12651339 encoder,
12661340 False
12671341 )
1268- else :
1269- pass
12701342
12711343 # First check for dataclasses in the union
12721344 for arg_type in targs:
1345+ subtype_origin = field._inner_origin or get_origin(arg_type)
12731346 if is_dc(arg_type):
12741347 if isinstance (data, dict ):
12751348 try :
12761349 return arg_type(** data)
12771350 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}" )
12791352 continue
12801353 elif isinstance (data, arg_type):
12811354 return data
12821355 else :
12831356 # For string inputs, don't accept them as dataclasses
12841357 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__}" )
12861359 continue
12871360 try :
12881361 return arg_type(data)
12891362 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}" )
12911364 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 :
13021371 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
13041377 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__}" )
13061379 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 ):
13081400 return _handle_list_of_dataclasses(field, field_name, data, T, None )
13091401 elif subtype_origin is dict :
13101402 if isinstance (data, dict ):
13111403 return _parse_dict_type(field, arg_type, data, encoder, targs)
13121404 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+ )
13141408 continue
13151409 elif arg_type is list :
13161410 if isinstance (data, list ):
@@ -1321,43 +1415,60 @@ cdef object _parse_union_type(
13211415 else :
13221416 return _parse_list_type(field, arg_type, data, encoder, targs)
13231417 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+ )
13251421 continue
13261422 elif arg_type is dict :
13271423 if isinstance (data, dict ):
13281424 return _parse_dict_type(field, arg_type, data, encoder, targs)
13291425 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+ )
13311429 continue
13321430 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}" )
13421446 continue
13431447 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
13561463
13571464 # 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+ )
13611472
13621473cdef object _parse_type(
13631474 object field,
0 commit comments