Your first BaseModel

Pydantic · Essentials · 10 min

Every Pydantic feature starts from a model: it is how you turn untrusted dicts and JSON into typed objects.

A Pydantic model is a class that inherits from BaseModel and declares its fields as annotated class attributes. Creating an instance runs validation: each value is checked against its annotation and, where it is safe, converted.

class User(BaseModel):
    id: int
    name: str

User(id="42", name="Ann") gives id=42 as an int, because Pydantic's default lax mode accepts a numeric string for an int. User(id="abc", name="Ann") raises ValidationError. A field without a default is required; a field with a default is optional.

Three ways in, all producing the same validated instance:

  • keyword arguments: User(id=1, name="Ann")
  • a dict: User.model_validate(data)
  • a JSON string or bytes: User.model_validate_json(raw), which parses and validates in one pass in Rust and is faster than json.loads followed by model_validate.

Instances are normal objects: read fields as attributes, compare them with == (same type and same field values), and print them for a readable repr. Mutable defaults such as tags: list[str] = [] are safe in Pydantic because each instance gets its own copy, unlike plain class attributes.

All the v2 methods start with model_ (model_validate, model_dump, model_copy, model_fields), so they never clash with your field names. The v1 names like parse_obj and .dict() still exist but are deprecated.

Example

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    tags: list[str] = []

u = User(id="42", name="Ann")
print(u)                 # id=42 name='Ann' tags=[]
print(type(u.id))        # <class 'int'>

u2 = User.model_validate({"id": 7, "name": "Bo", "tags": ["admin"]})
u3 = User.model_validate_json('{"id": 7, "name": "Bo", "tags": ["admin"]}')
print(u2 == u3)          # True
print(User(id=1, name="x").tags is User(id=2, name="y").tags)  # False

Remember

  • Fields are annotated class attributes; no default means required
  • Lax mode converts safe inputs like "42" to 42 and rejects the rest
  • model_validate for dicts, model_validate_json for raw JSON

Watch out for

  • Calling json.loads and then model_validate when model_validate_json does both, faster.

Check yourself

  1. Given class M(BaseModel): n: int, what does M(n="5").n return?

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

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