SQLAlchemy 0.6 中的新功能是什么?
关于本文档
本文档描述了 SQLAlchemy 版本 0.5(上次发布于 2010 年 1 月 16 日)与 SQLAlchemy 版本 0.6(上次发布于 2012 年 5 月 5 日)之间的变化。
文档日期:2010 年 6 月 6 日
本指南记录了影响用户将其应用程序从 SQLAlchemy 0.5 系列迁移到 0.6 版本的 API 更改。请注意,SQLAlchemy 0.6 移除了一些在 0.5 系列期间已弃用的行为,并且还弃用了更多与 0.5 版本特定的行为。
平台支持
- cPython 版本 2.4 及以上,遍及 2.xx 系列
- Jython 2.5.1 - 使用 Jython 随附的 zxJDBC DBAPI。
- cPython 3.x - 有关如何为 python3 构建的信息,请参见 [source:sqlalchemy/trunk/README.py3k]。
新的方言系统
方言模块现在被分解为单个数据库后端范围内的不同子组件。方言实现现在位于 sqlalchemy.dialects
包中。sqlalchemy.databases
包仍然存在作为占位符,以提供一定程度的向后兼容性,用于简单的导入。
对于每个受支持的数据库,在 sqlalchemy.dialects
中都存在一个子包,其中包含几个文件。每个包包含一个名为 base.py
的模块,该模块定义了该数据库使用的特定 SQL 方言。它还包含一个或多个“driver”模块,每个模块对应一个特定的 DBAPI - 这些文件的名称与 DBAPI 本身相对应,例如 pysqlite
、cx_oracle
或 pyodbc
。SQLAlchemy 方言使用的类首先在 base.py
模块中声明,定义数据库定义的所有行为特征。这些包括能力映射,例如“支持序列”,“支持返回”等,类型定义和 SQL 编译规则。然后,每个“driver”模块根据需要提供那些类的子类,这些子类覆盖默认行为,以适应该 DBAPI 的附加功能、行为和怪癖。对于支持多个后端的 DBAPI(如 pyodbc、zxJDBC、mxODBC),方言模块将使用来自 sqlalchemy.connectors
包的混合物,这些混合物提供了跨所有后端的该 DBAPI 的功能,最常见的是处理连接参数。这意味着使用 pyodbc、zxJDBC 或 mxODBC(当实现时)进行连接在受支持的后端上是非常一致的。
create_engine()
使用的 URL 格式已经增强,以处理特定后端的任意数量的 DBAPI,使用的方案受到 JDBC 的启发。以前的格式仍然有效,并且将选择“默认”DBAPI 实现,例如下面的 PostgreSQL URL 将使用 psycopg2:
create_engine("postgresql://scott:tiger@localhost/test")
但是,要指定特定的 DBAPI 后端,比如 pg8000,请将其添加到 URL 的“protocol”部分,使用加号“+”:
create_engine("postgresql+pg8000://scott:tiger@localhost/test")
重要的方言链接:
- 连接参数的文档:
www.sqlalchemy.org/docs/06/dbengine.html#create
- engine-url-arguments。 - 个别方言的参考文档:
ww
w.sqlalchemy.org/docs/06/reference/dialects/index.html - DatabaseNotes 上的技巧和窍门。
关于方言的其他注意事项:
- SQLAlchemy 0.6 中类型系统发生了巨大变化。这对所有方言都有影响,包括命名约定、行为和实现。请参阅下面关于“类型”的部分。
ResultProxy
对象现在在某些情况下提供了 2 倍的速度改进,这要归功于一些重构。RowProxy
,即单个结果行对象,现在可以直接进行 pickle。- setuptools 入口点现在用于定位外部方言的名称是
sqlalchemy.dialects
。针对 0.4 或 0.5 编写的外部方言需要修改以适应 0.6,在任何情况下这个改变并不增加任何额外的困难。 - 方言现在在初始连接时接收一个 initialize()事件来确定连接属性。
- 编译器生成的函数和操作符现在使用(几乎)常规的调度函数形式“visit_”和“visit__fn”来提供定制处理。这取代了在编译器子类中复制“functions”和“operators”字典的需要,改为使用直接的访问者方法,同时也允许编译器子类完全控制渲染,因为完整的 _Function 或 _BinaryExpression 对象被传递进来。
方言导入
方言的导入结构已经改变。每个方言现在通过sqlalchemy.dialects.
导出其基本的“dialect”类以及该方言支持的完整一组 SQL 类型。例如,要导入一组 PG 类型:
from sqlalchemy.dialects.postgresql import ( INTEGER, BIGINT, SMALLINT, VARCHAR, MACADDR, DATE, BYTEA, )
在上面,INTEGER
实际上是来自sqlalchemy.types
的普通INTEGER
类型,但 PG 方言使其以与那些特定于 PG 的类型相同的方式可用,例如BYTEA
和MACADDR
。
表达式语言变化
一个重要的表达式语言陷阱
表达式语言有一个相当重要的行为变化,可能会影响一些应用程序。Python 布尔表达式的布尔值,即==
、!=
等,现在在比较两个子句对象时会准确评估。
正如我们所知,将ClauseElement
与任何其他对象进行比较会返回另一个ClauseElement
:
>>> from sqlalchemy.sql import column >>> column("foo") == 5 <sqlalchemy.sql.expression._BinaryExpression object at 0x1252490>
这样 Python 表达式在转换为字符串时会产生 SQL 表达式:
>>> str(column("foo") == 5) 'foo = :foo_1'
但是如果我们这样说会发生什么?
>>> if column("foo") == 5: ... print("yes")
在之前的 SQLAlchemy 版本中,返回的_BinaryExpression
是一个普通的 Python 对象,其求值为True
。现在它的求值取决于实际的ClauseElement
是否应该具有与被比较的相同哈希值。意思是:
>>> bool(column("foo") == 5) False >>> bool(column("foo") == column("foo")) False >>> c = column("foo") >>> bool(c == c) True >>>
这意味着如下代码:
if expression: print("the expression is:", expression)
如果 expression
是二进制子句,则不会被评估。由于上述模式永远不应该被使用,因此基本的 ClauseElement
现在在布尔上下文中调用时会引发异常:
>>> bool(c) Traceback (most recent call last): File "<stdin>", line 1, in <module> ... raise TypeError("Boolean value of this clause is not defined") TypeError: Boolean value of this clause is not defined
希望检查 ClauseElement
表达式是否存在的代码应该这样说:
if expression is not None: print("the expression is:", expression)
请记住,这也适用于 Table 和 Column 对象。
更改的理由有两个:
- 形如
if c1 == c2:
的比较现在可以这样写了。 - 现在支持在其他平台(即 Jython)上正确对
ClauseElement
对象进行哈希处理。直到这一点,SQLAlchemy 在这方面严重依赖 cPython 的特定行为(并且仍然偶尔出现问题)。
更严格的“executemany”行为
SQLAlchemy 中的“executemany”对应于调用 execute()
,传递一系列绑定参数集:
connection.execute(table.insert(), {"data": "row1"}, {"data": "row2"}, {"data": "row3"})
当 Connection
对象发送给定的 insert()
构造进行编译时,它传递给编译器传递的第一组绑定中存在的键名,以确定语句的 VALUES 子句的构造。熟悉这种构造的用户会知道,剩余字典中存在的额外键没有任何影响。现在的不同之处在于,所有后续字典都需要至少包含第一个字典中存在的每个键。这意味着像这样的调用不再起作用:
connection.execute( table.insert(), {"timestamp": today, "data": "row1"}, {"timestamp": today, "data": "row2"}, {"data": "row3"}, )
因为第三行没有指定timestamp
列。之前的 SQLAlchemy 版本会简单地为这些缺失的列插入 NULL。然而,如果上面示例中的 timestamp
列包含 Python 端的默认值或函数,则不会被使用。这是因为“executemany”操作针对大量参数集的最大性能进行了优化,并且不会尝试评估那些缺失键的 Python 端默认值。由于默认值通常被实现为嵌入在 INSERT 语句中的 SQL 表达式,或者是服务器端表达式,再次根据 INSERT 字符串的结构触发,这些默认值无法根据每个参数集有条件地触发,因此 Python 端默认值与 SQL/服务器端默认值的行为不同是不一致的。(基于 SQL 表达式的默认值从 0.5 系列开始嵌入在内部,再次为了最小化大量参数集的影响)。
因此,SQLAlchemy 0.6 通过禁止任何后续参数集留下任何字段空白来建立可预测的一致性。这样,Python 端默认值和函数就不会再默默失败,而且它们的行为与 SQL 和服务器端默认值保持一致。
UNION 和其他“复合”结构都有一致的括号配对。
为了帮助 SQLite 而设计的规则已被移除,即在另一个复合元素内的第一个复合元素(例如,在except_()
内部的union()
)不会被括号括起来。这是不一致的,并且在 PostgreSQL 上产生错误的结果,因为它对 INTERSECTION 有优先规则,这通常会让人感到惊讶。在与 SQLite 使用复杂复合时,现在需要将第一个元素转换为子查询(这也在 PG 上兼容)。在 SQL 表达式教程的最后有一个新的示例[www.sqlalchemy.org/docs/06/sqlexpression.html
#unions-and-other-set-operations]。更多背景信息请参见#1665和 r6690。
用于结果提取的 C 扩展
ResultProxy
及其相关元素,包括大多数常见的“行处理”函数,如 Unicode 转换、数值/布尔转换和日期解析,已经被重新实现为可选的 C 扩展,以提高性能。这标志着 SQLAlchemy 走向“黑暗面”的开始,我们希望通过在 C 中重新实现关键部分来持续改进性能。可以通过指定--with-cextensions
来构建这些扩展,即python setup.py --with- cextensions install
。
这些扩展对使用直接ResultProxy
访问的结果提取影响最为显著,即由engine.execute()
、connection.execute()
或session.execute()
返回的结果。在 ORM Query
对象返回的结果中,结果提取不占很高的开销比例,因此 ORM 性能改善较为适度,主要体现在提取大型结果集方面。性能改进高度依赖于使用的 dbapi 以及访问每行列的语法(例如,row['name']
比row.name
快得多)。当前的扩展对插入/更新/删除的速度没有影响,也不会改善 SQL 执行的延迟,也就是说,一个大部分时间用于执行许多语句且结果集非常小的应用程序不会看到太多改进。
与扩展无关,0.6 版本的性能比 0.5 版本有所提高。使用 SQLite 连接和提取 50000 行的快速概述,主要使用直接的 SQLite 访问、ResultProxy
和简单的映射 ORM 对象:
sqlite select/native: 0.260s 0.6 / C extension sqlalchemy.sql select: 0.360s sqlalchemy.orm fetch: 2.500s 0.6 / Pure Python sqlalchemy.sql select: 0.600s sqlalchemy.orm fetch: 3.000s 0.5 / Pure Python sqlalchemy.sql select: 0.790s sqlalchemy.orm fetch: 4.030s
在上述情况下,ORM 由于 Python 中的性能增强,比 0.5 版本快 33%。使用 C 扩展,我们又获得了 20%的提升。然而,与不使用 C 扩展相比,ResultProxy
的提取速度提高了 67%。其他测试报告显示,在某些场景中,如发生大量字符串转换的情况下,速度提高了多达 200%。
新的模式功能
sqlalchemy.schema
包得到了一些长期需要的关注。最显著的变化是新扩展的 DDL 系统。在 SQLAlchemy 中,自版本 0.5 以来,可以创建自定义的 DDL 字符串并将其与表或元数据对象关联:
from sqlalchemy.schema import DDL DDL("CREATE TRIGGER users_trigger ...").execute_at("after-create", metadata)
现在,完整的 DDL 构造都在同一系统下可用,包括用于 CREATE TABLE、ADD CONSTRAINT 等的构造:
from sqlalchemy.schema import Constraint, AddConstraint AddContraint(CheckConstraint("value > 5")).execute_at("after-create", mytable)
此外,所有 DDL 对象现在都是常规的ClauseElement
对象,就像任何其他 SQLAlchemy 表达式对象一样:
from sqlalchemy.schema import CreateTable create = CreateTable(mytable) # dumps the CREATE TABLE as a string print(create) # executes the CREATE TABLE statement engine.execute(create)
并且使用sqlalchemy.ext.compiler
扩展,你可以自己制作:
from sqlalchemy.schema import DDLElement from sqlalchemy.ext.compiler import compiles class AlterColumn(DDLElement): def __init__(self, column, cmd): self.column = column self.cmd = cmd @compiles(AlterColumn) def visit_alter_column(element, compiler, **kw): return "ALTER TABLE %s ALTER COLUMN %s %s ..." % ( element.column.table.name, element.column.name, element.cmd, ) engine.execute(AlterColumn(table.c.mycolumn, "SET DEFAULT 'test'"))
废弃/移除的模式元素
模式包也经过了大幅简化。在整个 0.5 版本中被废弃的许多选项和方法已被移除。其他鲜为人知的访问器和方法也已被移除。
- “owner”关键字参数从
Table
中移除。使用“schema”表示要预置到表名之前的任何命名空间。 - 废弃的
MetaData.connect()
和ThreadLocalMetaData.connect()
已被移除 - 将“bind”属性发送到绑定元数据。 - 废弃的 metadata.table_iterator()方法已移除(使用 sorted_tables)
- 从
DefaultGenerator
和子类中移除了“metadata”参数,但在Sequence
上仍然本地存在,Sequence
是 DDL 中的一个独立构造。 - 废弃的
PassiveDefault
- 使用DefaultClause
。 - 从
Index
和Constraint
对象中移除了公共可变性:
ForeignKeyConstraint.append_element()
Index.append_column()
UniqueConstraint.append_column()
PrimaryKeyConstraint.add()
PrimaryKeyConstraint.remove()
这些应该以声明方式构建(即一次构建)。
- 其他已移除的内容:
Table.key
(不知道这是干什么的)Column.bind
(通过 column.table.bind 获取)Column.metadata
(通过 column.table.metadata 获取)Column.sequence
(使用 column.default)
其他行为变化
UniqueConstraint
,Index
,PrimaryKeyConstraint
都接受列名或列对象的列表作为参数。ForeignKey
上的use_alter
标志现在是一个快捷选项,用于可以使用DDL()
事件系统手动构建的操作。这次重构的一个副作用是,具有use_alter=True
的ForeignKeyConstraint
对象将不会在 SQLite 上发出,因为 SQLite 不支持外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上不遵守外键约束。Table.primary_key
不可分配 - 使用table.append_constraint(PrimaryKeyConstraint(...))
- 具有
ForeignKey
但没有类型的Column
定义,例如Column(name, ForeignKey(sometable.c.somecol))
曾经可以获得引用列的类型。现在对于该自动类型推断的支持是部分的,可能不适用于所有情况。
日志打开了
通过在引擎、池或映射器创建后进行一些额外的方法调用,您可以设置 INFO 和 DEBUG 的日志级别,日志记录将开始。现在,isEnabledFor(INFO)方法将每个 Connection 调用一次,isEnabledFor(DEBUG)将每个 ResultProxy 调用一次,如果已在父连接上启用。池日志发送到 log.info()和 log.debug(),无需检查 - 请注意,池的检出/归还通常是每个事务一次。
反射/检查器 API
反射系统,允许通过 Table(‘sometable’, metadata, autoload=True)反射表列的系统已经开放为自己的细粒度 API,允许直接检查数据库元素,如表、列、约束、索引等。这个 API 将返回值表达为简单的字符串列表、字典和 TypeEngine 对象。现在,autoload=True 的内部构建在这个系统之上,将原始数据库信息转换为 sqlalchemy.schema 构造集中化,各个方言的契约大大简化,大大减少了不同后端之间的错误和不一致性。
要使用检查器:
from sqlalchemy.engine.reflection import Inspector insp = Inspector.from_engine(my_engine) print(insp.get_schema_names())
在某些情况下,from_engine()方法将提供一个特定于后端的检查器,具有额外的功能,例如 PostgreSQL 提供了一个 get_table_oid()方法:
my_engine = create_engine("postgresql://...") pg_insp = Inspector.from_engine(my_engine) print(pg_insp.get_table_oid("my_table"))
RETURNING 支持
insert()、update()和 delete()构造现在支持一个 returning()方法,对应于 SQL RETURNING 子句,支持 PostgreSQL、Oracle、MS-SQL 和 Firebird。目前不支持其他后端。
给定一个与 select()构造相同方式的列表达式列表,这些列的值将作为常规结果集返回:
result = connection.execute( table.insert().values(data="some data").returning(table.c.id, table.c.timestamp) ) row = result.first() print("ID:", row["id"], "Timestamp:", row["timestamp"])
四个支持后端的 RETURNING 实现差异很大,Oracle 需要复杂使用 OUT 参数,这些参数被重新路由到一个“模拟”结果集中,而 MS-SQL 使用笨拙的 SQL 语法。RETURNING 的使用受到限制:
- 它不适用于任何“executemany()”风格的执行。这是所有支持的 DBAPI 的限制。
- 一些后端,比如 Oracle,只支持返回单行的 RETURNING - 这包括 UPDATE 和 DELETE 语句,这意味着 update()或 delete()构造必须仅匹配单行,否则会引发错误(由 Oracle 而不是 SQLAlchemy 引发)。
当单行 INSERT 语句需要获取新生成的主键值时,SQLAlchemy 也会自动使用 RETURNING,当没有通过显式的returning()
调用另行指定时。这意味着在需要主键值的插入语句中不再需要“SELECT nextval(sequence)”预执行。说实话,隐式 RETURNING 功能确实比旧的“select nextval()”系统产生更多的方法开销,后者使用快速而简单的 cursor.execute()来获取序列值,并且在 Oracle 的情况下需要额外绑定输出参数。因此,如果方法/协议开销比额外的数据库往返更昂贵,可以通过在create_engine()
中指定implicit_returning=False
来禁用该功能。
类型系统更改
新架构
类型系统在幕后完全重建,以实现两个目标:
- 将绑定参数和结果行值的处理分开,通常是 DBAPI 的要求,与类型本身的 SQL 规范分开,这是与总体方言重构一致的,将数据库 SQL 行为与 DBAPI 分开。
- 为从
TypeEngine
对象生成 DDL 和基于列反射构造TypeEngine
对象建立明确一致的契约。
这些变化的亮点包括:
- 方言中类型的构建已经彻底改变。方言现在专门将公开可用的类型定义为大写名称,并使用下划线标识符(即私有)来定义内部实现类型。用于在 SQL 和 DDL 中表达类型的系统已移至编译器系统。这样做的效果是大多数方言中的类型对象要少得多。有关此架构的详细文档供方言作者参考在[source:/lib/sqlalchemy/dialects/type_migration_guidelines.txt]中。
- 现在类型的反射返回 types.py 中的确切大写类型,或者如果类型不是标准 SQL 类型,则返回方言本身中的大写类型。这意味着反射现在返回有关反射类型的更准确信息。
- 用户定义的类型,如果要提供
get_col_spec()
方法,现在应该继承UserDefinedType
。 - 所有类型类上的
result_processor()
方法现在接受额外的参数coltype
。这是附加到 cursor.description 的 DBAPI 类型对象,应在适用时使用,以便更好地决定应返回什么类型的结果处理可调用函数。理想情况下,结果处理函数永远不应该使用isinstance()
,因为在这个级别上这是一个昂贵的调用。
本地 Unicode 模式
随着更多的 DBAPI 支持直接返回 Python Unicode 对象,基本方言现在在第一次连接时执行检查,以确定 DBAPI 是否为基本的 VARCHAR 值的基本选择返回 Python Unicode 对象。如果是这样,String
类型和所有子类(即Text
,Unicode
等)将在接收到结果行时跳过“unicode”检查/转换步骤。这为大型结果集提供了显著的性能提升。目前已知“unicode 模式”可以与以下一起使用:
- sqlite3 / pysqlite
- psycopg2 - SQLA 0.6 现在默认在每个 psycopg2 连接对象上使用“UNICODE”类型扩展
- pg8000
- cx_oracle(我们使用输出处理器 - 很好的功能!)
其他类型可能会根据需要禁用 Unicode 处理,例如在与 MS-SQL 一起使用时的NVARCHAR
类型。
特别是,如果迁移基于以前返回非 Unicode 字符串的 DBAPI 的应用程序,则“本地 Unicode”模式具有明显不同的默认行为 - 声明为String
或VARCHAR
的列现在默认返回 Unicode,而以前会返回字符串。这可能会破坏期望非 Unicode 字符串的代码。可以通过向create_engine()
传递use_native_unicode=False
来禁用 psycopg2 的“本地 Unicode”模式。
一个更通用的解决方案是针对明确不想要 Unicode 对象的字符串列使用TypeDecorator
,将 Unicode 转换回 utf-8,或者其他所需的格式:
class UTF8Encoded(TypeDecorator): """Unicode type which coerces to utf-8.""" impl = sa.VARCHAR def process_result_value(self, value, dialect): if isinstance(value, unicode): value = value.encode("utf-8") return value
请注意,assert_unicode
标志现已弃用。SQLAlchemy 允许 DBAPI 和后端数据库在可用时处理 Unicode 参数,并且不会通过检查传入类型增加操作开销;现代系统如 sqlite 和 PostgreSQL 会在其端引发编码错误,如果传递了无效数据。在 SQLAlchemy 确实需要将绑定参数从 Python Unicode 强制转换为编码字符串时,或者显式使用 Unicode 类型时,如果对象是字节串,则会发出警告。可以使用 Python 警告过滤器文档中记录的警告来抑制或将其转换为异常:docs.python.org/library/warnings.html
通用枚举类型
现在在 types
模块中有一个 Enum
。这是一个字符串类型,给定一组“标签”,限制了给这些标签赋予的可能值。默认情况下,该类型生成一个VARCHAR
,使用最大标签的大小,并在 CREATE TABLE 语句中对表应用 CHECK 约束。当使用 MySQL 时,默认情况下,该类型使用 MySQL 的 ENUM 类型;当使用 PostgreSQL 时,该类型将使用 CREATE TYPE AS ENUM
生成用户定义类型。为了使用 PostgreSQL 创建类型,必须在构造函数中指定 name
参数。该类型还接受一个 native_enum=False
选项,该选项将为所有数据库发出 VARCHAR/CHECK 策略。请注意,当前 PostgreSQL ENUM 类型不能与 pg8000 或 zxjdbc 一起使用。
反射返回方言特定类型
反射现在从数据库返回尽可能最具体的类型。也就是说,如果您使用 String
创建一个表,然后反射它,那么反射的列可能是 VARCHAR
。对于支持更特定形式类型的方言,您将得到相应的类型。因此,在 Oracle 上,Text
类型将返回为 oracle.CLOB
,LargeBinary
可能是 mysql.MEDIUMBLOB
等等。这里的明显优势是反射尽可能地保留了数据库要说的信息。
一些处理表元数据的应用程序可能希望比较反映的表和/或非反射的表上的类型。TypeEngine
上有一个半私有访问器叫做 _type_affinity
,以及一个相关的比较助手 _compare_type_affinity
。此访问器返回类型对应的“通用” types
类:
>>> String(50)._compare_type_affinity(postgresql.VARCHAR(50)) True >>> Integer()._compare_type_affinity(mysql.REAL) False
杂项 API 更改
通常的“通用”类型仍然是正在使用的一般系统,即 String
、Float
、DateTime
。在那里有一些变化:
- 类型不再猜测默认参数。特别是,
Numeric
、Float
,以及 NUMERIC、FLOAT、DECIMAL 的子类不生成长度或比例,除非指定。这也包括有争议的String
和VARCHAR
类型(尽管 MySQL 方言在要求不带长度渲染 VARCHAR 时会预先引发)。不假设默认值,如果它们在 CREATE TABLE 语句中使用,则在底层数据库不允许这些类型的非长度版本时会引发错误。 Binary
类型已更名为LargeBinary
,用于 BLOB/BYTEA/类似类型。对于BINARY
和VARBINARY
,它们直接存在于types.BINARY
、types.VARBINARY
,以及 MySQL 和 MS-SQL 方言中。- 当 mutable=True 时,
PickleType
现在使用 == 比较值,除非为该类型指定了带有比较函数的“comparator”参数。如果您要 pickle 自定义对象,应该实现一个__eq__()
方法,以便基于值的比较准确。 - Numeric 和 Float 的默认“precision”和“scale”参数已被移除,并且现在默认为 None。NUMERIC 和 FLOAT 默认情况下将不带有数值参数呈现,除非提供了这些值。
- SQLite 上的 DATE、TIME 和 DATETIME 类型现在可以接受可选的“storage_format”和“regexp”参数。“storage_format”可用于使用自定义字符串格式存储这些类型。“regexp”允许使用自定义正则表达式来匹配数据库中的字符串值。
- 不再支持 SQLite 上
Time
和DateTime
类型的__legacy_microseconds__
。你应该使用新的 “storage_format” 参数。 - SQLite 上的
DateTime
类型现在默认使用更严格的正则表达式来匹配数据库中的字符串。如果你使用存储在传统格式中的数据,请使用新的 “regexp” 参数。
SqlAlchemy 2.0 中文文档(八十)(2)https://developer.aliyun.com/article/1559880