SqlAlchemy 2.0 中文文档(七十六)(1)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: SqlAlchemy 2.0 中文文档(七十六)


原文:docs.sqlalchemy.org/en/20/contents.html

SQLAlchemy 1.0 中的新功能?

原文:docs.sqlalchemy.org/en/20/changelog/migration_10.html

关于本文档

本文档描述了 SQLAlchemy 版本 0.9 与 2014 年 5 月维护发布的版本之间的更改,以及于 2015 年 4 月发布的版本 1.0 之间的更改。

文档最后更新日期:2015 年 6 月 9 日

介绍

本指南介绍了 SQLAlchemy 版本 1.0 中的新功能,并记录了影响用户将其应用程序从 SQLAlchemy 0.9 系列迁移到 1.0 的更改。

请仔细查看行为变化部分,可能会有不兼容的行为变化。

新功能和改进 - ORM

新会话批量插入/更新 API

创建了一系列新的 Session 方法,直接提供钩子进入工作单元的发出 INSERT 和 UPDATE 语句的功能。当正确使用时,这个面向专家的系统可以允许使用 ORM 映射生成批量插入和更新语句批量执行,使语句以与直接使用 Core 相媲美的速度进行。

另请参阅

批量操作 - 介绍和完整文档

#3100

新性能示例套件

受到批量操作功能以及 FAQ 中的如何对 SQLAlchemy  驱动的应用程序进行性能分析?部分进行的基准测试的启发,添加了一个新的示例部分,其中包含几个旨在说明各种核心和 ORM  技术的相对性能特征的脚本。这些脚本按用例组织,并打包在一个单一的控制台界面下,以便可以运行任何组合的演示,输出时间、Python 分析结果和/或  RunSnake 分析显示。

另请参阅

性能

“烘焙”查询

“烘焙”查询功能是一种不同寻常的新方法,允许使用缓存直接构建和调用 Query 对象,通过连续调用大大减少了 Python 函数调用开销(超过 75%)。通过将一个 Query 对象指定为一系列仅调用一次的 lambda,查询作为一个预编译单元开始变得可行:

from sqlalchemy.ext import baked
from sqlalchemy import bindparam
bakery = baked.bakery()
def search_for_user(session, username, email=None):
    baked_query = bakery(lambda session: session.query(User))
    baked_query += lambda q: q.filter(User.name == bindparam("username"))
    baked_query += lambda q: q.order_by(User.id)
    if email:
        baked_query += lambda q: q.filter(User.email == bindparam("email"))
    result = baked_query(session).params(username=username, email=email).all()
    return result

另请参阅

烘焙查询

#3054

改进声明性混合,@declared_attr 和相关功能

声明式系统与declared_attr结合进行了大幅改进,以支持新的功能。

declared_attr修饰的函数现在仅在生成基于混合的列副本之后才被调用。这意味着该函数可以调用混合建立的列,并将接收到正确的Column对象的引用:

class HasFooBar(object):
    foobar = Column(Integer)
    @declared_attr
    def foobar_prop(cls):
        return column_property("foobar: " + cls.foobar)
class SomeClass(HasFooBar, Base):
    __tablename__ = "some_table"
    id = Column(Integer, primary_key=True)

在上述示例中,SomeClass.foobar_prop将针对SomeClass调用,并且SomeClass.foobar将是要映射到SomeClass的最终Column对象,而不是直接出现在HasFooBar上的非副本对象,即使列尚未映射。

declared_attr函数现在会根据每个类缓存返回的值,这样对同一属性的重复调用将返回相同的值。我们可以修改示例来说明这一点:

class HasFooBar(object):
    @declared_attr
    def foobar(cls):
        return Column(Integer)
    @declared_attr
    def foobar_prop(cls):
        return column_property("foobar: " + cls.foobar)
class SomeClass(HasFooBar, Base):
    __tablename__ = "some_table"
    id = Column(Integer, primary_key=True)

以前,SomeClass将使用foobar列的特定副本进行映射,但通过第二次调用foobar来调用foobar_prop将会产生不同的列。在声明性设置时间内,SomeClass.foobar的值现在被记忆,因此即使在属性由映射器映射之前,每次调用declared_attr时,中间列值都将保持一致。

上述两种行为应该极大地帮助声明式定义许多从其他属性派生的映射器属性类型,其中declared_attr函数是从其他本地declared_attr函数调用的,这些函数在类实际映射之前出现。

对于一个相当特殊的边缘情况,其中希望构建一个声明性混合类,为每个子类建立不同的列,添加了一个新的修饰符 declared_attr.cascading。使用此修饰符,装饰的函数将为映射继承层次结构中的每个类单独调用。虽然对于特殊属性如 __table_args____mapper_args__,这已经是行为,但对于列和其他属性,默认情况下假定该属性仅附加到基类,并且仅从子类继承。使用 declared_attr.cascading,可以应用个别行为:

class HasIdMixin(object):
    @declared_attr.cascading
    def id(cls):
        if has_inherited_table(cls):
            return Column(ForeignKey("myclass.id"), primary_key=True)
        else:
            return Column(Integer, primary_key=True)
class MyClass(HasIdMixin, Base):
    __tablename__ = "myclass"
    # ...
class MySubClass(MyClass):
  """ """
    # ...

另请参见

使用 _orm.declared_attr() 生成特定表继承列

最后,AbstractConcreteBase 类已经重新设计,以便在抽象基类上内联设置关系或其他映射器属性:

from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import (
    declarative_base,
    declared_attr,
    AbstractConcreteBase,
)
Base = declarative_base()
class Something(Base):
    __tablename__ = "something"
    id = Column(Integer, primary_key=True)
class Abstract(AbstractConcreteBase, Base):
    id = Column(Integer, primary_key=True)
    @declared_attr
    def something_id(cls):
        return Column(ForeignKey(Something.id))
    @declared_attr
    def something(cls):
        return relationship(Something)
class Concrete(Abstract):
    __tablename__ = "cca"
    __mapper_args__ = {"polymorphic_identity": "cca", "concrete": True}

上述映射将设置一个带有 idsomething_id 列的表 cca,并且 Concrete 还将具有一个名为 something 的关系。新功能是 Abstract 也将具有一个独立配置的关系 something,该关系构建在基类的多态联合上。

#3150 #2670 #3149 #2952 #3050

ORM 完整对象获取速度提高 25%

loading.py 模块的机制以及标识映射已经经历了几次内联、重构和修剪,因此现在原始行的加载速度大约快了 25%。假设有一个包含 100 万行的表,下面的脚本演示了改进最多的加载类型:

import time
from sqlalchemy import Integer, Column, create_engine, Table
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Foo(Base):
    __table__ = Table(
        "foo",
        Base.metadata,
        Column("id", Integer, primary_key=True),
        Column("a", Integer(), nullable=False),
        Column("b", Integer(), nullable=False),
        Column("c", Integer(), nullable=False),
    )
engine = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo=True)
sess = Session(engine)
now = time.time()
# avoid using all() so that we don't have the overhead of building
# a large list of full objects in memory
for obj in sess.query(Foo).yield_per(100).limit(1000000):
    pass
print("Total time: %d" % (time.time() - now))

本地 MacBookPro 结果从 0.9 秒降至 1.0 秒的时间为 19 秒,降至 14 秒。在批量处理大量行时,Query.yield_per() 的调用总是一个好主意,因为它可以防止 Python 解释器一次性为所有对象及其仪器分配大量内存。没有 Query.yield_per(),在 MacBookPro 上的上述脚本在 0.9 上需要 31 秒,在 1.0 上需要 26 秒,额外的时间用于设置非常大的内存缓冲区。

新的 KeyedTuple 实现速度显著提高

我们研究了 KeyedTuple 实现,希望改进这样的查询:

rows = sess.query(Foo.a, Foo.b, Foo.c).all()

使用 KeyedTuple 类而不是 Python 的 collections.namedtuple(),因为后者具有一个非常复杂的类型创建例程,比 KeyedTuple 慢得多。然而,当获取数十万行时,collections.namedtuple() 很快超过 KeyedTuple,随着实例调用次数的增加,KeyedTuple  的速度会急剧变慢。怎么办?一种新类型,介于两者之间的方法。对于“size”(返回的行数)和“num”(不同查询的数量)对所有三种类型进行测试,新的“轻量级键值元组”要么优于两者,要么略逊于更快的对象,具体取决于情况。在“甜蜜点”上,我们既创建了大量新类型,又获取了大量行,轻量级对象完全超过了  namedtuple 和 KeyedTuple:

-----------------
size=10 num=10000                 # few rows, lots of queries
namedtuple: 3.60302400589         # namedtuple falls over
keyedtuple: 0.255059957504        # KeyedTuple very fast
lw keyed tuple: 0.582715034485    # lw keyed trails right on KeyedTuple
-----------------
size=100 num=1000                 # <--- sweet spot
namedtuple: 0.365247011185
keyedtuple: 0.24896979332
lw keyed tuple: 0.0889317989349   # lw keyed blows both away!
-----------------
size=10000 num=100
namedtuple: 0.572599887848
keyedtuple: 2.54251694679
lw keyed tuple: 0.613876104355
-----------------
size=1000000 num=10               # few queries, lots of rows
namedtuple: 5.79669594765         # namedtuple very fast
keyedtuple: 28.856498003          # KeyedTuple falls over
lw keyed tuple: 6.74346804619     # lw keyed trails right on namedtuple

#3176### 结构化内存使用方面的显著改进

通过对许多内部对象更显著地使用 __slots__,改进了结构化内存使用。这种优化特别针对具有大量表和列的大型应用程序的基本内存大小,并减少了各种高容量对象的内存大小,包括事件监听内部、比较器对象以及 ORM 属性和加载器策略系统的部分。

一个使用 heapy 测量 Nova 启动大小的工作台展示了 SQLAlchemy 对象、相关字典以及弱引用在“nova.db.sqlalchemy.models”基本导入中占用的空间减少了约 3.7 兆字节,或者说减少了 46%:

# reported by heapy, summation of SQLAlchemy objects +
# associated dicts + weakref-related objects with core of Nova imported:
    Before: total count 26477 total bytes 7975712
    After: total count 18181 total bytes 4236456
# reported for the Python module space overall with the
# core of Nova imported:
    Before: Partition of a set of 355558 objects. Total size = 61661760 bytes.
    After: Partition of a set of 346034 objects. Total size = 57808016 bytes.
```### UPDATE 语句现在在刷新中与 executemany() 批处理
现在可以将 UPDATE 语句批处理到 ORM 刷新中,以更高效的 executemany() 调用执行,类似于 INSERT 语句可以批处理;这将根据以下标准在刷新中调用:
+   两个或更多连续的 UPDATE 语句涉及相同的要修改的列集。
+   语句在 SET 子句中没有嵌入的 SQL 表达式。
+   映射不使用 `mapper.version_id_col`,或者后端方言支持 executemany() 操作的“合理”行数;大多数 DBAPI 现在正确支持这一点。### Session.get_bind() 处理更广泛的继承场景
每当查询或工作单元刷新过程寻找与特定类对应的数据库引擎时,都会调用 `Session.get_bind()` 方法。该方法已经改进,以处理各种继承导向的场景,包括:
+   绑定到一个 Mixin 或抽象类:
    ```py
    class MyClass(SomeMixin, Base):
        __tablename__ = "my_table"
        # ...
    session = Session(binds={SomeMixin: some_engine})
    ```
+   基于表格,分别绑定到继承的具体子类:
    ```py
    class BaseClass(Base):
        __tablename__ = "base"
        # ...
    class ConcreteSubClass(BaseClass):
        __tablename__ = "concrete"
        # ...
        __mapper_args__ = {"concrete": True}
    session = Session(binds={base_table: some_engine, concrete_table: some_other_engine})
    ```
[#3035](https://www.sqlalchemy.org/trac/ticket/3035)  ### 在所有相关的查询情况下,Session.get_bind()将接收到 Mapper
修复了一系列问题,其中`Session.get_bind()`未接收到`Query`的主要`Mapper`,尽管此映射器是 readily available 的(主映射器是与`Query`对象关联的单个映射器,或者替代是与查询关联的第一个映射器)。
当传递给`Session.get_bind()`的`Mapper`对象通常由使用`Session.binds`参数的会话使用,以将映射器与一系列引擎关联(虽然在这种用例中,通常情况下“工作”,因为绑定通常会通过映射的表对象找到),或者更具体地实现一个用户定义的`Session.get_bind()`方法,该方法基于映射器提供一些选择引擎的模式,例如水平分片或所谓的“路由”会话,将查询路由到不同的后端。
这些场景包括:
+   `Query.count()`:
    ```py
    session.query(User).count()
    ```
+   `Query.update()` 和 `Query.delete()`,用于 UPDATE/DELETE 语句以及“fetch”策略所使用的 SELECT:
    ```py
    session.query(User).filter(User.id == 15).update(
        {"name": "foob"}, synchronize_session="fetch"
    )
    session.query(User).filter(User.id == 15).delete(synchronize_session="fetch")
    ```
+   对个别列的查询:
    ```py
    session.query(User.id, User.name).all()
    ```
+   对间接映射(例如`column_property`)的 SQL 函数和其他表达式:
    ```py
    class User(Base):
        ...
        score = column_property(func.coalesce(self.tables.users.c.name, None))
    session.query(func.max(User.score)).scalar()
    ```
[#3227](https://www.sqlalchemy.org/trac/ticket/3227) [#3242](https://www.sqlalchemy.org/trac/ticket/3242) [#1326](https://www.sqlalchemy.org/trac/ticket/1326)  ### .info 字典改进
`InspectionAttr.info` 集合现在可用于从`Mapper.all_orm_descriptors`集合中检索到的每种对象。这包括`hybrid_property`和`association_proxy()`。然而,由于这些对象是类绑定的描述符,必须**分开**从它们附加到的类中访问以获取属性。以下是使用`Mapper.all_orm_descriptors`命名空间进行说明:
```py
class SomeObject(Base):
    # ...
    @hybrid_property
    def some_prop(self):
        return self.value + 5
inspect(SomeObject).all_orm_descriptors.some_prop.info["foo"] = "bar"

它还可作为所有SchemaItem对象(例如ForeignKeyUniqueConstraint等)的构造函数参数,以及剩余的 ORM 构造,如synonym()

#2971

#2963 ### ColumnProperty 构造与别名,order_by 配合效果更好

关于column_property()的各种问题已得到解决,特别是关于aliased()构造以及在 0.9 版本中引入的“按标签排序”逻辑(参见标签构造现在可以单独作为其名称在 ORDER BY 中呈现)。

给定如下映射:

class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
A.b = column_property(select([func.max(B.id)]).where(B.a_id == A.id).correlate(A))

简单的场景中,包含两次“A.b”将无法正确渲染:

print(sess.query(A, a1).order_by(a1.b))

这会导致错误的列排序:

SELECT  a.id  AS  a_id,  (SELECT  max(b.id)  AS  max_1  FROM  b
WHERE  b.a_id  =  a.id)  AS  anon_1,  a_1.id  AS  a_1_id,
(SELECT  max(b.id)  AS  max_2
FROM  b  WHERE  b.a_id  =  a_1.id)  AS  anon_2
FROM  a,  a  AS  a_1  ORDER  BY  anon_1

新的输出:

SELECT  a.id  AS  a_id,  (SELECT  max(b.id)  AS  max_1
FROM  b  WHERE  b.a_id  =  a.id)  AS  anon_1,  a_1.id  AS  a_1_id,
(SELECT  max(b.id)  AS  max_2
FROM  b  WHERE  b.a_id  =  a_1.id)  AS  anon_2
FROM  a,  a  AS  a_1  ORDER  BY  anon_2

还有许多情况下,“order by”逻辑会无法按标签排序,例如如果映射为“多态”:

class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
    type = Column(String)
    __mapper_args__ = {"polymorphic_on": type, "with_polymorphic": "*"}

order_by 将无法使用标签,因为由于多态加载,标签将被匿名化:

SELECT  a.id  AS  a_id,  a.type  AS  a_type,  (SELECT  max(b.id)  AS  max_1
FROM  b  WHERE  b.a_id  =  a.id)  AS  anon_1
FROM  a  ORDER  BY  (SELECT  max(b.id)  AS  max_2
FROM  b  WHERE  b.a_id  =  a.id)

现在,由于按标签排序跟踪了匿名化标签,这现在有效:

SELECT  a.id  AS  a_id,  a.type  AS  a_type,  (SELECT  max(b.id)  AS  max_1
FROM  b  WHERE  b.a_id  =  a.id)  AS  anon_1
FROM  a  ORDER  BY  anon_1

这些修复中还包括了一系列能够破坏aliased()构造状态的 heisenbugs,从而导致标签逻辑再次失败;这些问题也已得到解决。

#3148 #3188

新功能和改进 - 核心

选择/查询 LIMIT / OFFSET 可以指定为任意 SQL 表达式

Select.limit()Select.offset() 方法现在接受任何 SQL 表达式作为参数,而不仅仅是整数值。ORM Query 对象也会将任何表达式传递给底层的 Select 对象。通常情况下,这用于允许传递绑定参数,稍后可以用值替换:

sel = select([table]).limit(bindparam("mylimit")).offset(bindparam("myoffset"))

不支持非整数 LIMIT 或 OFFSET 表达式的方言可能会继续不支持此行为;第三方方言可能还需要修改以利用新行为。当前使用 ._limit._offset 属性的方言将继续对指定为简单整数值的限制/偏移的情况进行处理。但是,当指定 SQL 表达式时,这两个属性在访问时将引发 CompileError。希望支持新功能的第三方方言现在应调用 ._limit_clause._offset_clause 属性以接收完整的 SQL 表达式,而不是整数值。### ForeignKeyConstraint 上的 use_alter 标志(通常)不再需要

MetaData.create_all()MetaData.drop_all() 方法现在将使用一个系统,自动为涉及表之间相互依赖循环的外键约束生成 ALTER 语句,无需指定 ForeignKeyConstraint.use_alter。此外,外键约束现在不再需要具有名称才能通过  ALTER 创建;只有 DROP 操作需要名称。在 DROP 的情况下,该功能将确保只有具有显式名称的约束实际上包含在 ALTER 语句中。在  DROP 中存在无法解决的循环的情况下,如果无法继续执行 DROP,系统现在会发出简洁明了的错误消息。

ForeignKeyConstraint.use_alterForeignKey.use_alter 标志仍然存在,并且继续具有相同的效果,用于在 CREATE/DROP 场景中需要 ALTER 的约束条件的建立。

从版本 1.0.1 开始,在 SQLite 的情况下,特殊逻辑接管,在 DROP 过程中,给定表存在无法解决的循环;在这种情况下会发出警告,并且表将以顺序删除,这在 SQLite 上通常是可以接受的,除非启用了约束。要解决警告并在 SQLite 数据库上至少进行部分排序,特别是在启用了约束的情况下,重新应用“use_alter”标志到那些应明确从排序中省略的 ForeignKeyForeignKeyConstraint 对象。

另请参见

通过 ALTER 创建/删除外键约束 - 新行为的完整描述。

#3282 ### ResultProxy “auto close” 现在是“soft” close

在许多版本中,ResultProxy 对象一直在获取所有结果行后自动关闭。这是为了允许在不需要显式调用 ResultProxy.close() 的情况下使用对象;因为所有的 DBAPI 资源都已被释放,对象可以安全丢弃。然而,对象保持了严格的“closed”行为,这意味着任何后续对 ResultProxy.fetchone()ResultProxy.fetchmany()ResultProxy.fetchall() 的调用现在会引发 ResourceClosedError

>>> result = connection.execute(stmt)
>>> result.fetchone()
(1, 'x')
>>> result.fetchone()
None  # indicates no more rows
>>> result.fetchone()
exception: ResourceClosedError

这种行为与 pep-249 所述的不一致,pep-249 表明,即使结果已经耗尽,也可以重复调用获取方法。它还干扰了某些结果代理的行为,例如 cx_oracle 方言用于某些数据类型的 BufferedColumnResultProxy

为了解决这个问题,ResultProxy 的“closed”状态被分为两个状态;一个“soft  close”执行了“close”大部分功能,释放了 DBAPI 游标,并且在“close with  result”对象的情况下还会释放连接,另一个“closed”状态包括了“soft close”的所有内容以及将获取方法设为“closed”。ResultProxy.close() 方法现在不会隐式调用,只会调用非公开的 ResultProxy._soft_close() 方法:

>>> result = connection.execute(stmt)
>>> result.fetchone()
(1, 'x')
>>> result.fetchone()
None  # indicates no more rows
>>> result.fetchone()
None  # still None
>>> result.fetchall()
[]
>>> result.close()
>>> result.fetchone()
exception: ResourceClosedError  # *now* it raises

#3330 #3329

CHECK 约束现在支持命名约定中的%(column_0_name)s标记

%(column_0_name)s将从CheckConstraint表达式中找到的第一列派生:

metadata = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"})
foo = Table("foo", metadata, Column("value", Integer))
CheckConstraint(foo.c.value > 5)

将呈现:

CREATE  TABLE  foo  (
  value  INTEGER,
  CONSTRAINT  ck_foo_value  CHECK  (value  >  5)
)

命名约束与由SchemaType生成的约束的组合,例如BooleanEnum,现在也将使用所有 CHECK 约束约定。

另请参阅

命名 CHECK 约束

为布尔值、枚举和其他模式类型配置命名

#3299

当引用的列未附加到表时,约束条件可以在其引用的列附加到表时自动附加

自至少版本 0.8 以来,Constraint已经具有根据传递的与表关联的列“自动附加”到Table的能力:

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint
m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer))
uq = UniqueConstraint(t.c.a, t.c.b)  # will auto-attach to Table
assert uq in t.constraints

为了帮助处理在声明性中经常出现的一些情况,即使Column对象尚未与Table关联,这种自动附加逻辑现在也可以运行;建立了额外的事件,以便当这些Column对象关联时,Constraint也被添加:

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint
m = MetaData()
a = Column("a", Integer)
b = Column("b", Integer)
uq = UniqueConstraint(a, b)
t = Table("t", m, a, b)
assert uq in t.constraints  # constraint auto-attached

以上功能是在版本 1.0.0b3 中作为一个晚期添加的。截至版本 1.0.4 的修复#3411确保如果Constraint引用了Column对象和字符串列名的混合,则不会发生此逻辑;因为我们尚未跟踪将名称添加到Table的操作:

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint
m = MetaData()
a = Column("a", Integer)
b = Column("b", Integer)
uq = UniqueConstraint(a, "b")
t = Table("t", m, a, b)
# constraint *not* auto-attached, as we do not have tracking
# to locate when a name 'b' becomes available on the table
assert uq not in t.constraints

在上面的示例中,将列“a”附加到表“t”的附件事件将在附加列“b”之前触发(因为在“b”之前在Table构造函数中声明了“a”),如果尝试附加约束,则约束将无法找到“b”。为了保持一致性,如果约束引用任何字符串名称,则会跳过在列附加时自动附加的逻辑。

Constraint构造时,如果Table已经包含所有目标Column对象,则原始的自动附加逻辑仍然存在:

from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint
m = MetaData()
a = Column("a", Integer)
b = Column("b", Integer)
t = Table("t", m, a, b)
uq = UniqueConstraint(a, "b")
# constraint auto-attached normally as in older versions
assert uq in t.constraints

#3341 #3411 ### INSERT FROM SELECT 现在包括 Python 和 SQL 表达式默认值

如果未指定,默认情况下,Insert.from_select()现在包括 Python 和 SQL 表达式默认值;现在解除了不包括非服务器列默认值在 INSERT FROM SELECT 中的限制,并将这些表达式呈现为常量插入 SELECT 语句中:

from sqlalchemy import Table, Column, MetaData, Integer, select, func
m = MetaData()
t = Table(
    "t", m, Column("x", Integer), Column("y", Integer, default=func.somefunction())
)
stmt = select([t.c.x])
print(t.insert().from_select(["x"], stmt))

将呈现为:

INSERT  INTO  t  (x,  y)  SELECT  t.x,  somefunction()  AS  somefunction_1
FROM  t

可以使用Insert.from_select.include_defaults来禁用该功能。### 现在列服务器默认值呈现为字面值

当由Column.server_default设置为 SQL 表达式的DefaultClause存在时,将打开“literal binds”编译器标志。这允许在 SQL 中嵌入的字面值正确呈现,例如:

from sqlalchemy import Table, Column, MetaData, Text
from sqlalchemy.schema import CreateTable
from sqlalchemy.dialects.postgresql import ARRAY, array
from sqlalchemy.dialects import postgresql
metadata = MetaData()
tbl = Table(
    "derp",
    metadata,
    Column("arr", ARRAY(Text), server_default=array(["foo", "bar", "baz"])),
)
print(CreateTable(tbl).compile(dialect=postgresql.dialect()))

现在呈现为:

CREATE  TABLE  derp  (
  arr  TEXT[]  DEFAULT  ARRAY['foo',  'bar',  'baz']
)

以前,字面值"foo", "bar", "baz"会呈现为绑定参数,在 DDL 中无用。

#3087 ### UniqueConstraint 现在是表反射过程的一部分

使用autoload=True填充的Table对象现在将包括UniqueConstraint构造以及Index构造。对于 PostgreSQL 和 MySQL,这种逻辑有一些注意事项:

PostgreSQL

当创建唯一约束时,PostgreSQL 的行为是隐式地创建一个与该约束对应的唯一索引。Inspector.get_indexes()Inspector.get_unique_constraints() 方法将继续分别返回这些条目,其中 Inspector.get_indexes() 现在在索引条目中特征上带有一个标记 duplicates_constraint,指示检测到的相应约束。然而,当使用 Table(..., autoload=True) 执行完整的表反射时,Index 构造被检测为与 UniqueConstraint 关联,并且不会出现在 Table.indexes 集合中;只有 UniqueConstraint 会出现在 Table.constraints 集合中。这种去重逻辑通过在查询 pg_index 时连接到 pg_constraint 表来实现,以查看这两个结构是否关联。

MySQL

MySQL 没有单独的 UNIQUE INDEX 和 UNIQUE 约束概念。虽然在创建表和索引时支持两种语法,但在存储时并没有任何不同。Inspector.get_indexes()Inspector.get_unique_constraints()方法将继续同时返回 MySQL 中 UNIQUE 索引的条目,其中Inspector.get_unique_constraints()在约束条目中具有一个新的标记duplicates_index,指示这是与该索引对应的重复条目。然而,在使用Table(..., autoload=True)执行完整表反射时,UniqueConstraint构造在任何情况下都是完全反映的Table构造的一部分;这个构造始终由在Table.indexes集合中具有unique=True设置的Index表示。

另请参阅

PostgreSQL Index Reflection

MySQL / MariaDB Unique Constraints and Reflection

#3184

安全地发出参数化警告的新系统

长期以来,存在一个限制,即警告消息不能引用数据元素,这样一个特定函数可能会发出无限数量的唯一警告。这种情况最常见的地方是在Unicode type received non-unicode bind param value警告中。将数据值放入此消息中意味着该模块的 Python __warningregistry__,或在某些情况下是 Python 全局的warnings.onceregistry,将无限增长,因为在大多数警告场景中,这两个集合中的一个会填充每个不同的警告消息。

通过使用特殊的string类型,有意改变字符串的哈希方式,我们可以控制大量参数化消息仅在一小组可能的哈希值上进行哈希,这样一个警告,比如Unicode type received non-unicode bind param value,可以被定制为仅发出特定次数;此后,Python 警告注册表将开始记录它们为重复项。

为了说明,以下测试脚本将仅显示对于 1000 个参数集中的十个参数集发出的十个警告:

from sqlalchemy import create_engine, Unicode, select, cast
import random
import warnings
e = create_engine("sqlite://")
# Use the "once" filter (which is also the default for Python
# warnings).  Exactly ten of these warnings will
# be emitted; beyond that, the Python warnings registry will accumulate
# new values as dupes of one of the ten existing.
warnings.filterwarnings("once")
for i in range(1000):
    e.execute(
        select([cast(("foo_%d" % random.randint(0, 1000000)).encode("ascii"), Unicode)])
    )

这里的警告格式是:

/path/lib/sqlalchemy/sql/sqltypes.py:186: SAWarning: Unicode type received
  non-unicode bind param value 'foo_4852'. (this warning may be
  suppressed after 10 occurrences)

#3178


SqlAlchemy 2.0 中文文档(七十六)(2)https://developer.aliyun.com/article/1561129

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十六)(2)
SqlAlchemy 2.0 中文文档(七十六)
74 2
|
4月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(七十九)(5)
SqlAlchemy 2.0 中文文档(七十九)
31 2
|
4月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(五十八)(4)
SqlAlchemy 2.0 中文文档(五十八)
30 0
|
4月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(五十八)(7)
SqlAlchemy 2.0 中文文档(五十八)
58 0
|
4月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(五十八)(1)
SqlAlchemy 2.0 中文文档(五十八)
56 0
|
4月前
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(五十八)(5)
SqlAlchemy 2.0 中文文档(五十八)
37 0
|
4月前
|
SQL 关系型数据库 MySQL
SqlAlchemy 2.0 中文文档(五十八)(2)
SqlAlchemy 2.0 中文文档(五十八)
84 0
|
4月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(五十八)(3)
SqlAlchemy 2.0 中文文档(五十八)
22 0
|
4月前
|
SQL Oracle 关系型数据库
SqlAlchemy 2.0 中文文档(五十八)(6)
SqlAlchemy 2.0 中文文档(五十八)
47 0
|
4月前
|
SQL 缓存 API
SqlAlchemy 2.0 中文文档(五十六)(5)
SqlAlchemy 2.0 中文文档(五十六)
49 0