Cheatsheet for @computed_field.
Basic
from pydantic import BaseModel, computed_field
class User(BaseModel):
first: str
last: str
@computed_field
@property
def full_name(self) -> str:
return f"{self.first} {self.last}"
user = User(first="Alice", last="X")
user.model_dump()
# {"first": "Alice", "last": "X", "full_name": "Alice X"}
@computed_field decorates @property. Type annotation required.
Alias
@computed_field(alias="fullName")
@property
def full_name(self) -> str:
return f"{self.first} {self.last}"
user.model_dump(by_alias=True)
# {"first": "...", "last": "...", "fullName": "..."}
Description / examples
@computed_field(
description="Concatenated first + last name",
examples=["Alice X"],
)
@property
def full_name(self) -> str:
return f"{self.first} {self.last}"
Exclude from output
@computed_field
@property
def expensive(self) -> int:
return self._compute()
user.model_dump(exclude={"expensive"})
Or return_type arg:
@computed_field(repr=False, return_type=str)
@property
def hidden_in_repr(self) -> str:
return "secret"
Cached vs not
By default, @computed_field re-runs on each access. For caching:
from functools import cached_property
class User(BaseModel):
first: str
last: str
@computed_field
@cached_property
def full_name(self) -> str:
return f"{self.first} {self.last}"
model_config = {"ignored_types": (cached_property,)} # don't validate property
cached_property runs once; subsequent accesses return cached.
In JSON Schema
Appears as a read-only property. validation schema doesn’t include it (not accepted on input); serialization schema does.
User.model_json_schema(mode="validation") # no full_name
User.model_json_schema(mode="serialization") # includes full_name
Use cases
Derived attributes
class Order(BaseModel):
quantity: int
unit_price: Decimal
@computed_field
@property
def total(self) -> Decimal:
return self.quantity * self.unit_price
Counts
class User(BaseModel):
posts: list[Post]
@computed_field
@property
def post_count(self) -> int:
return len(self.posts)
Status from state
class Job(BaseModel):
started_at: datetime
completed_at: datetime | None
@computed_field
@property
def status(self) -> str:
return "completed" if self.completed_at else "running"
Public-facing API field
class UserOut(BaseModel):
id: int
first: str
last: str
@computed_field
@property
def display_name(self) -> str:
return f"{self.first} {self.last}".strip() or f"user{self.id}"
Not for input
@computed_field is output-only. Don’t expect to set it from input:
User(first="Alice", last="X", full_name="Custom") # error or ignored
If you need input + derived: use plain field + model_validator(mode="after") to compute.
In FastAPI
class UserOut(BaseModel):
id: int
first: str
last: str
@computed_field
@property
def full_name(self) -> str:
return f"{self.first} {self.last}"
@app.get("/users/{id}", response_model=UserOut)
async def get_user(id: int): ...
full_name appears in OpenAPI response schema.
With SQLAlchemy hybrid_property
# SA model
class User(Base):
first: Mapped[str]
last: Mapped[str]
@hybrid_property
def full_name(self) -> str:
return f"{self.first} {self.last}"
@full_name.expression
@classmethod
def full_name(cls):
return func.concat(cls.first, " ", cls.last)
# Pydantic
class UserOut(BaseModel):
id: int
full_name: str # populated from SA hybrid_property
model_config = {"from_attributes": True}
Read attribute from SA via getattr. Don’t need @computed_field if SA has hybrid_property.
Common mistakes
- Missing return type annotation —
@computed_fieldrequires it. - Trying to accept input — it’s serialization-only.
- Returning non-JSON-serializable types in
mode="json"— set a serializer. - Heavy computation per access — use
cached_propertyor compute inmodel_validator(mode="after").
Read this next
If you want my computed-field patterns for API responses, 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 .