@@ -46,6 +46,8 @@ class User:
4646 Any ,
4747 Callable ,
4848 Dict ,
49+ Generic ,
50+ Hashable ,
4951 List ,
5052 Mapping ,
5153 NewType as typing_NewType ,
@@ -367,6 +369,43 @@ def _dataclass_fields(clazz: type) -> Tuple[dataclasses.Field, ...]:
367369 return dataclasses .fields (clazz )
368370
369371
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+
370409@lru_cache (maxsize = MAX_CLASS_SCHEMA_CACHE_SIZE )
371410def _internal_class_schema (
372411 clazz : type ,
@@ -377,7 +416,8 @@ def _internal_class_schema(
377416 # generic aliases do not have a __name__ prior python 3.10
378417 _name = getattr (clazz , "__name__" , repr (clazz ))
379418
380- _RECURSION_GUARD .seen_classes [clazz ] = _name
419+ future : _Future [Type [marshmallow .Schema ]] = _Future ()
420+ _RECURSION_GUARD .seen_classes [clazz ] = future
381421 try :
382422 fields = _dataclass_fields (clazz )
383423 except TypeError : # Not a dataclass
@@ -430,8 +470,11 @@ def _internal_class_schema(
430470 if field .init
431471 )
432472
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
435478
436479
437480def _field_by_type (
@@ -769,17 +812,18 @@ def field_for_schema(
769812
770813 # Nested marshmallow dataclass
771814 # 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+ )
783827
784828 return marshmallow .fields .Nested (nested , ** metadata )
785829
0 commit comments