Model configuration and strict mode

Pydantic · Practical · 10 min

Config decides how forgiving a model is, which matters as soon as models guard an API boundary.

Model-wide behavior is set with a class attribute model_config = ConfigDict(...) (it replaces v1's inner class Config). Settings are inherited by subclasses and can be overridden there.

The options you will reach for most:

  • extra: what to do with unknown keys. "ignore" (default) drops them, "forbid" raises extra_forbidden, "allow" keeps them, available via model_extra.
  • frozen=True: instances are immutable and become hashable, so they can be dict keys or set members.
  • validate_assignment=True: obj.field = value is validated too; otherwise assignment is unchecked.
  • strict=True: no type coercion at all; "42" is rejected for an int. You can also make single fields strict with Field(strict=True) or call model_validate(data, strict=True) for one call.
  • str_strip_whitespace=True, str_to_lower=True, str_max_length: global string handling.
  • populate_by_name=True / validate_by_name=True: accept field names as well as aliases.
  • from_attributes=True: validate from arbitrary objects such as ORM rows via model_validate(row).
  • use_enum_values=True: store the enum's value instead of the member.

Strict mode is not all-or-nothing in JSON: when validating JSON, strict still accepts an ISO string for a datetime, since JSON has no date type.

Choose extra="forbid" for inbound API payloads where a typo like emial should fail loudly, and the default "ignore" for reading third-party data that may grow new fields.

Example

from pydantic import BaseModel, ConfigDict, ValidationError

class Point(BaseModel):
    model_config = ConfigDict(frozen=True, extra="forbid")
    x: int
    y: int

p = Point(x=1, y="2")
print({p: "origin-ish"}[Point(x=1, y=2)])    # origin-ish  (frozen -> hashable)
for bad in ({"x": 1, "y": 2, "z": 3}, {"x": "1", "y": 2}):
    try:
        Point.model_validate(bad, strict=True)
    except ValidationError as e:
        print(e.errors()[0]["type"])        # extra_forbidden, then int_type

class Account(BaseModel):
    model_config = ConfigDict(validate_assignment=True)
    balance: int

a = Account(balance=10)
try:
    a.balance = "lots"
except ValidationError:
    print("assignment rejected")             # assignment rejected

Remember

  • model_config = ConfigDict(...) replaces v1's class Config
  • extra="forbid" catches typos in inbound payloads
  • frozen=True makes instances immutable and hashable
  • strict mode disables coercion per model, field or call

Watch out for

  • Assuming attribute assignment is validated; it is not without validate_assignment=True.

Check yourself

  1. With default config, what happens to an unknown key in the input dict?

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

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