为什么FreezeGun无法使用SQLAlchemy默认值?

时间:2019-11-09 04:40:52

标签: python sqlalchemy freezegun

我使用以下模型创建了一个SQLAlchemy应用程序:

class MyObject(db.Model):
    __tablename__ = 'my_object'
    id = db.Column(db.Integer, nullable=False, primary_key=True, autoincrement=True)
    some_string = db.Column(db.String(20), nullable=False)
    created = db.Column(db.DateTime, default=datetime.datetime.now)
    updated = db.Column(db.DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)

我创建了该迁移文件并与之一起使用:

"""
Junk

Revision ID: 4b6fffffffff_
Revises: 4b6e7775856f
Create Date: 2019-11-08 00:31:13.297355

"""

# revision identifiers, used by Alembic.
revision = '4b6fffffffff_'
down_revision = '4b6e7775856f'

from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
from sqlalchemy import false


def upgrade():
    op.create_table(
        'my_object',
        sa.Column('created', sa.DateTime(), nullable=False),
        sa.Column('updated', sa.DateTime(), nullable=False),
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('some_string', sa.String(length=20), nullable=False),
        sa.PrimaryKeyConstraint('id')
    )

现在我有一个使用Freezegun的单元测试案例:

def test_freeze_gun_on_sql_alchemy(self):
    now_time = datetime.datetime(year=2012, month=4, day=1, hour=5, minute=12, second=32, microsecond=543)
    freezer = freeze_time(now_time)
    freezer.start()
    print 'datetime.datetime.now() = {}'.format(datetime.datetime.now())
    m = MyObject(some_string="Hello World")
    db.session.add(m)
    db.session.commit()
    freezer.stop()
    print 'm.created = {}'.format(m.created)

此测试用例产生以下输出:

datetime.datetime.now() = 2012-04-01 05:12:32.000543
m.created = 2019-11-09 04:04:55

为什么m.created是当前的挂钟时间而不是FreezeGun时间?两者应该是相同的。

根据this answer,Freezegun修补了datetime.datetime.now()。我的测试案例证实了这一点。那么为什么/如何在数据库中存储一个不同的值呢?

1 个答案:

答案 0 :(得分:4)

由于MyObject位于模块命名空间中,因此它及其类级别属性在编译时进行评估。这是在Freezegun修补datetime.datetime.now之前发生的,因此列默认功能仍指向stdlib实现。

这是一个更简单的示例:

import datetime
from freezegun import freeze_time


class MyObject:
    dt_now = datetime.datetime.now


now_time = datetime.datetime(
    year=2012, month=4, day=1, hour=5, minute=12, second=32, microsecond=543
)
freezer = freeze_time(now_time)
freezer.start()
print(datetime.datetime.now())  # 2012-04-01 05:12:32.000543
print(MyObject().dt_now())  # 2019-11-09 15:44:29.437382
freezer.stop()

现在在设置class属性之前实例化Freezegun:

now_time = datetime.datetime(
    year=2012, month=4, day=1, hour=5, minute=12, second=32, microsecond=543
)
freezer = freeze_time(now_time)
freezer.start()


class MyObject:
    dt_now = datetime.datetime.now


print(datetime.datetime.now())  # 2012-04-01 05:12:32.000543
print(MyObject().dt_now())  # 2012-04-01 05:12:32.000543
freezer.stop()

参考this issue,将默认值包装在lambda中可以工作:

created = db.Column(db.DateTime, default=lambda: datetime.datetime.now())

...因为这样可以防止将stdlib函数绑定到Column默认值。或者,确保在设置Freezegun之后导入MyObject所在的模块。