3.7. Typing TypedDict

  • Since Python 3.8

  • PEP 589 -- TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

3.7.1. SetUp

>>> from typing import TypedDict

3.7.2. Dict

>>> pt: dict = {'x':1 , 'y':2}
>>> pt: dict = {'a':1 , 'b':2}

3.7.3. Dict[...]

>>> pt: dict[str, int] = {'x':1 , 'y':2}
>>> pt: dict[str, int] = {'a':1 , 'b':2}

3.7.4. TypedDict

  • Since Python 3.8

  • PEP 589 -- TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys

>>> class Point(TypedDict):
...     x: int
...     y: int
>>>
>>> pt = Point(x=1, y=2)  # ok
>>> pt = Point(x=1, y=2, z=3)  # error, Extra key 'z' for TypedDict 'Point'
>>> pt = Point(x=1, y=2)  # error, TypedDict 'Point' has missing keys: 'x', 'y'

3.7.5. Optional Values

>>> class Point(TypedDict):
...     x: int
...     y: int
...     z: int | None
>>> Point(x=1, y=2, z=3)  # ok
{'x': 1, 'y': 2, 'z': 3}
>>> Point(x=1, y=2, z=None)  # ok
{'x': 1, 'y': 2, 'z': None}
>>> Point(x=1, y=2)  # error, TypedDict 'Point' has missing key: 'z'
{'x': 1, 'y': 2}

3.7.6. Default Values

  • Does not support default values

>>> class Point(TypedDict):
...     x: int
...     y: int
...     z: int = 0
>>> Point(x=1, y=2, z=3)  # ok
{'x': 1, 'y': 2, 'z': 3}
>>> Point(x=1, y=2)  # error, TypedDict 'Point' has missing key: 'z'
{'x': 1, 'y': 2}

3.7.7. NotRequired

  • Since Python 3.11

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

>>> from typing import NotRequired
>>> class Point(TypedDict):
...     x: int
...     y: int
...     z: NotRequired[int]
>>> Point(x=1, y=2)  # ok
{'x': 1, 'y': 2}
>>> Point(x=1, y=2, z=3)  # ok
{'x': 1, 'y': 2, 'z': 3}

3.7.8. Required

  • Since Python 3.11

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

>>> from typing import Required, NotRequired
>>> class Point(TypedDict):
...     x: Required[int]
...     y: Required[int]
...     z: NotRequired[int]
>>> Point(x=1, y=2)  # ok
{'x': 1, 'y': 2}
>>> Point(x=1, y=2, z=3)  # ok
{'x': 1, 'y': 2, 'z': 3}

3.7.9. Total

  • Since Python 3.11

  • PEP 655 –- Marking individual TypedDict items as required or potentially-missing

>>> class Point(TypedDict, total=True):
...     x: int
...     y: int
...     z: int
>>> Point(x=1, y=2)  # error, TypedDict 'Point' has missing key: 'z'
{'x': 1, 'y': 2}
>>> Point(x=1, y=2, z=3)  # ok
{'x': 1, 'y': 2, 'z': 3}

3.7.10. Use Case - 0x01

>>> class User(TypedDict):
...     firstname: str
...     lastname: str
>>> def hello(user: User):
...     result = f'Hello {user["firstname"]} {user["lastname"]}'
>>> mark: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 40}
>>> hello(mark)  # ok
>>> mark: User = {'firstname': 'Mark', 'lastname': 'Watney'}
>>> hello(mark)  # error: missing `age`  
>>> mark: User = {'firstname': 'Mark'}
>>> hello(mark)  # error: missing `lastname` and `age`  
>>> mark = User(firstname='Mark', lastname='Watney', age=42)
>>> hello(mark)  # ok
>>> mark = User(firstname='Mark', lastname='Watney')
>>> hello(mark)  # error: missing `age`  
>>> mark = User(firstname='Mark')
>>> hello(mark)  # error: missing `lastname`  
>>> iris = {'genus': 'Iris', 'species': 'Setosa'}
>>> hello(iris)  # error: not an user  

3.7.11. Use Case - 0x02

>>> from datetime import datetime, date, time
>>> from typing import Literal, TypedDict
>>>
>>> class Log(TypedDict):
...     when: datetime
...     level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
...     message: str
>>>
>>>
>>> def parse(line: str) -> Log:
...     d, t, lvl, msg = line.strip().split(', ', maxsplit=3)
...     d = date.fromisoformat(d)
...     t = time.fromisoformat(t)
...     dt = datetime.combine(d,t)
...     return Log(when=dt, level=lvl, message=msg)
>>> line = '1969-07-21, 02:56:15, WARNING, Neil Armstrong first words on the Moon'
>>>
>>> parse(line)  
{'when': datetime.datetime(1969, 7, 21, 2, 56, 15),
 'level': 'WARNING',
 'message': 'Neil Armstrong first words on the Moon'}

3.7.12. Further Reading

3.7.13. References

3.7.14. Assignments

Code 3.40. Solution
"""
* Assignment: Typing Annotations Mapping
* Complexity: easy
* Lines of code: 3 lines
* Time: 2 min

English:
    1. Define `User: TypedDict` with:
        a. `firstname: str`
        b. `lastname: str`
        c. `age: int`
    2. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `User: TypedDict` z:
        a. `firstname: str`
        b. `lastname: str`
        c. `age: int`
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> data: User = {'firstname': 'Mark', 'lastname': 'Watney', 'age': 40}
    >>> data
    {'firstname': 'Mark', 'lastname': 'Watney', 'age': 40}

    >>> data: User = User(**{'firstname': 'Mark', 'lastname': 'Watney', 'age': 40})
    >>> data
    {'firstname': 'Mark', 'lastname': 'Watney', 'age': 40}

    >>> data: User = User(firstname='Mark', lastname='Watney', age=40)
    >>> data
    {'firstname': 'Mark', 'lastname': 'Watney', 'age': 40}
"""


# Declare proper types for variables
class User:
    ...