Skip to content

Commit 024375f

Browse files
committed
Fix bug with repeated use of the sane generic dataclass
See lovasoa#172 (comment)
1 parent de27c32 commit 024375f

File tree

2 files changed

+59
-15
lines changed

2 files changed

+59
-15
lines changed

marshmallow_dataclass/__init__.py

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
371410
def _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

437480
def _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

tests/test_class_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,11 @@ class BB(typing.Generic[T]):
462462

463463
@dataclasses.dataclass
464464
class Nested:
465+
y: BB[AA]
465466
x: BB[float]
466467
z: BB[float]
467468
# if y is the first field in this class, deserialisation will fail.
468469
# see https://github.com/lovasoa/marshmallow_dataclass/pull/172#issuecomment-1334024027
469-
y: BB[AA]
470470

471471
schema_nested = class_schema(Nested)()
472472
self.assertEqual(

0 commit comments

Comments
 (0)