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: strUser(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 thanjson.loadsfollowed bymodel_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) # FalseRemember
- 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.