-
-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Forward type references are used to refer to types that have not yet been defined. They are implemented as strings which must be evaluated later to compute the actual type declaration.
Whenever anything is evaled, it gets done within the context of a specific pair of global and local namespaces.
When computing a schema for a dataclass, marshmallow_dataclass needs access to the final resolved type hints for the dataclass' attributes.
It’s basically easy: we call [typing.get_type_hints][get_type_hints] to compute those type hints.
The only tricky part is that for type references to be properly resolved we often need to provide the correct local namespace to get_type_hints for it to use when resolving those references.
Currently, our class_schema defaults to using the caller's locals. Often that's the right thing. E.g. it does allow for correctly resolving the attribute types in
def f():
@dataclasses.dataclass
class A:
b: "B"
@dataclasses.dataclass
class B:
x: int
MySchema = marshmallow.class_schema(A)But using the caller's locals is just a guess — it doesn't always work. Consider this case:
def f() -> None:
@dataclasses.dataclass
class A:
b: "B"
@dataclasses.dataclass
class B:
a: "A"
def g():
MySchema = marshmallow_dataclass.class_schema(A)
print(locals())
g()This currently doesn't work ("NameError: name 'B' is not defined"). We need the locals from f to resolve the type references, but class_schema uses the locals from g.
@dataclasses.dataclass
class A:
b: "B"
@dataclasses.dataclass
class B:
x: int
def f() -> None:
@dataclasses.dataclass
class B:
y: str
MySchema = marshmallow.class_schema(A)In this case, A.b should is an instance of the module-level B (the one with an x attribute). However, class_schema currently passes f’s globals to get_type_hints, which results in A.b being treated as an instance of the local f.<locals>.B (the one without an x attribute).