如何在SQLAlchemy中模拟创建时间?

12

我在Flask应用程序中使用SQLAlchemy。在我的测试中,我尝试模拟自动创建和更新条目的时间戳。但是我不知道如何在SQLAlchemy中实现这个功能。例如,我尝试使用freezgun来模拟日期时间的创建,但失败了。例如:

class Entry(db.Model):
    __tablename__ = 'entries'
    created = db.Column(db.DateTime(), default=db.func.now())
    updated = db.Column(db.DateTime(), default=db.func.now(), onupdate=db.func.now())

class ViewTestCase(AppliactionTestCase):
    def test(self):
        with freeze_time("2014-06-01 16:00:00"):
            db.session.add(Entry())
            db.session.commit()
        entry = db.session.query(Entry).first()
        self.assertEqual(entry.created, datetime(2014, 6, 1, 16, 0, 0))

还有一个问题。如果我需要刷新条目的updated,但没有进行任何更改,如何强制保存条目?

2个回答

5

SQLAlchemy提供了钩子sqlalchemy.event API来修补查询会话。

前提条件: flask-sqlalchemy,pytest,freezengun,contextlib

使用@prokoptesev所使用的flask-sqlalchemy示例:

class Entry(db.Model):
    __tablename__ = 'entries'
    created = db.Column(db.DateTime(), default=db.func.now())
    updated = db.Column(db.DateTime(), default=db.func.now(), onupdate=db.func.now())

只需编写pytest fixtures以修改行创建时间:
import datetime
from flask_sqlalchemy import event
from contextlib import contextmanager
from freezegun import freeze_time

import Entry
# if you want the code be commonly used, change Entry to db.Model instead


@contextmanager
def patch_time(time_to_freeze, tick=True):
    with freeze_time(time_to_freeze, tick=tick) as frozen_time:
        def set_timestamp(mapper, connection, target):
           now = datetime.datetime.now()
           if hasattr(target, 'created'):
               target.created = now
           if hasattr(target, 'updated'):
               target.updated = now
        event.listen(Entry, 'before_insert', set_timestamp,
                     propagate=True)
        yield frozen_time
        event.remove(Entry, 'before_insert', set_timestamp)


@pytest.fixture(scope='function')
def patch_current_time():
    return patch_time

然后编写一个 Pytest 类型的测试用例:

def test_patch_insert_time(patch_current_time, assertions):
    with patch_current_time("2014-06-01 16:00:00", tick=False):
        db.session.add(Entry())
        db.session.commit()
    entry = db.session.query(Entry).first()
    assert entry.created == datetime(2014, 6, 1, 16, 0, 0)

如果有人使用unittools或其他测试包,您也可以直接使用patch_time函数:

class ViewTestCase(AppliactionTestCase):
    def test(self):
        with patch_time("2014-06-01 16:00:00", tick=False):
            db.session.add(Entry())
            db.session.commit()
        entry = db.session.query(Entry).first()
        self.assertEqual(entry.created, datetime(2014, 6, 1, 16, 0, 0))

愉快编码。


2

Freezegun仅修补datetime.datetime.now。您可以通过以下两种方式之一解决此问题:

  1. 使用mock.patch来修补db.func.now(),或者(在我看来更简单)
  2. 使用default=datetime.datetime.now,这样freezegun将正确地对其进行修补。

1
对我来说不起作用。我不知道它在底层是如何工作的,但如果我在模型中使用default=datetime.datetime.now,datetime会被修补,但在保存的模型中,“created”会返回当前时间。 - prokoptsev
我该如何修补 db.func.now ? 如果我错了,请纠正我。db.func.now 是保存模型时的数据库函数。也许修补它不正确? - prokoptsev
你的测试中,Entry类相对于哪里? - Patrick Collins
抱歉,我不明白你的意思。 - prokoptsev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接