همونطور که آقا سروش اشاره کرد، اینجا یک اشتباه سهوی به وجود اومده.
این قسمت:
category_id: int | None = None
نمیگه:
اگر category_id مقدار 0 داشت، اون را به None تبدیل کن.
بلکه میگه:
category_idمیتونه یک
intیا
Noneباشه و اگر فیلد اصلا ارسال نشد، مقدار پیشفرض
Noneخواهد بود.
بنابراین:
اگه category_id اصلا ارسال نشه، مقدار پیشفرض که None هست به category_id اختصاص داده میشه
و اگه :
{
"category_id": null
}ارسال بشه بازم None میشه.
اما اگه بنویسیم :
{
"category_id": 0
}و ارسالش کنیم، category_id = 0 میشه. تا اینجا همه چی ساده و مشخص بود. اما پس مشکل اصلی کجاست؟
مشکل اصلی در Router صورت میگیره!
اینجا ما گفتیم:
if data.category_id:
که یعنی اگه ما 0 ارسال کنیم مثل این میمونه که ما این شرط رو چک کردیم:
if 0:
و در پایتون مقدار 0 برابر با Falsy value هست.
پس وقتی:
data.category_id == 0
باشه، این شرط:
if data.category_id:
اجرا نمیشه.
در نتیجه، اصلاً این Query هم اجرا نمیشه:
select(Category).where(Category.id == data.category_id)
و Router مستقیم میره سراغ ساخت Post:
post = Post(
...
category_id=0,
)
بعد:
await db.commit()
و SQLAlchemy درخواست INSERT را به دیتابیس (در اینجا PostgreSQL) میفرسته.
PostgreSQL هم هنگام INSERT بررسی میکنه:
آیا در جدول
categoriesرکوردی با
id = 0وجود دارد؟
چون مقدار
category_id=0 به رکوردی در جدول categories اشاره نمیکنه، Foreign Key Constraint نقض میشه و IntegrityError میگیریم.راه حل چیه؟
در این مثال،
category_id اختیاریه و میتونه None باشه. پس ما میتونیم None بودن یا نبودنش رو چک کنیم و به صراحتا بگیم :if data.category_id is not None:
result = await db.execute(
select(Category).where(Category.id == data.category_id)
)
category = result.scalar_one_or_none()
if category is None:
raise CategoryNotFound(data.category_id)
حالا اگر:
"category_id": 0
باشه، شرط اجرا میشه و وجود Category با ID صفر بررسی میشه.
اگر وجود نداشته باشه، قبل از رسیدن به Database خطای مناسب رو raise میکنیم.
که اینجا میرسیم به یکی از اصول Zen of Python :
Explicit is better than implicit⚠️ نکته : اگر از نظر Business Logic پروژه تون، مقدار
category_id باید عددی بزرگتر از صفر باشه، بهتره که این محدودیت رو در Schema تعریف کنیم:from pydantic import BaseModel, Field
class PostBase(BaseModel):
title: str
content: str
category_id: int | None = Field(default=None, gt=0)
حالا:
{
"category_id": 0
}اصلاً وارد منطق Router نمیشه و Pydantic خطای Validation Error بر میگردونه.
اما:
{
"category_id": null
}و :
{
"category_id": 5
}مشکلی نداره.