Python TypedDict with Generics

Posted 1 July 2023


Python added support for type hints in 3.5 but it's suprisingly tricky to type a dict using a generic type for values.

Defining type hints for a dictionary with a fixed set of keys is fairly straightforward with the TypedDict class from the typing module:

from typing import TypedDict

class Book(TypedDict):
    id: int 
    name: str 

book: Book = {"id": 123, "name": "To Kill A Mockingbird"}

If you add the line:

not_a_book: Book = {"something": "A book doesn't have"}

And run this with mypy you'll get:

$ python -m mypy type-dict.py
type-dict.py:9: error: Missing keys ("id", "name") for TypedDict "Book"  [typeddict-item]
type-dict.py:9: error: Extra key "something" for TypedDict "Book"  [typeddict-unknown-key]
Found 2 errors in 1 file (checked 1 source file)

Simple enough!

The typing module also provides Generic and TypeVar to provide support for generic classes. In my case I wanted to define a generic class for a nested dict with a value key that could be a variable type. Extending our book example:

from typing import TypeVar, Generic, TypedDict

T = TypeVar('T')

class Attribute(TypedDict, Generic[T]):
  value: T

class Book(TypedDict):
  id: Attribute[int]
  name: Attribute[str]

book: Book = {"id": {"value": 123}, "name": {"value": "To Kill A Mockingbird"}}

Type-check with mypy and it works great! Try to run this however...

$ python --version
Python 3.10.12
$ python type-dict-generic.py 
Traceback (most recent call last):
  File "/home/aocallaghan/exp/py-type-dict/type-dict-generic.py", line 5, in <module>
    class Attribute(TypedDict, Generic[T]):
  File "/home/aocallaghan/.pyenv/versions/3.10.12/lib/python3.10/typing.py", line 2348, in __new__
    raise TypeError('cannot inherit from both a TypedDict type '
TypeError: cannot inherit from both a TypedDict type and a non-TypedDict base class

You can't inherit from both a TypedDict type and something else with Python 3.10. However this has been fixed in Python 3.11:

$ python --version
Python 3.11.4
$ python type-dict-generic.py

Unfortunately there doesn't seem to be a simple way to achieve this in versions <3.11. One option is to use the Enum class to represent keys:

from enum import Enum, auto
from typing import TypeVar, TypedDict 

T = TypeVar('T')

class AttributeKeys(Enum):
  value = auto()

class Book(TypedDict):
  id: dict[AttributeKeys, int]
  name: dict[AttributeKeys, str]

book: Book = {"id": {AttributeKeys.value: 123}, "name": {AttributeKeys.value: "To Kill A Mockingbird"}}

not_a_book: Book = {"something": "A book doesn't have"}

Related posts

Using TypedDict with invalid attribute names

Published

How to use a TypedDict with item keys that aren't valid identifiers

Mocking Boto3 with pytest

Published

Mock requests to AWS services with pytest

Configure Boto3 endpoint_url

Published

Use environment variables to point Boto3 at local services


Thanks for reading

I'm Alex O'Callaghan and this is my personal website where I write about software development and do my best to learn in public. I currently work at Mintel as a Principal Engineer working primarily with React, TypeScript & Python.

I've been leading one of our platform teams, first as an Engineering Manager and now as a Principal Engineer, maintaining a collection of shared libraries, services and a micro-frontend architecture.