Understanding Pydantic Model Validation with Simple Examples
Pydantic is a Python library that provides runtime type checking and validation of data. Let me explain how validation works with simple examples. Basic Example Without ORM First, let's look at a simple Pydantic model without database/ORM involvement: from pydantic import BaseModel class Person(BaseModel): name: str age: int # Valid input (dictionary) valid_data = {"name": "Alice", "age": 30} person = Person(**valid_data) # or Person.model_validate(valid_data) print(person) # Works fine # Invalid input invalid_data = {"name": "Bob", "age": "thirty"} # age should be int try: person = Person(**invalid_data) except Exception as e: print(f"Error: {e}") # Shows validation error for age The Problem With ORM Objects When working with SQLAlchemy ORM models, we can't directly use them with Pydantic because: # SQLAlchemy model class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String) # Pydantic model class UserResponse(BaseModel): id: int name: str # This WON'T work directly: db_user = User(id=1, name="Alice") try: response = UserResponse.model_validate(db_user) except Exception as e: print(f"Error: {e}") # Fails because Pydantic doesn't know how to handle ORM objects Solution: from_attributes=True This is where from_attributes=True comes in. It tells Pydantic: "When I give you an ORM object, don't treat it as a dictionary" "Instead, access its attributes directly (like db_user.id, db_user.name)" class UserResponse(BaseModel): id: int name: str class Config: from_attributes = True # Previously called orm_mode=True # Now this WORKS: db_user = User(id=1, name="Alice") response = UserResponse.model_validate(db_user) print(response) # UserResponse(id=1, name='Alice') How It Works Step-by-Step You pass an ORM object to model_validate() Pydantic checks from_attributes in the Config Instead of treating it as a dict, Pydantic: Looks at the field names in your Pydantic model (id, name) Tries to access those attributes on the ORM object (db_user.id, db_user.name) Validates the values it finds Real-world Example Your WorkHistoryResponse should be: class WorkHistoryResponse(BaseModel): id: int user_id: int facility_name: str # ... other fields ... class Config: from_attributes = True # This enables ORM model conversion Then when you do: WorkHistoryResponse.model_validate(work_history_orm_object) Pydantic will: See from_attributes=True Look for work_history_orm_object.id Look for work_history_orm_object.user_id Look for work_history_orm_object.facility_name And so on for all fields in response model Key Takeaways Direct dictionaries work automatically with Pydantic ORM objects need from_attributes=True to be validated The Config class must be inside your Pydantic model Field names must match between your ORM model and Pydantic model

Pydantic is a Python library that provides runtime type checking and validation of data. Let me explain how validation works with simple examples.
Basic Example Without ORM
First, let's look at a simple Pydantic model without database/ORM involvement:
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
# Valid input (dictionary)
valid_data = {"name": "Alice", "age": 30}
person = Person(**valid_data) # or Person.model_validate(valid_data)
print(person) # Works fine
# Invalid input
invalid_data = {"name": "Bob", "age": "thirty"} # age should be int
try:
person = Person(**invalid_data)
except Exception as e:
print(f"Error: {e}") # Shows validation error for age
The Problem With ORM Objects
When working with SQLAlchemy ORM models, we can't directly use them with Pydantic because:
# SQLAlchemy model
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
# Pydantic model
class UserResponse(BaseModel):
id: int
name: str
# This WON'T work directly:
db_user = User(id=1, name="Alice")
try:
response = UserResponse.model_validate(db_user)
except Exception as e:
print(f"Error: {e}") # Fails because Pydantic doesn't know how to handle ORM objects
Solution: from_attributes=True
This is where from_attributes=True
comes in. It tells Pydantic:
- "When I give you an ORM object, don't treat it as a dictionary"
- "Instead, access its attributes directly (like
db_user.id
,db_user.name
)"
class UserResponse(BaseModel):
id: int
name: str
class Config:
from_attributes = True # Previously called orm_mode=True
# Now this WORKS:
db_user = User(id=1, name="Alice")
response = UserResponse.model_validate(db_user)
print(response) # UserResponse(id=1, name='Alice')
How It Works Step-by-Step
-
You pass an ORM object to
model_validate()
-
Pydantic checks
from_attributes
in the Config -
Instead of treating it as a dict, Pydantic:
- Looks at the field names in your Pydantic model (
id
,name
) - Tries to access those attributes on the ORM object (
db_user.id
,db_user.name
) - Validates the values it finds
- Looks at the field names in your Pydantic model (
Real-world Example
Your WorkHistoryResponse
should be:
class WorkHistoryResponse(BaseModel):
id: int
user_id: int
facility_name: str
# ... other fields ...
class Config:
from_attributes = True # This enables ORM model conversion
Then when you do:
WorkHistoryResponse.model_validate(work_history_orm_object)
Pydantic will:
- See
from_attributes=True
- Look for
work_history_orm_object.id
- Look for
work_history_orm_object.user_id
- Look for
work_history_orm_object.facility_name
- And so on for all fields in response model
Key Takeaways
- Direct dictionaries work automatically with Pydantic
-
ORM objects need
from_attributes=True
to be validated - The Config class must be inside your Pydantic model
- Field names must match between your ORM model and Pydantic model