PEP 593: Discovering Annotated in Python
A practical guide to the Annotated type introduced in Python 3.9 that allows you to enrich your type hints with custom metadata.
Why You Should Know About PEP 593
If you’ve been working with Python for a while, you probably already know about type hints. Introduced in PEP 484 and PEP 3107, they allow us to write code like this:
def saludar(nombre: str) -> str:
return f"Hola, {nombre}!"
But here’s the interesting question: what if you want to add extra information to those types? For example, validating that a number is within a specific range, or indicating that a list has a maximum number of elements?
That’s where PEP 593 and its star comes in: Annotated.
What is Annotated
PEP 593 introduces Annotated[T, metadata] in the typing module. Basically, it lets you decorate any type with additional information without breaking compatibility with type checking tools like mypy or Pyre.
The syntax is simple:
from typing import Annotated
# A list with maximum 10 elements
ListaLimitada = Annotated[list, {"max_len": 10}]
# An integer between 0 and 100
Porcentaje = Annotated[int, {"range": (0, 100)}]
The golden rule: if a tool doesn’t know about your metadata, it simply ignores it and treats the type as T. This means you can use Annotated without fear of breaking anything.
A Real Example: Validating Data with Pydantic
Although Annotated was designed to be used with any metadata, it especially shines when combined with libraries like Pydantic. Since Pydantic v2, you can use Annotated for inline validations:
from typing import Annotated
from pydantic import Field, constr, conint
class Usuario:
nombre: Annotated[str, Field(min_length=3, max_length=50)]
edad: Annotated[int, Field(ge=0, le=120)]
email: Annotated[str, constr(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")]
intentos_login: Annotated[int, Field(ge=0, le=5)] = 0
# This user is valid
usuario = Usuario(nombre="Ana", edad=30, email="ana@ejemplo.com")
# This will throw a validation error
try:
usuario_invalido = Usuario(nombre="Yo", edad=150, email="invalid")
except Exception as e:
print(e) # Validation error for field edad
The fascinating part is that mypy still recognizes the underlying type. When it analyzes the code above, it sees str, int, etc., ignoring Pydantic’s validations. But Pydantic, when running, reads that metadata and applies the restrictions automatically.
Why This Is a Game Changer
Before PEP 593, if you wanted to add metadata to your types you had two options:
- Create new types: subclasses of
int,str, etc. It works, but it’s tedious and pollutes the namespace. - Use decorators or wrapper classes: you end up with complex code that’s hard to maintain.
With Annotated, you can enrich your types effortlessly. Imagine an inventory system where products have restrictions:
from typing import Annotated
Cantidad = Annotated[int, {"min": 0, "max": 1000}]
Precio = Annotated[float, {"min": 0.0}]
class Producto:
nombre: str
cantidad: Cantidad
precio: Precio
Now any library or tool that understands these restrictions can use them. And those that don’t, simply see int or float.
Aliases to Avoid Repetition
If you find it verbose writing Annotated everywhere, you can create aliases:
from typing import Annotated, TypeVar
T = TypeVar("T")
# Generic alias for positive values
Positive = Annotated[T, {"gt": 0}]
class Transaccion:
monto: Positive[float]
cantidad: Positive[int]
get_type_hints and the include_extras Parameter
There’s an important detail: typing.get_type_hints() by default ignores the metadata. If you need to access it, use the include_extras=True parameter:
from typing import Annotated, get_type_hints
class Persona:
nombre: Annotated[str, {"format": "uppercase"}]
# Without extras (default behavior)
get_type_hints(Persona) # {'nombre': str}
# With extras
get_type_hints(Persona, include_extras=True) # {'nombre': Annotated[str, {'format': 'uppercase'}]}
Conclusion
PEP 593 solves a real problem: the need to enrich types without losing compatibility. Whether it’s for validations with Pydantic, additional documentation, or creating your own static analysis tools, Annotated is a tool every Python developer should have in their utility belt.
The next time you write type hints, ask yourself: do I need to add something extra to this type?
Enjoyed this read?
Join our newsletter for more engineering insights.