@@ -46,6 +46,8 @@ class User:
46
46
Any ,
47
47
Callable ,
48
48
Dict ,
49
+ Generic ,
50
+ Hashable ,
49
51
List ,
50
52
Mapping ,
51
53
NewType as typing_NewType ,
@@ -367,6 +369,43 @@ def _dataclass_fields(clazz: type) -> Tuple[dataclasses.Field, ...]:
367
369
return dataclasses .fields (clazz )
368
370
369
371
372
+ class InvalidStateError (Exception ):
373
+ """Raised when an operation is performed on a future that is not
374
+ allowed in the current state.
375
+ """
376
+
377
+
378
+ class _Future (Generic [_U ]):
379
+ """The _Future class allows deferred access to a result that is not
380
+ yet available.
381
+ """
382
+
383
+ _done : bool
384
+ _result : _U
385
+
386
+ def __init__ (self ) -> None :
387
+ self ._done = False
388
+
389
+ def done (self ) -> bool :
390
+ """Return ``True`` if the value is available"""
391
+ return self ._done
392
+
393
+ def result (self ) -> _U :
394
+ """Return the deferred value.
395
+
396
+ Raises ``InvalidStateError`` if the value has not been set.
397
+ """
398
+ if self .done ():
399
+ return self ._result
400
+ raise InvalidStateError ("result has not been set" )
401
+
402
+ def set_result (self , result : _U ) -> None :
403
+ if self .done ():
404
+ raise InvalidStateError ("result has already been set" )
405
+ self ._result = result
406
+ self ._done = True
407
+
408
+
370
409
@lru_cache (maxsize = MAX_CLASS_SCHEMA_CACHE_SIZE )
371
410
def _internal_class_schema (
372
411
clazz : type ,
@@ -377,7 +416,8 @@ def _internal_class_schema(
377
416
# generic aliases do not have a __name__ prior python 3.10
378
417
_name = getattr (clazz , "__name__" , repr (clazz ))
379
418
380
- _RECURSION_GUARD .seen_classes [clazz ] = _name
419
+ future : _Future [Type [marshmallow .Schema ]] = _Future ()
420
+ _RECURSION_GUARD .seen_classes [clazz ] = future
381
421
try :
382
422
fields = _dataclass_fields (clazz )
383
423
except TypeError : # Not a dataclass
@@ -430,8 +470,11 @@ def _internal_class_schema(
430
470
if field .init
431
471
)
432
472
433
- schema_class = type (_name , (_base_schema (clazz , base_schema ),), attributes )
434
- return cast (Type [marshmallow .Schema ], schema_class )
473
+ schema_class : Type [marshmallow .Schema ] = type (
474
+ _name , (_base_schema (clazz , base_schema ),), attributes
475
+ )
476
+ future .set_result (schema_class )
477
+ return schema_class
435
478
436
479
437
480
def _field_by_type (
@@ -769,17 +812,18 @@ def field_for_schema(
769
812
770
813
# Nested marshmallow dataclass
771
814
# it would be just a class name instead of actual schema util the schema is not ready yet
772
- nested_schema = getattr (typ , "Schema" , None )
773
-
774
- # Nested dataclasses
775
- forward_reference = getattr (typ , "__forward_arg__" , None )
776
-
777
- nested = (
778
- nested_schema
779
- or forward_reference
780
- or _RECURSION_GUARD .seen_classes .get (typ )
781
- or _internal_class_schema (typ , base_schema , typ_frame , generic_params_to_args )
782
- )
815
+ if typ in _RECURSION_GUARD .seen_classes :
816
+ nested = _RECURSION_GUARD .seen_classes [typ ].result
817
+ elif hasattr (typ , "Schema" ):
818
+ nested = typ .Schema
819
+ elif hasattr (typ , "__forward_arg__" ):
820
+ # FIXME: is this still used?
821
+ nested = typ .__forward_arg__
822
+ else :
823
+ assert isinstance (typ , Hashable )
824
+ nested = _internal_class_schema (
825
+ typ , base_schema , typ_frame , generic_params_to_args
826
+ )
783
827
784
828
return marshmallow .fields .Nested (nested , ** metadata )
785
829
0 commit comments