Skip to content
Jeff Dairiki edited this page Sep 26, 2022 · 18 revisions

Resolving Type References in marshmallow_dataclass

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).

Clone this wiki locally