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

May 9, 2025 - 08:53
 0
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:

  1. "When I give you an ORM object, don't treat it as a dictionary"
  2. "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

  1. You pass an ORM object to model_validate()
  2. Pydantic checks from_attributes in the Config
  3. 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:

  1. See from_attributes=True
  2. Look for work_history_orm_object.id
  3. Look for work_history_orm_object.user_id
  4. Look for work_history_orm_object.facility_name
  5. And so on for all fields in response model

Key Takeaways

  1. Direct dictionaries work automatically with Pydantic
  2. ORM objects need from_attributes=True to be validated
  3. The Config class must be inside your Pydantic model
  4. Field names must match between your ORM model and Pydantic model