Python: Sharing type annotations between Protocol and TypedDict

Viewed 925

Take this simple example:

from __future__ import annotations
import typing as t


class MyType:
    def __init__(self, s: str, i: int) -> None:
        self.s = s
        self.i = i


class MyProto(t.Protocol):
    s: str
    i: int


class MyDict(t.TypedDict):
    s: str
    i: int


def my_serializer(inst: MyProto) -> MyDict:
    return {"s": inst.s, "i": inst.i}


d = my_serializer(MyType("a", 1))

All type checks pass.

Now lets say that MyType is actually an ORM class with many attributes that is the source of truth for the typing of the protocol and the dict. It feels a little redundant having to maintain identical annotations in the Protocol class body, and the TypedDict class body each time an attribute is added to the class.

I'd like to know if there is a way that I can centrally define the type annotations and tell mypy that these are the type definitions for both the protocol and the dict class.

I tried this:

class TypeMixin:
    s: str
    i: int


class MyProto(TypeMixin, t.Protocol):
    pass


class MyDict(TypeMixin, t.TypedDict):
    pass

However, mypy complains:

test.py:15: error: All bases of a protocol must be protocols
test.py:19: error: All bases of a new TypedDict must be TypedDict types

... and this one is actually a TypeError at run time.

And this:

annos = {"s": "str", "i": "int"}
MyProto = type("MyProto", (t.Protocol,), {"__annotations__": annos})
MyDict = type("MyDict", (t.TypedDict,), {"__annotations__": annos})


def my_serializer(inst: MyProto) -> MyDict:
    return {"s": inst.s, "i": inst.i}

This runs, but mypy complains, and I assume it's all a bit too dynamic for mypy anyway:

test.py:12: error: Argument 2 to "type" has incompatible type "Tuple[_SpecialForm]"; expected "Tuple[type, ...]"
test.py:13: error: Argument 2 to "type" has incompatible type "Tuple[object]"; expected "Tuple[type, ...]"
test.py:16: error: Variable "topsport.events.test.MyProto" is not valid as a type
test.py:16: error: Variable "topsport.events.test.MyDict" is not valid as a type
test.py:17: error: MyProto? has no attribute "s"
test.py:17: error: MyProto? has no attribute "i"

Is what I'm trying to do impossible?

0 Answers
Related