Field() defaults and constraints

Pydantic · Essentials · 10 min

Most business rules are simple ranges and lengths; declaring them beats writing validators by hand.

Field() attaches extra information to a field. Use it as the default value, or inside Annotated (the preferred style because the type and its rules travel together and can be reused):

price: Annotated[float, Field(gt=0)]

Common arguments:

  • numbers: gt, ge, lt, le, multiple_of
  • strings and collections: min_length, max_length; strings also pattern (a regex)
  • default=... or default_factory=list for a fresh value per instance; the factory is how you get datetime.now() at creation time instead of at import
  • description, title and examples flow into JSON Schema and API docs
  • alias for an input name that differs from the attribute (covered later)
  • exclude=True to leave a field out of dumps, repr=False to hide it from the repr

Constraints are enforced in the Rust core and produce specific error types, such as greater_than or string_too_short, with the limit in ctx.

Because Annotated types are just values, you can name them once and reuse them across models:

Sku = Annotated[str, Field(pattern=r"^[A-Z]{3}-\d{4}$")]

A frequent bug is created: datetime = datetime.now(): the expression runs once when the class is defined, so every instance shares the same timestamp. Use Field(default_factory=datetime.now).

Example

from datetime import datetime
from typing import Annotated
from pydantic import BaseModel, Field, ValidationError

Sku = Annotated[str, Field(pattern=r"^[A-Z]{3}-\d{4}$")]

class Product(BaseModel):
    sku: Sku
    name: Annotated[str, Field(min_length=2, max_length=50)]
    price: Annotated[float, Field(gt=0, description="Unit price in EUR")]
    stock: int = Field(default=0, ge=0)
    created: datetime = Field(default_factory=datetime.now)

print(Product(sku="PEN-0001", name="Pen", price=1.5).stock)   # 0
try:
    Product(sku="pen", name="P", price=0)
except ValidationError as e:
    print([err["type"] for err in e.errors()])
# ['string_pattern_mismatch', 'string_too_short', 'greater_than']

Remember

  • Annotated[T, Field(...)] keeps type and constraints together and reusable
  • gt/ge/lt/le, min_length/max_length and pattern cover most simple rules
  • default_factory computes a fresh default per instance

Watch out for

  • Using datetime.now() as a plain default, which is evaluated once at class definition.

Check yourself

  1. Why use Field(default_factory=datetime.now) instead of = datetime.now()?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pydantic.dev/latest/concepts/fields/