为啥 sqlalchemy 没有正确设置默认值?

Posted

技术标签:

【中文标题】为啥 sqlalchemy 没有正确设置默认值?【英文标题】:Why is sqlalchemy not setting default value correctly?为什么 sqlalchemy 没有正确设置默认值? 【发布时间】:2022-01-21 02:25:22 【问题描述】:

由于某种原因,在此示例中,可选 is_active 属性未设置为默认值。

from pydantic import BaseModel, EmailStr
from datetime import datetime

# Pydantic schemas

# Shared properties
class UserBase(BaseModel):
    email: Optional[EmailStr] = None
    is_active: Optional[bool] = True
    is_superuser: bool = False
    username: Optional[str] = None
    

# Properties to receive via API on creation
class UserCreate(UserBase):
    email: EmailStr
    password: str


# sqlalchemy model

class User(Base):
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(25), index=True, unique=True, nullable=False)
    email = Column(String(50), unique=True, index=True, nullable=False)
    hashed_password = Column(String(256), nullable=False)
    is_active = Column(Boolean(), default=True, nullable=False)
    is_superuser = Column(Boolean(), default=False, nullable=False)

    __mapper_args__ = "eager_defaults": True


我期望is_active 的默认值是可选输入,是True。但如果没有明确通过,我会得到None

obj_in = UserCreate(email=email, password=password, username=username)
print(obj_in.is_active)
# True


db_obj = User(
            email=obj_in.email,
            hashed_password=get_password_hash(obj_in.password),
            username=obj_in.username,
            is_superuser=obj_in.is_superuser,
            # is_active=obj_in.is_active, 
        )
print(db_obj.is_active)
# None


# I have to add the is_active flag explicitly
db_obj = User(
            email=obj_in.email,
            hashed_password=get_password_hash(obj_in.password),
            username=obj_in.username,
            is_superuser=obj_in.is_superuser,
            is_active=obj_in.is_active, 
        )
print(db_obj.is_active)
# True
    

【问题讨论】:

不确定是否是问题所在;但是,我认为您不应该致电 Boolean。请改用,例如:is_active = Column(Boolean, default=True, nullable=False) 即末尾没有括号/括号。 谢谢,但这似乎无关紧要。以String(25) 为例。 刚刚以您编写的方式在 SQLite 上进行了测试,您是对的:似乎无关紧要。它是什么关系型数据库?也许有一些不兼容的东西? 我正在使用 postgres,但应该没关系吧?因为这个问题甚至在插入数据库之前就出现了 嗯,是的,那很可能不是问题,因为它是一个主要的数据库。我刚刚在 SQLAlchemy 文档中读到,如果数据库没有布尔类型,可能会有问题。但是,例如,SQLite 存储一个 1 就可以了。 【参考方案1】:

事后看来,非常明显。 (不是一切)。有两个问题:

    在将 db_obj 实际插入数据库之前,我正在检查它的值。无论出于何种原因,我认为 sqlalchemy 模型 db_obj = User(...) 返回的对象也将分配默认值。原来只有在将对象插入数据库后才分配默认值。

    影响我的用例的第二个问题是我没有刷新会话并尝试在退出 with session.begin() 块之前访问 obj id,即在提交对 db 的更改之前。

所以,修正这两点,我们得到:


from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker

engine = create_async_engine(
        SQLALCHEMY_DATABASE_URI,
        echo=True,
    )
    
# expire_on_commit=False will prevent attributes from being expired
# after commit.
async_session = sessionmaker(
    engine, expire_on_commit=False, class_=AsyncSession
)


obj_in = UserCreate(email=email, password=password, username=username)

db_obj = User(
            email=obj_in.email,
            hashed_password=get_password_hash(obj_in.password),
            username=obj_in.username,
            is_superuser=obj_in.is_superuser,
            # is_active=obj_in.is_active, 
        )


# this was my use case for running tests.
# I start session.begin() in a pytest fixture, so I don't have
# control on exiting the with block to allow db commit. 
# So in this case, a force `flush` is required.
async with async_session() as session, session.begin():
    session.add(db_obj)

    # force commit 
    await session.flush()

    # this assertion fails if above flush is removed.
    assert db_obj.is_active == obj_in.is_active

【讨论】:

以上是关于为啥 sqlalchemy 没有正确设置默认值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在提交对象之前 SQLAlchemy 默认列值不可用?

如何根据 SqlAlchemy 关系设置模型默认值?

SQLAlchemy docs(设置列默认值) - 这行语法有效的python如何? [关闭]

如何根据SqlAlchemy关系设置模型默认值?

Flask-Sqlalchemy设置时间默认值

如何将sqlalchemy中列的默认值设置为关系中列的值?