14

I have a bunch of @dataclasses and a bunch of corresponding TypedDicts, and I want to facilitate smooth and type-checked conversion between them.

For example, consider

from dataclasses import dataclass
from typing_extensions import TypedDict

@dataclass
class Foo:
    bar: int
    baz: int
   
    @property
    def qux(self) -> int:
        return self.bar + self.baz

class SerializedFoo(TypedDict):
    bar: int
    baz: int
    qux: int

To create a serialization function, I can write something like

def serialize(foo: Foo) -> SerializedFoo:
    return SerializedFoo(
        bar=foo.bar,
        baz=foo.baz,
        qux=foo.qux,
    )

but doing this for many types becomes tedious, and every time I update the types, I also have to update the serialization function.

I can also do something like

import dataclasses

def serialize(foo: Foo) -> SerializedFoo:
    return SerializedFoo(**dataclasses.asdict(foo))

but this doesn't type check; mypy complains that it Expected keyword arguments, {...}, or dict(...) in TypedDict constructor.

Theoretically, it should be possible for a sufficiently smart type-checker to know that the dataclass has the properties needed to initialize the typed dictionary, but of course the usage of asdict makes this impossible in practice.

Is there a better way to convert a dataclass to a TypedDict with corresponding fields, that lets me both have the type checker tell me when something is wrong, and not have to type out every field in the conversion?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Tomas Aschan
  • 58,548
  • 56
  • 243
  • 402

1 Answers1

5

TypedDict is a regular dict at runtime, not a real class, doesn't do any type-checking, and is only for type hinting purposes. So, you can simply use typing.cast (docs here):

import dataclasses
from typing import cast

def serialize(foo: Foo) -> SerializedFoo:
    return cast(SerializedFoo, dataclasses.asdict(foo))

which will make Python type-checkers happy.

You could also check out the dacite library. It has some nifty things for stuff like this without the overkill of marshmallow. It allows some kind of type casting in its asdict/from_dict functions.

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Jeremyfx
  • 51
  • 1
  • 4
  • Yep, that will make most python type checkers happy and is how I do it myself in my code. I do not directly use mypy though, so I cannot speak directly to that part. – Jeremyfx Sep 09 '21 at 08:52
  • 1
    The link I shared in my comment above is to a site that runs mypy on a server, so you can try out the solution there and you'll see that MyPy is perfectly happy with this :) – Alex Waygood Sep 09 '21 at 09:06