SQLAlchemy 0.8 新特性

关于本文档

本文档描述了 SQLAlchemy 0.7 版本(截至 2012 年 10 月正在进行维护版本发布)和 SQLAlchemy 0.8 版本(预计于 2013 年初发布)之间的更改。

文档日期:2012 年 10 月 25 日 更新日期:2013 年 3 月 9 日

简介

本指南介绍了 SQLAlchemy 0.8 版本的新特性,并记录了将应用程序从 SQLAlchemy 0.7 系列迁移到 0.8 系列的用户会受到的影响。

SQLAlchemy 版本正在逼近 1.0,并且自 0.5 以来的每个新版本的功能使用更改都越来越少。大多数已适应现代 0.7 模式的应用程序应该可以迁移到 0.8,而无需更改。使用 0.6 甚至 0.5 模式的应用程序也应该可以直接迁移到 0.8,尽管较大的应用程序可能希望使用每个中间版本进行测试。

平台支持

现在以 Python 2.5 及更高版本为目标

SQLAlchemy 0.8 将以 Python 2.5 及更高版本为目标;将放弃对 Python 2.4 的兼容性。

内部组件将能够使用 Python 三元运算符(即 x if y else z),这将比使用 y and x or z 更好,后者自然是某些 bug 的根源,以及上下文管理器(即 with:)和可能在某些情况下 try:/except:/else: 块,这将有助于代码的可读性。

SQLAlchemy 最终也将放弃 2.5 支持 - 当 2.6 成为基线时,SQLAlchemy 将转向使用 2.6/3.3 原位兼容性,移除 2to3 工具的使用,并维护一个同时适用于 Python 2 和 3 的源代码库。

新的 ORM 特性

重写的 relationship() 机制

0.8 版本具有一个大大改进且功能强大的系统,用于处理 relationship() 如何确定两个实体之间如何连接。新系统包括以下特性

  • 当针对具有指向目标的多个外键路径的类构造 relationship() 时,不再需要 primaryjoin 参数。只需要 foreign_keys 参数来指定应包括的那些列

    class Parent(Base):
        __tablename__ = "parent"
        id = Column(Integer, primary_key=True)
        child_id_one = Column(Integer, ForeignKey("child.id"))
        child_id_two = Column(Integer, ForeignKey("child.id"))
    
        child_one = relationship("Child", foreign_keys=child_id_one)
        child_two = relationship("Child", foreign_keys=child_id_two)
    
    
    class Child(Base):
        __tablename__ = "child"
        id = Column(Integer, primary_key=True)
  • 现在支持针对自引用复合外键(其中列指向自身)的关系。规范的例子如下

    class Folder(Base):
        __tablename__ = "folder"
        __table_args__ = (
            ForeignKeyConstraint(
                ["account_id", "parent_id"], ["folder.account_id", "folder.folder_id"]
            ),
        )
    
        account_id = Column(Integer, primary_key=True)
        folder_id = Column(Integer, primary_key=True)
        parent_id = Column(Integer)
        name = Column(String)
    
        parent_folder = relationship(
            "Folder", backref="child_folders", remote_side=[account_id, folder_id]
        )

    上面,Folder 引用其父 Folder,从 account_id 连接到自身,从 parent_id 连接到 folder_id。当 SQLAlchemy 构造自动连接时,它不再能假设 “远程” 侧的所有列都是别名,而 “本地” 侧的所有列都不是别名 - account_id在两侧都有。因此,内部关系机制被完全重写,以支持一个完全不同的系统,其中生成 account_id 的两个副本,每个副本都包含不同的注释,以确定它们在语句中的角色。请注意基本预先加载中的连接条件

    SELECT
        folder.account_id AS folder_account_id,
        folder.folder_id AS folder_folder_id,
        folder.parent_id AS folder_parent_id,
        folder.name AS folder_name,
        folder_1.account_id AS folder_1_account_id,
        folder_1.folder_id AS folder_1_folder_id,
        folder_1.parent_id AS folder_1_parent_id,
        folder_1.name AS folder_1_name
    FROM folder
        LEFT OUTER JOIN folder AS folder_1
        ON
            folder_1.account_id = folder.account_id
            AND folder.folder_id = folder_1.parent_id
    
    WHERE folder.folder_id = ? AND folder.account_id = ?
  • 以前难以实现的自定义连接条件,例如那些涉及函数和/或类型 CAST 的条件,现在将在大多数情况下按预期运行

    class HostEntry(Base):
        __tablename__ = "host_entry"
    
        id = Column(Integer, primary_key=True)
        ip_address = Column(INET)
        content = Column(String(50))
    
        # relationship() using explicit foreign_keys, remote_side
        parent_host = relationship(
            "HostEntry",
            primaryjoin=ip_address == cast(content, INET),
            foreign_keys=content,
            remote_side=ip_address,
        )

    新的 relationship() 机制使用了 SQLAlchemy 的一个概念,称为注释。这些注释也可以通过 foreign()remote() 函数显式地用于应用程序代码,既可以作为提高高级配置可读性的手段,也可以直接注入精确的配置,绕过通常的连接检查启发式方法

    from sqlalchemy.orm import foreign, remote
    
    
    class HostEntry(Base):
        __tablename__ = "host_entry"
    
        id = Column(Integer, primary_key=True)
        ip_address = Column(INET)
        content = Column(String(50))
    
        # relationship() using explicit foreign() and remote() annotations
        # in lieu of separate arguments
        parent_host = relationship(
            "HostEntry",
            primaryjoin=remote(ip_address) == cast(foreign(content), INET),
        )

另请参阅

配置关系如何连接 - 关于 relationship() 的新修订章节,详细介绍了自定义相关属性和集合访问的最新技术。

#1401 #610

新的类/对象检查系统

许多 SQLAlchemy 用户正在编写需要检查映射类的属性的系统,包括能够访问主键列、对象关系、普通属性等等,通常是为了构建数据编组系统,例如 JSON/XML 转换方案,当然还有大量的表单库。

最初,TableColumn 模型是最初的检查点,它们具有完善的文档系统。虽然 SQLAlchemy ORM 模型也是完全可内省的,但这从来不是一个完全稳定和受支持的功能,用户往往不清楚如何获取此信息。

0.8 版本现在为此目的提供了 一致、稳定且完全文档化的 API,包括一个检查系统,该系统适用于映射类、实例、属性以及其他核心和 ORM 构造。此系统的入口点是核心级别的 inspect() 函数。在大多数情况下,被检查的对象已经是 SQLAlchemy 系统的一部分,例如 MapperInstanceStateInspector。在某些情况下,添加了新对象来提供特定上下文中的检查 API,例如 AliasedInspAttributeState

以下是一些关键功能的概述

>>> class User(Base):
...     __tablename__ = "user"
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     name_syn = synonym(name)
...     addresses = relationship("Address")

>>> # universal entry point is inspect()
>>> b = inspect(User)

>>> # b in this case is the Mapper
>>> b
<Mapper at 0x101521950; User>

>>> # Column namespace
>>> b.columns.id
Column('id', Integer(), table=<user>, primary_key=True, nullable=False)

>>> # mapper's perspective of the primary key
>>> b.primary_key
(Column('id', Integer(), table=<user>, primary_key=True, nullable=False),)

>>> # MapperProperties available from .attrs
>>> b.attrs.keys()
['name_syn', 'addresses', 'id', 'name']

>>> # .column_attrs, .relationships, etc. filter this collection
>>> b.column_attrs.keys()
['id', 'name']

>>> list(b.relationships)
[<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>]

>>> # they are also namespaces
>>> b.column_attrs.id
<sqlalchemy.orm.properties.ColumnProperty object at 0x101525090>

>>> b.relationships.addresses
<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>

>>> # point inspect() at a mapped, class level attribute,
>>> # returns the attribute itself
>>> b = inspect(User.addresses)
>>> b
<sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0>

>>> # From here we can get the mapper:
>>> b.mapper
<Mapper at 0x101525810; Address>

>>> # the parent inspector, in this case a mapper
>>> b.parent
<Mapper at 0x101521950; User>

>>> # an expression
>>> print(b.expression)
"user".id = address.user_id
>>> # inspect works on instances >>> u1 = User(id=3, name="x") >>> b = inspect(u1) >>> # it returns the InstanceState >>> b <sqlalchemy.orm.state.InstanceState object at 0x10152bed0> >>> # similar attrs accessor refers to the >>> b.attrs.keys() ['id', 'name_syn', 'addresses', 'name'] >>> # attribute interface - from attrs, you get a state object >>> b.attrs.id <sqlalchemy.orm.state.AttributeState object at 0x10152bf90> >>> # this object can give you, current value... >>> b.attrs.id.value 3 >>> # ... current history >>> b.attrs.id.history History(added=[3], unchanged=(), deleted=()) >>> # InstanceState can also provide session state information >>> # lets assume the object is persistent >>> s = Session() >>> s.add(u1) >>> s.commit() >>> # now we can get primary key identity, always >>> # works in query.get() >>> b.identity (3,) >>> # the mapper level key >>> b.identity_key (<class '__main__.User'>, (3,)) >>> # state within the session >>> b.persistent, b.transient, b.deleted, b.detached (True, False, False, False) >>> # owning session >>> b.session <sqlalchemy.orm.session.Session object at 0x101701150>

另请参阅

运行时检查 API

#2208

新的 with_polymorphic() 特性,可用于任何地方

Query.with_polymorphic() 方法允许用户指定在查询连接表实体时应存在的表。不幸的是,该方法很笨拙,仅适用于列表中的第一个实体,并且在用法以及内部结构中都存在笨拙的行为。已向 aliased() 构造添加了一个新的增强功能,称为 with_polymorphic(),它允许将任何实体 “别名” 为其自身的 “多态” 版本,可以在任何地方自由使用

from sqlalchemy.orm import with_polymorphic

palias = with_polymorphic(Person, [Engineer, Manager])
session.query(Company).join(palias, Company.employees).filter(
    or_(Engineer.language == "java", Manager.hair == "pointy")
)

另请参阅

使用 with_polymorphic() - 新更新的用于多态加载控制的文档。

#2333

of_type() 可与 alias(),with_polymorphic(),any(),has(),joinedload(),subqueryload(),contains_eager() 一起使用

PropComparator.of_type() 方法用于指定在沿着具有多态映射作为目标的 relationship() 构建 SQL 表达式时使用的特定子类型。通过将其与新的 with_polymorphic() 函数结合使用,此方法现在可用于定位任意数量的目标子类型

# use eager loading in conjunction with with_polymorphic targets
Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
q = (
    s.query(DataContainer)
    .join(DataContainer.jobs.of_type(Job_P))
    .options(contains_eager(DataContainer.jobs.of_type(Job_P)))
)

该方法现在在大多数接受常规关系属性的地方同样适用,包括加载器函数,如 joinedload()subqueryload()contains_eager(),以及比较方法,如 PropComparator.any()PropComparator.has()

# use eager loading in conjunction with with_polymorphic targets
Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
q = (
    s.query(DataContainer)
    .join(DataContainer.jobs.of_type(Job_P))
    .options(contains_eager(DataContainer.jobs.of_type(Job_P)))
)

# pass subclasses to eager loads (implicitly applies with_polymorphic)
q = s.query(ParentThing).options(
    joinedload_all(ParentThing.container, DataContainer.jobs.of_type(SubJob))
)

# control self-referential aliasing with any()/has()
Job_A = aliased(Job)
q = (
    s.query(Job)
    .join(DataContainer.jobs)
    .filter(
        DataContainer.jobs.of_type(Job_A).any(
            and_(Job_A.id < Job.id, Job_A.type == "fred")
        )
    )
)

#2438 #1106

事件可以应用于未映射的超类

Mapper 和实例事件现在可以与未映射的超类关联,其中这些事件将在子类映射时传播到子类。propagate=True 标志应被使用。此特性允许事件与声明式基类关联

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


@event.listens_for("load", Base, propagate=True)
def on_load(target, context):
    print("New instance loaded:", target)


# on_load() will be applied to SomeClass
class SomeClass(Base):
    __tablename__ = "sometable"

    # ...

#2585

声明式区分模块/包

声明式的一个关键特性是能够使用其字符串名称引用其他映射类。类名称的注册表现在对给定类的所有者模块和包敏感。类可以通过表达式中的点号名称引用

class Snack(Base):
    # ...

    peanuts = relationship(
        "nuts.Peanut", primaryjoin="nuts.Peanut.snack_id == Snack.id"
    )

解析允许使用任何完整或部分消除歧义的包名称。如果特定类的路径仍然不明确,则会引发错误。

#2338

声明式中的新 DeferredReflection 特性

“延迟反射” 示例已移至声明式中的受支持特性。此特性允许构建仅包含占位符 Table 元数据的声明式映射类,直到调用 prepare() 步骤,并给定一个 Engine 以完全反射所有表并建立实际映射。该系统支持列的覆盖、单一和连接继承,以及每个引擎不同的基类。现在可以针对现有表创建完整的声明式配置,该配置在引擎创建时一步完成组装

class ReflectedOne(DeferredReflection, Base):
    __abstract__ = True


class ReflectedTwo(DeferredReflection, Base):
    __abstract__ = True


class MyClass(ReflectedOne):
    __tablename__ = "mytable"


class MyOtherClass(ReflectedOne):
    __tablename__ = "myothertable"


class YetAnotherClass(ReflectedTwo):
    __tablename__ = "yetanothertable"


ReflectedOne.prepare(engine_one)
ReflectedTwo.prepare(engine_two)

另请参阅

DeferredReflection

#2485

ORM 类现在被核心构造接受

虽然用于 Query.filter() 的 SQL 表达式(例如 User.id == 5)始终与核心构造(例如 select())兼容使用,但当传递给 select()Select.select_from()Select.correlate() 时,映射类本身将无法被识别。新的 SQL 注册系统允许将映射类作为核心中的 FROM 子句接受

from sqlalchemy import select

stmt = select([User]).where(User.id == 5)

在上面,映射的 User 类将扩展为 TableUser 类映射到该表。

#2245

Query.update() 支持 UPDATE..FROM

新的 UPDATE..FROM 机制在 query.update() 中工作。下面,我们针对 SomeEntity 发出 UPDATE,添加针对 SomeOtherEntity 的 FROM 子句(或等效项,具体取决于后端)

query(SomeEntity).filter(SomeEntity.id == SomeOtherEntity.id).filter(
    SomeOtherEntity.foo == "bar"
).update({"data": "x"})

特别是,支持对连接继承实体进行更新,前提是 UPDATE 的目标位于正在筛选的表本地,或者如果父表和子表混合,则在查询中显式连接它们。下面,给定 Engineer 作为 Person 的连接子类

query(Engineer).filter(Person.id == Engineer.id).filter(
    Person.name == "dilbert"
).update({"engineer_data": "java"})

将产生

UPDATE engineer SET engineer_data='java' FROM person
WHERE person.id=engineer.id AND person.name='dilbert'

#2365

rollback() 将仅回滚 begin_nested() 中 “dirty” 对象

行为更改应提高使用 SAVEPOINT 的用户通过 Session.begin_nested() 的效率 - 在 rollback() 时,只会使自上次 flush 以来变 dirty 的对象过期,Session 的其余部分保持不变。这是因为 ROLLBACK 到 SAVEPOINT 不会终止包含事务的隔离,因此除了当前事务中未刷新的更改之外,不需要过期。

#2452

缓存示例现在使用 dogpile.cache

缓存示例现在使用 dogpile.cache。Dogpile.cache 是 Beaker 缓存部分的重写,具有更简单、更快速的操作,以及对分布式锁定的支持。

请注意,Dogpile 示例以及之前的 Beaker 示例使用的 SQLAlchemy API 略有更改,特别是需要进行此更改,如 Beaker 示例所示

--- examples/beaker_caching/caching_query.py
+++ examples/beaker_caching/caching_query.py
@@ -222,7 +222,8 @@

         """
         if query._current_path:
-            mapper, key = query._current_path[-2:]
+            mapper, prop = query._current_path[-2:]
+            key = prop.key

             for cls in mapper.class_.__mro__:
                 if (cls, key) in self._relationship_options:

另请参阅

Dogpile 缓存

#2589

新的核心特性

核心中完全可扩展的类型级别运算符支持

到目前为止,Core 从未有过任何系统可以向 Column 和其他表达式构造添加对新 SQL 运算符的支持,除了 ColumnOperators.op() 方法,该方法 “只是足以” 使其工作。Core 中也从未有过任何系统允许覆盖现有运算符的行为。到目前为止,灵活地重新定义运算符的唯一方法是在 ORM 层中,使用 column_property() 并给定 comparator_factory 参数。因此,GeoAlchemy 等第三方库被迫以 ORM 为中心,并依赖于一系列 hacks 来应用新操作并使其正确传播。

Core 中的新运算符系统添加了长期以来一直缺失的一个钩子,即将新的和覆盖的运算符与类型关联。毕竟,真正驱动存在哪些类型操作的不是列、CAST 运算符或 SQL 函数,而是表达式的类型。实现细节非常少 - 只需向核心 ColumnElement 类型添加几个额外的方法,以便它咨询其 TypeEngine 对象以获取可选的运算符集。可以通过子类化现有类型、使用 TypeDecorator 或通过将新的 Comparator 对象附加到现有类型类来将新的或修订的操作与任何类型关联起来。

例如,要为 Numeric 类型添加对数支持

from sqlalchemy.types import Numeric
from sqlalchemy.sql import func


class CustomNumeric(Numeric):
    class comparator_factory(Numeric.Comparator):
        def log(self, other):
            return func.log(self.expr, other)

新类型像任何其他类型一样可用

data = Table(
    "data",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("x", CustomNumeric(10, 5)),
    Column("y", CustomNumeric(10, 5)),
)

stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value)
print(conn.execute(stmt).fetchall())

由此立即产生的新特性包括对 PostgreSQL 的 HSTORE 类型的支持,以及与 PostgreSQL 的 ARRAY 类型相关的新操作。它还为现有类型铺平了道路,使其能够获得更多特定于这些类型的运算符,例如更多的字符串、整数和日期运算符。

#2547

Insert 的多 VALUES 支持

Insert.values() 方法现在支持字典列表,这将呈现一个多 VALUES 语句,例如 VALUES (<row1>), (<row2>), ...。这仅与支持此语法的后端相关,包括 PostgreSQL、SQLite 和 MySQL。它与通常的 executemany() 样式的 INSERT 不同,后者保持不变

users.insert().values(
    [
        {"name": "some name"},
        {"name": "some other name"},
        {"name": "yet another name"},
    ]
)

另请参阅

Insert.values()

#2623

类型表达式

SQL 表达式现在可以与类型关联。从历史上看,TypeEngine 始终允许 Python 端函数接收绑定参数和结果行值,在往返数据库的过程中通过 Python 端转换函数传递它们。新特性允许类似的功能,但位于数据库端

from sqlalchemy.types import String
from sqlalchemy import func, Table, Column, MetaData


class LowerString(String):
    def bind_expression(self, bindvalue):
        return func.lower(bindvalue)

    def column_expression(self, col):
        return func.lower(col)


metadata = MetaData()
test_table = Table("test_table", metadata, Column("data", LowerString))

在上面,LowerString 类型定义了一个 SQL 表达式,每当 test_table.c.data 列在 SELECT 语句的 columns 子句中呈现时,都会发出该表达式

>>> print(select([test_table]).where(test_table.c.data == "HI"))
SELECT lower(test_table.data) AS data FROM test_table WHERE test_table.data = lower(:data_1)

GeoAlchemy 的新版本也大量使用此特性,以根据类型规则将 PostGIS 表达式内联嵌入到 SQL 中。

#1534

核心检查系统

新的类/对象检查系统 中介绍的 inspect() 函数也适用于核心。应用于 Engine 时,它会生成一个 Inspector 对象

from sqlalchemy import inspect
from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost/test")
insp = inspect(engine)
print(insp.get_table_names())

它也可以应用于任何 ClauseElement,它返回 ClauseElement 本身,例如 TableColumnSelect 等。这使得它可以在 Core 和 ORM 构造之间流畅地工作。

新方法 Select.correlate_except()

select() 现在有一个方法 Select.correlate_except(),它指定“关联所有 FROM 子句,但指定的子句除外”。它可用于映射场景,其中相关的子查询应正常关联,但与特定的目标 selectable 除外

class SnortEvent(Base):
    __tablename__ = "event"

    id = Column(Integer, primary_key=True)
    signature = Column(Integer, ForeignKey("signature.id"))

    signatures = relationship("Signature", lazy=False)


class Signature(Base):
    __tablename__ = "signature"

    id = Column(Integer, primary_key=True)

    sig_count = column_property(
        select([func.count("*")])
        .where(SnortEvent.signature == id)
        .correlate_except(SnortEvent)
    )

PostgreSQL HSTORE 类型

现在可以使用 HSTORE 来支持 PostgreSQL 的 HSTORE 类型。此类型充分利用了新的运算符系统,为 HSTORE 类型提供全方位的运算符,包括索引访问、连接和包含方法,例如 comparator_factory.has_key()comparator_factory.has_any()comparator_factory.matrix()

from sqlalchemy.dialects.postgresql import HSTORE

data = Table(
    "data_table",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("hstore_data", HSTORE),
)

engine.execute(select([data.c.hstore_data["some_key"]])).scalar()

engine.execute(select([data.c.hstore_data.matrix()])).scalar()

另请参阅

HSTORE

hstore

#2606

增强的 PostgreSQL ARRAY 类型

ARRAY 类型将接受一个可选的 “dimension” 参数,将其固定到固定数量的维度,并大大提高检索结果时的效率。

# old way, still works since PG supports N-dimensions per row:
Column("my_array", postgresql.ARRAY(Integer))

# new way, will render ARRAY with correct number of [] in DDL,
# will process binds and results more efficiently as we don't need
# to guess how many levels deep to go
Column("my_array", postgresql.ARRAY(Integer, dimensions=2))

该类型还引入了新的运算符,使用新的类型特定运算符框架。新操作包括索引访问。

result = conn.execute(select([mytable.c.arraycol[2]]))

SELECT 中的切片访问

result = conn.execute(select([mytable.c.arraycol[2:4]]))

UPDATE 中的切片更新

conn.execute(mytable.update().values({mytable.c.arraycol[2:3]: [7, 8]}))

独立的数组字面量

>>> from sqlalchemy.dialects import postgresql
>>> conn.scalar(select([postgresql.array([1, 2]) + postgresql.array([3, 4, 5])]))
[1, 2, 3, 4, 5]

数组连接,在下面,右侧 [4, 5, 6] 被强制转换为数组字面量。

select([mytable.c.arraycol + [4, 5, 6]])

另请参阅

ARRAY

数组

#2441

SQLite 的新的、可配置的 DATE、TIME 类型

SQLite 没有内置的 DATE、TIME 或 DATETIME 类型,而是提供了一些对日期和时间值以字符串或整数形式存储的支持。SQLite 的日期和时间类型在 0.8 中得到了增强,使其在特定格式方面更加可配置,包括 “微秒” 部分是可选的,以及几乎所有其他内容。

Column("sometimestamp", sqlite.DATETIME(truncate_microseconds=True))
Column(
    "sometimestamp",
    sqlite.DATETIME(
        storage_format=(
            "%(year)04d%(month)02d%(day)02d"
            "%(hour)02d%(minute)02d%(second)02d%(microsecond)06d"
        ),
        regexp="(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{6})",
    ),
)
Column(
    "somedate",
    sqlite.DATE(
        storage_format="%(month)02d/%(day)02d/%(year)04d",
        regexp="(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)",
    ),
)

非常感谢 Nate Dub 在 2012 年 Pycon 大会上为此进行的冲刺开发。

另请参阅

DATETIME

DATE

TIME

#2363

“COLLATE” 在所有方言中均受支持;特别是 MySQL、PostgreSQL、SQLite

“collate” 关键字,长期以来被 MySQL 方言接受,现在已在所有 String 类型上建立,并且将在任何后端呈现,包括在使用 MetaData.create_all()cast() 等功能时。

>>> stmt = select([cast(sometable.c.somechar, String(20, collation="utf8"))])
>>> print(stmt)
SELECT CAST(sometable.somechar AS VARCHAR(20) COLLATE "utf8") AS anon_1 FROM sometable

另请参阅

String

#2276

“前缀” 现在支持 update()delete()

针对 MySQL,可以在任何这些构造中呈现 “前缀”。例如:

stmt = table.delete().prefix_with("LOW_PRIORITY", dialect="mysql")


stmt = table.update().prefix_with("LOW_PRIORITY", dialect="mysql")

除了 insert()select()Query 上已存在的方法之外,此方法是新增的。

另请参阅

Update.prefix_with()

Delete.prefix_with()

Insert.prefix_with()

Select.prefix_with()

Query.prefix_with()

#2431

行为变更

将 “pending” 对象视为 “orphan” 的考虑已变得更加严格。

这是 0.8 系列的后期添加,但希望新的行为在更广泛的情况下通常更加一致和直观。自至少 0.4 版本以来,ORM 包含这样的行为:一个 “pending” 对象(意味着它与 Session 关联但尚未插入数据库)在变为 “orphan” 时会自动从 Session 中 expunged,这意味着它已与父对象解除关联,该父对象在其配置的 relationship() 上使用 delete-orphan 级联引用它。此行为旨在近似地反映 persistent(即已插入)对象的行为,其中 ORM 将为基于 detachment 事件拦截而变为 orphan 的此类对象发出 DELETE。

行为变更适用于被多种类型的父对象引用的对象,这些父对象都指定了 delete-orphan;典型的示例是 association object,它在多对多模式中桥接两种其他类型的对象。以前,行为是这样的:pending 对象仅在与所有父对象解除关联时才会被 expunged。随着行为的改变,pending 对象一旦与之前与之关联的任何父对象解除关联,就会立即被 expunged。此行为旨在更紧密地匹配 persistent 对象的行为,persistent 对象一旦与任何父对象解除关联就会被删除。

旧行为的基本原理至少可以追溯到 0.4 版本,并且基本上是一种防御性决策,旨在尝试减轻在对象仍在为 INSERT 构建时产生的混淆。但现实情况是,对象一旦附加到任何新父对象,就会在任何情况下都重新与 Session 关联。

仍然可以 flush 未与其所有必需父对象关联的对象,如果该对象最初未与这些父对象关联,或者如果它已被 expunged,但随后通过后续的 attachment 事件重新与 Session 关联,但仍未完全关联。在这种情况下,预计数据库会发出完整性错误,因为可能存在未填充的 NOT NULL 外键列。ORM 决定让这些 INSERT 尝试发生,这是基于这样的判断:仅与其部分必需父对象关联但已积极与其中一些父对象关联的对象,通常是用户错误,而不是应被静默跳过的有意遗漏——在此处静默跳过 INSERT 将使此类用户错误非常难以调试。

对于可能依赖于旧行为的应用程序,可以通过将 legacy_is_orphan 标志指定为 mapper 选项,为任何 Mapper 重新启用旧行为。

新行为允许以下测试用例工作。

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String(64))


class UserKeyword(Base):
    __tablename__ = "user_keyword"
    user_id = Column(Integer, ForeignKey("user.id"), primary_key=True)
    keyword_id = Column(Integer, ForeignKey("keyword.id"), primary_key=True)

    user = relationship(
        User, backref=backref("user_keywords", cascade="all, delete-orphan")
    )

    keyword = relationship(
        "Keyword", backref=backref("user_keywords", cascade="all, delete-orphan")
    )

    # uncomment this to enable the old behavior
    # __mapper_args__ = {"legacy_is_orphan": True}


class Keyword(Base):
    __tablename__ = "keyword"
    id = Column(Integer, primary_key=True)
    keyword = Column("keyword", String(64))


from sqlalchemy import create_engine
from sqlalchemy.orm import Session

# note we're using PostgreSQL to ensure that referential integrity
# is enforced, for demonstration purposes.
e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)

Base.metadata.drop_all(e)
Base.metadata.create_all(e)

session = Session(e)

u1 = User(name="u1")
k1 = Keyword(keyword="k1")

session.add_all([u1, k1])

uk1 = UserKeyword(keyword=k1, user=u1)

# previously, if session.flush() were called here,
# this operation would succeed, but if session.flush()
# were not called here, the operation fails with an
# integrity error.
# session.flush()
del u1.user_keywords[0]

session.commit()

#2655

after_attach 事件在项目与 Session 关联之后而不是之前触发;添加了 before_attach

使用 after_attach 的事件处理程序现在可以假定给定实例与给定会话关联。

@event.listens_for(Session, "after_attach")
def after_attach(session, instance):
    assert instance in session

某些用例要求它以这种方式工作。但是,其他用例要求项目尚不是会话的一部分,例如当一个查询(旨在加载实例所需的某些状态)首先发出 autoflush,否则会过早地 flush 目标对象时。这些用例应使用新的 “before_attach” 事件。

@event.listens_for(Session, "before_attach")
def before_attach(session, instance):
    instance.some_necessary_attribute = (
        session.query(Widget).filter_by(instance.widget_name).first()
    )

#2464

Query 现在像 select() 一样自动关联。

以前,为了使 column- 或 WHERE- 子查询与父查询关联,需要调用 Query.correlate()

subq = (
    session.query(Entity.value)
    .filter(Entity.id == Parent.entity_id)
    .correlate(Parent)
    .as_scalar()
)
session.query(Parent).filter(subq == "some value")

这与普通的 select() 构造的行为相反,后者默认会假定自动关联。0.8 中的上述语句将自动关联。

subq = session.query(Entity.value).filter(Entity.id == Parent.entity_id).as_scalar()
session.query(Parent).filter(subq == "some value")

就像在 select() 中一样,可以通过调用 query.correlate(None) 禁用关联,或者通过传递实体 query.correlate(someentity) 手动设置关联。

#2179

关联现在始终是上下文特定的。

为了允许更广泛的关联场景,Select.correlate()Query.correlate() 的行为略有改变,使得 SELECT 语句仅在语句实际在该上下文中使用时才从 FROM 子句中省略 “关联的” 目标。此外,放置在封闭 SELECT 语句中作为 FROM 的 SELECT 语句不再可能 “关联”(即省略)FROM 子句。

就呈现 SQL 而言,此更改只会使事情变得更好,因为不再可能呈现非法 SQL,其中相对于正在选择的内容,FROM 对象不足。

from sqlalchemy.sql import table, column, select

t1 = table("t1", column("x"))
t2 = table("t2", column("y"))
s = select([t1, t2]).correlate(t1)

print(s)

在此更改之前,上述操作将返回:

SELECT t1.x, t2.y FROM t2

这是无效的 SQL,因为任何 FROM 子句中都没有引用 “t1”。

现在,在没有封闭 SELECT 的情况下,它返回:

SELECT t1.x, t2.y FROM t1, t2

在 SELECT 中,关联按预期生效。

s2 = select([t1, t2]).where(t1.c.x == t2.c.y).where(t1.c.x == s)
print(s2)
SELECT t1.x, t2.y FROM t1, t2
WHERE t1.x = t2.y AND t1.x =
    (SELECT t1.x, t2.y FROM t2)

预计此更改不会影响任何现有应用程序,因为对于正确构造的表达式,关联行为保持不变。只有在非关联上下文中使用的关联 SELECT 的无效字符串输出上(很可能在测试场景中)依赖的应用程序才会看到任何变化。

#2668

MetaData.create_all()MetaData.drop_all() 现在将把空列表视为这种情况。

方法 MetaData.create_all()MetaData.drop_all() 现在将接受一个空的 Table 对象列表,并且不会发出任何 CREATE 或 DROP 语句。以前,空列表被解释为与为集合传递 None 相同,并且将无条件地为所有项目发出 CREATE/DROP。

这是一个错误修复,但某些应用程序可能一直依赖于以前的行为。

#2664

修复了 InstrumentationEvents 的事件目标定位。

InstrumentationEvents 系列事件目标已记录,事件将仅根据作为目标传递的实际类触发。在 0.7 版本中,情况并非如此,并且应用于 InstrumentationEvents 的任何事件侦听器都将为所有映射的类调用。在 0.8 版本中,添加了额外的逻辑,以便事件仅为传入的那些类调用。此处的 propagate 标志默认设置为 True,因为类 instrumentation 事件通常用于拦截尚未创建的类。

#2590

在 MS-SQL 中比较子查询时,不再将 “=” 魔法般地强制转换为 IN。

我们在 MSSQL 方言中发现了一个非常旧的行为,当执行如下操作时,它会尝试将用户从他们自己手中解救出来:

scalar_subq = select([someothertable.c.id]).where(someothertable.c.data == "foo")
select([sometable]).where(sometable.c.id == scalar_subq)

SQL Server 不允许与标量 SELECT 进行相等比较,即 “x = (SELECT something)”。MSSQL 方言会将此转换为 IN。但是,在像 “(SELECT something) = x” 这样的比较中也会发生同样的事情,总的来说,这种猜测程度超出了 SQLAlchemy 的通常范围,因此该行为被删除。

#2277

修复了 Session.is_modified() 的行为。

Session.is_modified() 方法接受一个参数 passive,该参数基本上不应该是必要的,在所有情况下,该参数都应该是值 True——当保留其默认值 False 时,它会产生访问数据库的效果,并且经常触发 autoflush,而 autoflush 本身会更改结果。在 0.8 版本中,passive 参数将不起作用,并且永远不会检查未加载属性的历史记录,因为根据定义,未加载属性上不可能存在 pending 状态更改。

另请参阅

Session.is_modified()

#2320

Column.key 在使用 Select.apply_labels()select()Select.c 属性中被遵循。

表达式系统的用户知道 Select.apply_labels() 会将表名添加到每个列名前面,从而影响从 Select.c 可用的名称。

s = select([table1]).apply_labels()
s.c.table1_col1
s.c.table1_col2

在 0.8 之前,如果 Column 具有不同的 Column.key,则此键将被忽略,这与不使用 Select.apply_labels() 时不一致。

# before 0.8
table1 = Table("t1", metadata, Column("col1", Integer, key="column_one"))
s = select([table1])
s.c.column_one  # would be accessible like this
s.c.col1  # would raise AttributeError

s = select([table1]).apply_labels()
s.c.table1_column_one  # would raise AttributeError
s.c.table1_col1  # would be accessible like this

在 0.8 版本中,Column.key 在两种情况下都被遵循。

# with 0.8
table1 = Table("t1", metadata, Column("col1", Integer, key="column_one"))
s = select([table1])
s.c.column_one  # works
s.c.col1  # AttributeError

s = select([table1]).apply_labels()
s.c.table1_column_one  # works
s.c.table1_col1  # AttributeError

关于 “name” 和 “key” 的所有其他行为都是相同的,包括呈现的 SQL 仍将使用 <tablename>_<colname> 形式——这里的重点是防止将 Column.key 内容呈现到 SELECT 语句中,以便在使用 Column.key 中使用的特殊/非 ASCII 字符时不会出现问题。

#2397

single_parent 警告现在是一个错误。

一个多对一或多对多的 relationship(),并指定 “cascade=’all, delete-orphan’”,这是一个笨拙但仍然受支持的用例(带有限制),如果 relationship 未指定 single_parent=True 选项,则现在会引发错误。以前,它只会发出警告,但在任何情况下,属性系统内几乎会立即出现故障。

#2405

inspector 参数添加到 column_reflect 事件。

0.7 添加了一个名为 column_reflect 的新事件,提供该事件是为了在每个列被反射时可以增强列的反射。我们对这个事件的理解略有偏差,因为在需要来自数据库的附加信息的情况下,该事件无法访问用于反射的当前 InspectorConnection。由于这是一个尚未广泛使用的新事件,我们将直接将 inspector 参数添加到其中。

@event.listens_for(Table, "column_reflect")
def listen_for_col(inspector, table, column_info): ...

#2418

禁用 MySQL 的排序规则、大小写的自动检测。

MySQL 方言在 Engine 首次连接时执行两个调用(其中一个非常昂贵),以从数据库加载所有可能的排序规则以及有关大小写的信息。这些集合都不用于任何 SQLAlchemy 函数,因此这些调用将被更改为不再自动发出。可能依赖于这些集合存在于 engine.dialect 上的应用程序将需要直接调用 _detect_collations()_detect_casing()

#2404

“未使用的列名” 警告变为异常。

insert()update() 构造中引用不存在的列将引发错误而不是警告。

t1 = table("t1", column("x"))
t1.insert().values(x=5, z=5)  # raises "Unconsumed column names: z"

#2415

Inspector.get_primary_keys() 已弃用,请使用 Inspector.get_pk_constraint

Inspector 上的这两种方法是冗余的,其中 get_primary_keys() 将返回与 get_pk_constraint() 相同的信息,但没有约束的名称。

>>> insp.get_primary_keys()
["a", "b"]

>>> insp.get_pk_constraint()
{"name":"pk_constraint", "constrained_columns":["a", "b"]}

#2422

不区分大小写的结果行名称将在大多数情况下被禁用。

一个非常旧的行为,RowProxy 中的列名始终以不区分大小写的方式进行比较。

>>> row = result.fetchone()
>>> row["foo"] == row["FOO"] == row["Foo"]
True

这是为了少数早期需要此功能的方言(如 Oracle 和 Firebird)的利益,但在现代用法中,我们有更准确的方法来处理这两个平台的不区分大小写行为。

展望未来,此行为将仅作为可选功能提供,方法是将标志 `case_sensitive=False` 传递给 `create_engine()`,但在其他情况下,从行请求的列名必须在大小写方面匹配。

#2423

InstrumentationManager 和备用类 instrumentation 现在是一个扩展。

sqlalchemy.orm.interfaces.InstrumentationManager 类已移动到 sqlalchemy.ext.instrumentation.InstrumentationManager。“备用 instrumentation” 系统的构建是为了使极少数需要使用现有或不寻常的类 instrumentation 系统的安装受益,并且通常很少使用。该系统的复杂性已导出到 ext. 模块。它仍然未使用,直到导入后才使用,通常在第三方库导入 InstrumentationManager 时,此时它通过将默认的 InstrumentationFactory 替换为 ExtendedInstrumentationRegistry 而被注入回 sqlalchemy.orm

已移除

SQLSoup

SQLSoup 是一个方便的软件包,它在 SQLAlchemy ORM 之上提供了一个替代接口。SQLSoup 现在已移至其自己的项目,并单独记录/发布;请参阅 https://github.com/zzzeek/sqlsoup

SQLSoup 是一个非常简单的工具,也可以从对它的使用风格感兴趣的贡献者那里受益。

#2262

MutableType

SQLAlchemy ORM 中较旧的 “mutable” 系统已被删除。这指的是 MutableType 接口,该接口应用于 PickleType 等类型,并有条件地应用于 TypeDecorator,并且自 SQLAlchemy 的早期版本以来,一直为 ORM 提供了一种检测所谓的 “mutable” 数据结构(如 JSON 结构和 pickled 对象)中的更改的方法。但是,该实现从未合理过,并且在 unit-of-work 上强制执行了一种非常低效的使用模式,这导致在 flush 期间对所有对象进行昂贵的扫描。在 0.7 版本中,引入了 sqlalchemy.ext.mutable 扩展,以便用户定义的数据类型可以在发生更改时适当地向 unit of work 发送事件。

如今,MutableType 的使用量预计会很低,因为关于其低效率的警告已经存在多年。

#2442

sqlalchemy.exceptions(多年来一直是 sqlalchemy.exc)

我们保留了一个别名 sqlalchemy.exceptions,试图使一些尚未升级到使用 sqlalchemy.exc 的非常旧的库更容易使用。但是,一些用户仍然对此感到困惑,因此在 0.8 版本中,我们将完全删除它,以消除任何这种困惑。

#2433