SQLAlchemy 默认日期时间

Posted

技术标签:

【中文标题】SQLAlchemy 默认日期时间【英文标题】:SQLAlchemy default DateTime 【发布时间】:2012-11-02 10:43:45 【问题描述】:

这是我的声明模型:

import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
    __tablename__ = 'test'

    id = Column(Integer, primary_key=True)
    created_date = DateTime(default=datetime.datetime.utcnow)

但是,当我尝试导入此模块时,我收到此错误:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "orm/models2.py", line 37, in <module>
    class Test(Base):
  File "orm/models2.py", line 41, in Test
    created_date = sqlalchemy.DateTime(default=datetime.datetime.utcnow)
TypeError: __init__() got an unexpected keyword argument 'default'

如果我使用整数类型,我可以设置一个默认值。怎么回事?

【问题讨论】:

不应该使用这个。 utcnow 是 UTC 时区中的原始时间戳,但是,原始时间戳可能会在本地时区中解释。 我知道这个问题是很久以前提出的,但我认为您的答案应该更改为@Jeff Widman 提供的答案,因为当表类为时,另一个答案将使用“编译”时间日期时间定义与创建记录的时间。如果您是 AFK,那么至少此评论会警告其他人仔细检查每个问题的 cmets。 【参考方案1】:

在您的数据库中计算时间戳,而不是在您的客户端中

出于理智,您可能希望所有datetimes 都由您的数据库服务器而不是应用程序服务器计算。在应用程序中计算时间戳可能会导致问题,因为网络延迟是可变的,客户端会遇到稍微不同的时钟漂移,并且不同的编程语言有时会稍微不同地计算时间。

SQLAlchemy 允许您通过传递 func.now()func.current_timestamp()(它们是彼此的别名)来执行此操作,这告诉数据库自己计算时间戳。

使用 SQLALchemy 的server_default

此外,对于您已经告诉数据库计算值的默认值,通常最好使用server_default 而不是default。这告诉 SQLAlchemy 将默认值作为CREATE TABLE 语句的一部分传递。

例如,如果您针对此表编写临时脚本,使用 server_default 意味着您无需担心手动向脚本添加时间戳调用 - 数据库会自动设置它。

了解 SQLAlchemy 的 onupdate/server_onupdate

SQLAlchemy 还支持onupdate,因此只要行更新,它就会插入一个新的时间戳。同样,最好告诉数据库自己计算时间戳:

from sqlalchemy.sql import func

time_created = Column(DateTime(timezone=True), server_default=func.now())
time_updated = Column(DateTime(timezone=True), onupdate=func.now())

有一个server_onupdate 参数,但与server_default 不同,它实际上并没有在服务器端设置任何东西。它只是告诉 SQLalchemy,当更新发生时,您的数据库将更改列(也许您创建了一个 trigger on the column ),因此 SQLAlchemy 将要求返回值,以便它可以更新相应的对象。

另一个潜在的问题:

您可能会惊讶地发现,如果您在单个事务中进行大量更改,它们都具有相同的时间戳。这是因为 SQL 标准规定 CURRENT_TIMESTAMP 根据事务的开始返回值。

PostgreSQL 提供非 SQL 标准的 statement_timestamp()clock_timestamp()确实在事务中更改。文档在这里:https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

UTC 时间戳

如果您想使用 UTC 时间戳,func.utcnow() 的实现存根在 SQLAlchemy documentation 中提供。不过,您需要自己提供适当的驱动程序特定功能。

【讨论】:

我确实看到现在有一​​种方法可以告诉 Postgres 使用当前时间,而不仅仅是事务开始时间......它在 postgres 文档中 有 func.utcnow() 之类的吗? @JeffWidman:我们有 utcnow 的 mysql 实现吗?我在文档中只有 mssql 和 postreg 这确实是一个很好的答案! server_default=func.now() 为我工作,但 onupdate=func.now() 没有。试过onupdate=datetime.datetime.utcnow(不带括号),也没有帮助【参考方案2】:

DateTime 没有默认键作为输入。默认键应该是Column 函数的输入。试试这个:

import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
    __tablename__ = 'test'

    id = Column(Integer, primary_key=True)
    created_date = Column(DateTime, default=datetime.datetime.utcnow)

【讨论】:

这是不对的。模型加载时的时间戳将用于所有新记录,而不是添加记录的时间。 @SkyLeach 这个代码是正确的,你可以在这里测试它:pastebin.com/VLyWktUn。 datetime.datetime.utcnow 似乎被称为回调。 我使用了这个答案,但模型加载时的时间戳正用于所有新记录。 @scottdelta:确保你没有使用default=datetime.datetime.utcnow();你想传递utcnow 函数,而不是在模块加载时评估它的结果。 仍然对我不起作用。 jeff-widman 的方法对我有用:from sqlalchemy.sql import func; created_date = Column(DateTime(timezone=True), server_default=func.now()【参考方案3】:

您也可以使用 sqlalchemy 内置函数作为默认 DateTime

from sqlalchemy.sql import func

DT = Column(DateTime(timezone=True), default=func.now())

【讨论】:

您也可以使用server_default 代替default,这样值将由数据库本身处理。 在定义描述符时不执行 func.now(),因此所有模型/子项(如果这是基类)将具有相同的 DT 值。通过传入像“datetime.datetime.utcnow”这样的函数引用,它会为每个函数单独执行。这对于“创建”或“更新”属性至关重要。 @Metalstorm 不,这是正确的。 sqlalchemy.sql.func 是一个特殊情况,它返回一个 sqlalchemy.sql.functions.now 实例,而不是当前时间。 docs.sqlalchemy.org/en/latest/core/… timezone=True 是做什么的? @self, From the documentation: "如果为真,并且受后端支持,将生成 'TIMESTAMP WITH TIMEZONE'。对于不支持时区感知时间戳的后端,没有效果。 .【参考方案4】:

您可能希望使用onupdate=datetime.now,以便更新也更改last_updated 字段。

SQLAlchemy 有两个 Python 执行函数的默认值。

default 在 INSERT 上设置值,只设置一次 onupdate 也在 UPDATE 上将值设置为 callable 结果。

【讨论】:

我不知道为什么,但出于某种原因onupdate 没有为我做任何事情。 我终于通过这样做在 Postgres db 上工作了:created_at = db.Column(db.DateTime, server_default=UtcNow())updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow()) 其中UtcNowclass UtcNow(expression.FunctionElement): type = DateTime() 的类,我们有@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)" 我相信sqlalchmey.sql.func.now() 是首选,但不会有太大不同。如果系统是分布式的并且许多不同的用户可能在同一时间进行更改,那么使用 DB 时间而不是服务器时间会稍微好一些 有没有办法禁用对 python 执行函数的 onupdate 调用? docs.sqlalchemy.org/en/14/core/defaults.html 我做了同样的事情,但由于没有将任何 args 传递给 DB 模型中的 UtcNow,我得到了一个错误。【参考方案5】:

default 关键字参数应该给 Column 对象。

例子:

Column(u'timestamp', TIMESTAMP(timezone=True), primary_key=False, nullable=False, default=time_now),

默认值可以是可调用的,我在这里定义如下。

from pytz import timezone
from datetime import datetime

UTC = timezone('UTC')

def time_now():
    return datetime.now(UTC)

【讨论】:

time_now 来自哪里? 谢谢!我假设有一个我以某种方式忽略的名称的内置函数。 datetime.datetime.utcnow from datetime import datetime as dt .... then just dt.utcnow()【参考方案6】:

default 参数与datetime.now 一起使用:

from datetime import datetime
class Test(Base):
     __tablename__ = 'test'
     id = Column(Integer, primary_key=True)
     created_at = Column(DateTime, default=datetime.now)
     updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)                                                            

【讨论】:

请正确格式化您的代码,并解释一下它是如何解决问题的。【参考方案7】:

根据PostgreSQL documentation:

now,CURRENT_TIMESTAMP,LOCALTIMESTAMP返回交易时间

这被认为是一项功能:其目的是允许单个 事务具有一致的“当前”时间概念,因此 同一事务中的多个修改具有相同的时间戳。

如果您不想要交易时间戳,您可能想要使用 statement_timestampclock_timestamp

statement_timestamp()

返回当前语句的开始时间(更具体地说, 收到客户端最新命令消息的时间)。 statement_timestamp

clock_timestamp()

返回实际的当前时间,因此它的值甚至会改变 在单个 SQL 命令中。

【讨论】:

【参考方案8】:

mariadb 这对我有用:

from sqlalchemy import Column, Integer, String, DateTime, TIMESTAMP, text
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
  __tablename__ = "test"

  id              = Column(Integer, primary_key=True, autoincrement=True)
  name            = Column(String(255), nullable=False)
  email           = Column(String(255), nullable=False)
  created_at      = Column(TIMESTAMP, nullable=False, server_default=func.now())
  updated_at      = Column(DateTime, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))

在mariadb的sqlalchemy documentation中,建议从sqlalchemy本身导入text,并将server_default设置为text,插入自定义命令。

updated_at=Column(DateTime, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))

要了解func.now,您可以阅读 sql alchemy 文档。

希望我能以某种方式提供帮助。

【讨论】:

【参考方案9】:

Jeff Widman 在 his answer 上说,您需要为 func.utcnow() 创建自己的 UTC 时间戳实现

由于我不想自己实现它,所以我搜索并找到了一个 python 包,它已经完成了这项工作并由很多人维护。

包名是spoqa/sqlalchemy-ut。

这个包的作用总结是: 长话短说,UtcDateTime 确实:

只知道datetime.datetime, 只返回知道datetime.datetime, 永远不要天真地接受或返回datetime.datetime, 确保数据库中的时间戳始终以 UTC 编码,并且 按照您的预期工作。

【讨论】:

【参考方案10】:

请注意,server_default=func.now()func.now() 工作:

Local_modified = Column(DateTime, server_default=func.now(), onupdate=func.now())

您需要在表格 DDL 中设置DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

例如

create table test
(
    id int auto_increment
        primary key,
    source varchar(50) null,
    Local_modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
collate=utf8mb4_bin;

否则,server_default=func.now(), onupdate=func.now() 无效。

【讨论】:

以上是关于SQLAlchemy 默认日期时间的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy - 将日期时间设置为字符串并获取日期?

在 SQLAlchemy 中以特定格式按日期分组

如何在 SqlAlchemy / Mysql 中存储时区感知日期时间值?

SQLAlchemy 混合属性日期时间到假期

SQLAlchemy,如何将混合表达式设置为“理解”日期时间?

python UTC日期和sqlalchemy