I'm trying to remove the Any type hint from code similar to the following:
from typing import TypedDict, Any
class NestedDict(TypedDict):
foo: str
class EventDict(TypedDict):
nested: NestedDict
class BaseEventDict(TypedDict):
nested: Any # this should accept NestedDict but also other TypedDicts which may contain additional fields
test_dict: EventDict = {
"nested": {"foo": "abc"},
}
def print_dict(source_dict: BaseEventDict):
print(source_dict)
print_dict(test_dict)
Since the nested field can contain either NestedDict or other TypedDicts with additional fields (for other EventDicts), I've not been able to come up with a compatible TypedDict (mypy complains about extra keys). I thought Mapping[str, object] might work in Any's place, since [A]ny TypedDict type is consistent with Mapping[str, object]. However, mypy complains with Argument 1 to "print_dict" has incompatible type "EventDict"; expected "BaseDict". Is there anything I can use instead of Any, which essentially disables the check? Also, any insights into why Mapping[str, object] is not a valid type here?
TypedDict fields are invariant, because TypedDict is a mutable structure. The reasoning behind that is explained in PEP589 in detail. So, to accept a TypedDict with a field of type "some TypedDict or anything compatible with it" you can use a generic solution:
from __future__ import annotations
from typing import TypedDict, Generic, TypeVar
class NestedDict(TypedDict):
foo: str
_T = TypeVar('_T', bound=NestedDict)
class BaseEventDict(Generic[_T], TypedDict):
nested: _T # this should accept NestedDict but also other TypedDicts which may contain additional fields
BaseEventDict is parametrized with a type of its field, which is bound to NestedDict - this way T can be substituted only with something compatible with NestedDict. Let's check:
class GoodNestedDict(TypedDict):
foo: str
bar: str
class BadNestedDict(TypedDict):
foo: int
class EventDict(TypedDict):
nested: NestedDict
class GoodEventDict(TypedDict):
nested: GoodNestedDict
class BadEventDict(TypedDict):
nested: BadNestedDict
# Funny case: lone TypeVar makes sense here
def print_dict(source_dict: BaseEventDict[_T]) -> None:
print(source_dict)
test_dict: EventDict = {
"nested": {"foo": "abc"},
}
good_test_dict: GoodEventDict = {
"nested": {"foo": "abc", "bar": "bar"},
}
bad_test_dict: BadEventDict = {
"nested": {"foo": 1},
}
print_dict(test_dict)
print_dict(good_test_dict)
print_dict(bad_test_dict) # E: Value of type variable "_T" of "print_dict" cannot be "BadNestedDict" [type-var]
In this setup print_dict is also interesting: you cannot use an upper bound, because the field type is invariant, so a single TypeVar with a bound (same as before) comes to rescue. Anything compatible with NestedDict is accepted as _T resolver, and everything incompatible is rejected.
Here's a playground with this implementation.