Cheatsheet for converting ORM (or any attribute-bearing) instances to Pydantic.

Enable from_attributes

class UserOut(BaseModel):
    id: int
    email: str
    name: str | None
    
    model_config = {"from_attributes": True}

Now Pydantic reads via getattr(obj, "field_name").

Validate from SQLAlchemy

sa_user = await session.get(User, 1)
out = UserOut.model_validate(sa_user)

Works with SQLAlchemy declarative models, attrs, dataclasses, anything with attributes.

FastAPI integration

@app.get("/users/{id}", response_model=UserOut)
async def get_user(id: int, db: AsyncSession = Depends(get_db)):
    return await db.get(User, id)             # SQLAlchemy → UserOut auto

response_model=UserOut + from_attributes=True = automatic conversion.

Eager-load relationships first

stmt = select(User).options(selectinload(User.posts)).where(User.id == id)
user = await db.scalar(stmt)

# Then convert
out = UserOutWithPosts.model_validate(user)

Without eager-load: lazy-load in async fails (MissingGreenlet).

Nested

class PostOut(BaseModel):
    id: int
    title: str
    model_config = {"from_attributes": True}

class UserOutWithPosts(BaseModel):
    id: int
    email: str
    posts: list[PostOut]
    model_config = {"from_attributes": True}

Both must have from_attributes=True.

Computed-on-output (using ORM hybrid_property)

# SQLAlchemy
class User(Base):
    first: Mapped[str]
    last: Mapped[str]
    
    @hybrid_property
    def full_name(self) -> str:
        return f"{self.first} {self.last}"

# Pydantic
class UserOut(BaseModel):
    full_name: str
    model_config = {"from_attributes": True}

# When SA user → UserOut: full_name property read via getattr

Exclude sensitive fields

# Bad: returning SQLAlchemy model directly
@app.get("/users/{id}")
async def get_user(id: int, db = Depends(get_db)):
    return await db.get(User, id)          # may include password_hash

# Good: explicit response_model
@app.get("/users/{id}", response_model=UserOut)
async def get_user(id: int, db = Depends(get_db)):
    return await db.get(User, id)          # UserOut only has safe fields
class OrderOut(BaseModel):
    id: int
    items_count: int          # not on SA model; provide computed
    
    model_config = {"from_attributes": True}
    
    @computed_field
    @property
    def items_count(self) -> int:
        return len(self.items)             # but where does self.items come from?

For computed_field on ORM-derived Pydantic: needs the source attr accessible. Eager-load.

Alias from ORM

class UserOut(BaseModel):
    user_id: int = Field(alias="id")        # SA model has `id`, Pydantic field is `user_id`
    model_config = {"from_attributes": True, "populate_by_name": True}

Lazy ORM load + async pitfall

# BAD in async (lazy-load triggers sync IO)
user = await db.get(User, 1)
return UserOutWithPosts.model_validate(user)
# user.posts not eager-loaded → error

Always eager-load relationships before converting.

Convert lists

@app.get("/users", response_model=list[UserOut])
async def list_users(db = Depends(get_db)):
    return (await db.execute(select(User))).scalars().all()

FastAPI converts each item.

Pure attrs / dataclass

from attrs import define

@define
class UserAttrs:
    id: int
    email: str

class UserOut(BaseModel):
    id: int
    email: str
    model_config = {"from_attributes": True}

UserOut.model_validate(UserAttrs(id=1, email="[email protected]"))

Same mechanism.

When NOT to use from_attributes

  • Plain dicts — use model_validate({...}) directly (default).
  • Strict input validation — re-define source schema.

Common mistakes

  • Lazy ORM access in async → MissingGreenlet.
  • ORM model returned directly without response_model → field leak.
  • Forgetting model_config = {"from_attributes": True}.
  • Trying to validate from sync ORM in async-only handler → blocks loop.

Read this next

If you want my SA → Pydantic schema reference, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .