pythontypeddict

Nested TypeDict defined as inner class


I'm trying to define a "nested" TypedDict as a set of inner classes, where SomeDict dictionary is supposed to have an id field which can be either yet another TypedDict or None:

from typing import TypedDict

class MyClass:

    class SomeIdFieldDict(TypedDict):
        some_nested_field: str

    class SomeDict(TypedDict):
        id: SomeIdFieldDict | None # The error is in this line.

The above code gives me the following error: NameError: name 'SomeIdFieldDict' is not defined

I've tried to quote the SomeIdFieldDict, but now Python type checker treats it as a string:

from typing import TypedDict

class MyClass:

    class SomeIdFieldDict(TypedDict):
        some_nested_field: str

    class SomeDict(TypedDict):
        id: "SomeIdFieldDict" | None # The error is in this line.

With the above, I'm getting:

TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'

I've also tried referencing to the top-level class, to no avail (getting the same error as above):

from typing import TypedDict

class MyClass:

    class SomeIdFieldDict(TypedDict):
        some_nested_field: str

    class SomeDict(TypedDict):
        id: "MyClass.SomeIdFieldDict" | None # The error is in this line.

Yet another approach I've tried taking was to define the id type "inline", like so:

from typing import TypedDict

class MyClass:

    class SomeDict(TypedDict):
        id: TypedDict("SomeIdFieldDict", {"some_nested_field": str}) | None

... but it seems it isn't parsed correctly and the field is being treated as Any, the type hints for id field show as: id: Any | None

Is there any way to be able to define this sort of "nested" TypeDict as inner classes?


Solution

  • You can either use typing.Optional["SomeIdFieldDict"] instead or, what I would suggest, use __future__.annotations like this:

    from __future__ import annotations
    from typing import TypedDict
    
    
    class MyClass:
        class SomeIdFieldDict(TypedDict):
            some_nested_field: str
    
        class SomeDict(TypedDict):
            id: SomeIdFieldDict | None
    
    
    def f(x: MyClass.SomeDict) -> None:
        print(x)
    
    
    if __name__ == '__main__':
        f({"id": {"some_nested_field": "a"}})  # works
        f({"foo": "bar"})  # error: Extra key "foo" for TypedDict "SomeDict"
    

    This will pass/fail with mypy as expected. However, I noticed that PyCharm mistakenly still complains about Unresolved reference 'SomeIdFieldDict'. But that is a bug on their end.