У меня есть интересный случай с Pydantic: хотя поле уже заполнено объектом, когда FastAPI возвращает результат (или я вручную использую model_validate
), к некоторым полям (точнее, к неотложенным и объединенным загруженным полям) вызывается дополнительный SQL:
class PortfolioFullSchema(BaseModel):
id: int
title: str
category: str
cover_image: Optional[str] = None
description: Optional[str] = None
user_username: str
is_saved: bool = False
is_appreciated: bool = False
read: int
tools: list[str]
tags: list[str]
portfolio_files: list[PortfolioFileSchema]
created_at: datetime
simple_appreciates_count: int = 0
senior_appreciates_count: int = 0
@router.get(
'/{portfolio_id}',
summary='Get portfolio detail',
response_model=PortfolioFullSchema,
status_code=status.HTTP_200_OK,
)
async def get_portfolio(
portfolio_id: int,
auth_user: OptionalAuthUser,
db: DbDependency,
):
"""get user portfolio route"""
builder = PortfolioQueryBuilder(
auth_user_username=auth_user and auth_user.username, # type: ignore[arg-type]
portfolio_id=portfolio_id,
)
portfolio: Portfolio = db.execute(await builder.full()).unique()
print(portfolio.portfolio_files) # [<models.portfolio.PortfolioFile object at 0x7f5f0a5d8a50>]
print(portfolio.created_at) # 2024-05-22 09:20:11.749022+00:00
portfolio.simple_appreciates_count, portfolio.senior_appreciates_count = 4, 5 # type: ignore[attr-defined]
portfolio.read += 1
db.add(portfolio)
db.commit()
return PortfolioFullSchema.model_validate(portfolio, from_attributes=True)
builder.full()
приходит:
select(Portfolio).where(Portfolio.id == self.portfolio_id)
.options(joinedload(Portfolio.portfolio_files).joinedload(PortfolioFile.file))
.options(undefer(Portfolio.created_at))
Сгенерированный им SQL выглядит следующим образом (для id=7):
select
user_profile.id,
user_profile.user_username,
user_profile.is_senior,
user_profile.job_title,
user_profile.company_name,
user_profile.location,
user_profile.biography,
user_profile.social_profiles,
user_profile.cover_photo,
user_profile.open_to_work,
user_user.username,
user_user.id as id_1,
user_user.first_name,
user_user.last_name,
user_user.email,
user_user.avatar,
user_user.password,
user_user.user_type,
user_user.is_active,
user_user.last_login,
portfolio_owner.id as id_2,
portfolio_owner.portfolio_id,
portfolio_owner.user_username as user_username_1,
portfolio_portfolio.id as id_3,
portfolio_portfolio.user_username as user_username_2,
portfolio_portfolio.title,
portfolio_portfolio.description,
portfolio_portfolio.cover_image,
portfolio_portfolio.category,
portfolio_portfolio.read,
portfolio_portfolio.tools,
portfolio_portfolio.tags,
portfolio_portfolio.created_at,
core_file_1.id as id_4,
core_file_1.name,
core_file_1.original_name,
core_file_1.url,
core_file_1.mime_type,
portfolio_file_1.id as id_5,
portfolio_file_1.portfolio_id as portfolio_id_1,
portfolio_file_1.file_id,
portfolio_file_1."order",
portfolio_file_1.slideshow
from
portfolio_portfolio
join portfolio_owner on
portfolio_portfolio.id = portfolio_owner.portfolio_id
join user_user on
user_user.username = portfolio_owner.user_username
join user_profile on
user_user.username = user_profile.user_username
left outer join portfolio_file as portfolio_file_1 on
portfolio_portfolio.id = portfolio_file_1.portfolio_id
left outer join core_file as core_file_1 on
core_file_1.id = portfolio_file_1.file_id
where
portfolio_portfolio.id = 7
order by
portfolio_owner.id
Два вывода в основном коде работают нормально: они НЕ генерируют дополнительный SQL, а поля загружаются как есть, показывая правильные результаты. Однако когда я это делаю model_validate
, запускаются два дополнительных SQL-запроса:
select
portfolio_portfolio.id as portfolio_portfolio_id,
portfolio_portfolio.user_username as portfolio_portfolio_user_username,
portfolio_portfolio.title as portfolio_portfolio_title,
portfolio_portfolio.description as portfolio_portfolio_description,
portfolio_portfolio.cover_image as portfolio_portfolio_cover_image,
portfolio_portfolio.category as portfolio_portfolio_category,
portfolio_portfolio.read as portfolio_portfolio_read,
portfolio_portfolio.tools as portfolio_portfolio_tools,
portfolio_portfolio.tags as portfolio_portfolio_tags,
core_file_1.id as core_file_1_id,
core_file_1.name as core_file_1_name,
core_file_1.original_name as core_file_1_original_name,
core_file_1.url as core_file_1_url,
core_file_1.mime_type as core_file_1_mime_type,
portfolio_file_1.id as portfolio_file_1_id,
portfolio_file_1.portfolio_id as portfolio_file_1_portfolio_id,
portfolio_file_1.file_id as portfolio_file_1_file_id,
portfolio_file_1."order" as portfolio_file_1_order,
portfolio_file_1.slideshow as portfolio_file_1_slideshow
from
portfolio_portfolio
left outer join portfolio_file as portfolio_file_1 on
portfolio_portfolio.id = portfolio_file_1.portfolio_id
left outer join core_file as core_file_1 on
core_file_1.id = portfolio_file_1.file_id
where
portfolio_portfolio.id = 7
И
select
portfolio_portfolio.created_at as portfolio_portfolio_created_at
from
portfolio_portfolio
where
portfolio_portfolio.id = 7
Однако их не следует запускать, поскольку объект portfolio
уже содержит эти данные.
Наконец-то мне удалось разгадать загадку. Оказывается, проблема была не в Pydantic. После строк:
db.add(portfolio)
db.commit()
объект portfolio
«сбрасывает» свои поля, поэтому мне нужно снова получить его со всеми необходимыми полями. Или я могу просто переместить проверку перед любыми изменениями в базе данных:
// rest of the function
portfolio_schema = PortfolioFullSchema.model_validate(portfolio, from_attributes=True)
db.add(portfolio)
db.commit()
return portfolio_schema
После этого дополнительный SQL не запускается, и все извлекается один раз.