The TypedDict class

When getting data from an API for example, it's fairly common to have data in a dictionary, like so:

user = {
    'name': 'Mike',
    'age': 21,
}

The thing with such data is that it's usually well structured with string keys, but the values are of various different types. Often you can define a schema for the data type, i.e. you know that the dictionary will have a string "name", and an integer "age". But with normal dictionaries, it doesn't work that way:

user = {
    'name': 'Mike',
    'age': 21,
}
reveal_type(user['name'])
reveal_type(user['age'])

Just like the case with ['Mike', 21], mypy defaults to assuming you're just making a dict[str, object]. And usually, that would be the right assumption. But not in this specific case.

Maybe you can try to define a schema by making a dictionary with the types? If you try to do that you'll get weird errors, about Type[int] and Type[str]:

user = {
    'name': str,
    'age': int
}
d['name'] = 'Mike'
d['age'] = 31

Mypy knows about this usecase, and it has a feature that allows making such dict types. The solution is to use TypedDict to define a User type first, and to tell mypy that we want the user dictionary to adhere to that shape:

from typing import TypedDict

User = TypedDict('User', {
  'name': str,
  'age': int
})

user: User = {
    'name': 'Mike',
    'age': 21,
}

reveal_type(user['name'])
reveal_type(user['age'])

Another option is to use the same syntax as NamedTuple. But unlike NamedTuple, you don't need to instantiate the User class, a regular dictionary also works.

from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

user: User = {
    'name': 'Mike',
    'age': 21,
}

reveal_type(user['name'])
reveal_type(user['age'])

You can even define deeply nested data in this way:

from typing import TypedDict

class UserDetails(TypedDict):
    name: str
    age: int

class User(TypedDict):
    userid: int
    username: str
    details: UserDetails

def get_user() -> User:
    return {
        "username": "miketyson",
        "userid": 123456,
        "details": {
            "name": "Mike Tyson",
            "age": 21,
        },
    }

We can use TypedDicts inside TypedDicts to make this work.