
Pydantic is a Python library that uses type hints to validate, coerce, and structure input data.
When you define a BaseModel, Pydantic treats the type annotations as a schema and automatically enforces that schema whenever you create a model instance.
Example:
from datetime import datetime
from pydantic import BaseModel, PositiveInt
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: datetime | None
tastes: dict[str, PositiveInt]
Each annotation describes:
the field name (id, name, signup_ts, …)
the expected type
optional default values
These types are the validation rules.
When you create a model:
user = User(**external_data)
Pydantic:
Reads your input
Validates each field
Converts (coerces) values when allowed
Raises detailed errors when validation fails
Example coercions Pydantic can perform:
| Field type | Input | What Pydantic does |
|---|---|---|
int | "123" | converts to 123 |
datetime | "2020-01-02T03:04:05Z" | parses into a datetime object |
tuple[int, int] | ["10", "20"] | converts to (10, 20) |
PositiveInt | "7" | parses and validates positivity |
Pydantic stores the validated, converted values inside the model:
print(user.model_dump())
Example output:
{
'id': 123,
'name': 'John Doe',
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'tastes': {'wine': 9, 'cheese': 7, 'cabbage': 1},
}
Notice:
b'cheese') became "cheese""1" became integer 1 for PositiveIntIf any field fails validation, Pydantic raises a ValidationError:
try:
User(id="not an int", tastes={})
except ValidationError as e:
print(e.errors())
Sample error description:
[
{
'type': 'int_parsing',
'msg': 'Input should be a valid integer',
'loc': ('id',),
'input': 'not an int',
},
{
'type': 'missing',
'msg': 'Field required',
'loc': ('signup_ts',),
}
]
Key features of Pydantic’s errors:
int_parsing, missing, …)('id',))This makes debugging very easy.
By default, Pydantic is lax: it tries to coerce values.
But you can enable strict mode to prevent coercion:
from pydantic import BaseModel, StrictInt
class Model(BaseModel):
x: StrictInt
Now:
Model(x="1") # ❌ fails, no conversion allowed
You can add your own validation rules:
from pydantic import BaseModel, field_validator
class User(BaseModel):
age: int
@field_validator("age")
def check_age(cls, v):
if v < 0:
raise ValueError("Age must be positive")
return v
Pydantic validation works by:
1. Reading type hints from your model
2. Using these as a schema
3. Coercing types when allowed
4. Validating values deeply
5. Producing powerful error messages on failure
6. Returning structured Python objects on success
It gives you:
- familiar Python syntax
- automatic type enforcement
- extremely fast validation (written in Rust)
- great error reporting
- deep integration with other tools (e.g., FastAPI, Logfire)