Python TypedDict with Generics

Posted 1 July 2023 · 1 min read

Tags:

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 · 1 min read

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

Mocking Boto3 with pytest

Published · 1 min read

Mock requests to AWS services with pytest

Configure Boto3 endpoint_url

Published · 1 min read

Use environment variables to point Boto3 at local services

Subscribe via RSS to get new posts delivered to your feed reader, or browse posts by tag.


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 maintaining a collection of shared libraries, services and a micro-frontend architecture.

I'm from London and you can find me on a few different social media platforms: