SQLAlchemy 0.5 的新功能?

关于本文档

本文档描述了 SQLAlchemy 版本 0.4(上次发布于 2008 年 10 月 12 日)和 SQLAlchemy 版本 0.5(上次发布于 2010 年 1 月 16 日)之间的变更。

文档日期:2009 年 8 月 4 日

本指南记录了 API 变更,这些变更会影响用户将其应用程序从 SQLAlchemy 的 0.4 系列迁移到 0.5。也建议那些使用 Essential SQLAlchemy 的用户阅读,该书仅涵盖 0.4,甚至似乎还有一些旧的 0.3 的痕迹。请注意,SQLAlchemy 0.5 移除了在 0.4 系列的整个跨度中已弃用的许多行为,并且还弃用了更多特定于 0.4 的行为。

主要文档变更

文档的某些部分已完全重写,可以作为新 ORM 特性的介绍。QuerySession 对象尤其在 API 和行为上存在一些明显的差异,这些差异从根本上改变了许多基本操作的完成方式,尤其是在构建高度定制的 ORM 查询以及处理过时的会话状态、提交和回滚方面。

弃用源

另一个信息来源记录在一系列单元测试中,这些测试说明了一些常见的 Query 模式的最新用法;可以在 [source:sqlalchemy/trunk/test/orm/test_deprecations.py] 查看此文件。

需求变更

  • 需要 Python 2.4 或更高版本。SQLAlchemy 0.4 系列是最后一个支持 Python 2.3 的版本。

对象关系映射

  • Query 中的列级别表达式。 - 如 教程 中详述,Query 具有创建特定 SELECT 语句的能力,而不仅仅是针对完整行的语句

    session.query(User.name, func.count(Address.id).label("numaddresses")).join(
        Address
    ).group_by(User.name)

    任何多列/实体查询返回的元组都是命名’ 元组

    for row in (
        session.query(User.name, func.count(Address.id).label("numaddresses"))
        .join(Address)
        .group_by(User.name)
    ):
        print("name", row.name, "number", row.numaddresses)

    Query 具有 statement 访问器,以及 subquery() 方法,这些方法允许使用 Query 创建更复杂的组合

    subq = (
        session.query(Keyword.id.label("keyword_id"))
        .filter(Keyword.name.in_(["beans", "carrots"]))
        .subquery()
    )
    recipes = session.query(Recipe).filter(
        exists()
        .where(Recipe.id == recipe_keywords.c.recipe_id)
        .where(recipe_keywords.c.keyword_id == subq.c.keyword_id)
    )
  • 建议对别名连接使用显式 ORM 别名 - aliased() 函数生成类的“别名”,允许在 ORM 查询中精细控制别名。虽然表级别别名(即 table.alias())仍然可用,但 ORM 级别别名保留了 ORM 映射对象的语义,这对于继承映射、选项和其他场景非常重要。例如:

    Friend = aliased(Person)
    session.query(Person, Friend).join((Friend, Person.friends)).all()
  • query.join() 大大增强。 - 现在可以以多种方式指定连接的目标和 ON 子句。可以单独提供目标类,其中 SQLA 将尝试通过外键形成与它的连接,方式与 table.join(someothertable) 相同。可以提供目标和显式的 ON 条件,其中 ON 条件可以是 relation() 名称、实际的类描述符或 SQL 表达式。或者,仅使用 relation() 名称或类描述符的旧方法也有效。请参阅 ORM 教程,其中有几个示例。

  • 建议对不需要(并且不偏好)表和映射器之间抽象的应用程序使用声明式 - [/docs/05/reference/ext/declarative.html 声明式] 模块用于组合 Tablemapper() 和用户定义的类对象的表达式,强烈建议使用,因为它简化了应用程序配置,确保了“每个类一个映射器”的模式,并允许 distinct mapper() 调用可用的全部配置范围。单独的 mapper()Table 用法现在被称为“经典 SQLAlchemy 用法”,当然可以与声明式自由混合使用。

  • 已从类中移除 .c. 属性(即 MyClass.c.somecolumn)。与 0.4 中的情况一样,类级别属性可用作查询元素,即 Class.c.propname 现在被 Class.propname 取代,并且 c 属性继续保留在 Table 对象上,在其中它们指示表上存在的 Column 对象的命名空间。

    要获取映射类的 Table(如果您没有事先保留它)

    table = class_mapper(someclass).mapped_table

    迭代列

    for col in table.c:
        print(col)

    处理特定列

    table.c.somecolumn

    类绑定的描述符支持全套 Column 运算符以及记录在案的面向关系的运算符,如 has()any()contains() 等。

    硬性移除 .c. 的原因是,在 0.5 中,类绑定的描述符可能带有不同的含义,以及关于类映射的信息,而不是普通的 Column 对象 - 并且在某些用例中,您可能特别希望使用其中一个或另一个。通常,使用类绑定的描述符会调用一组映射/多态感知转换,而使用表绑定的列则不会。在 0.4 中,这些转换应用于所有表达式,但 0.5 完全区分列和映射的描述符,仅对后者应用转换。因此,在许多情况下,尤其是在处理连接表继承配置以及使用 query(<columns>) 时,Class.propnametable.c.colname 不是可互换的。

    例如,session.query(users.c.id, users.c.name)session.query(User.id, User.name) 不同;在后一种情况下,Query 知道正在使用的映射器,并且可以使用进一步特定于映射器的操作,如 query.join(<propname>)query.with_parent() 等,但在前一种情况下则不能。此外,在多态继承场景中,类绑定的描述符引用多态可选对象中存在的列,而不一定是直接对应于描述符的表列。例如,一组通过连接表继承与 person 表沿每个表的 person_id 列相关的类都将它们的 Class.person_id 属性映射到 person 中的 person_id 列,而不是它们的子类表。版本 0.4 会自动将此行为映射到表绑定的 Column 对象。在 0.5 中,此自动转换已被移除,因此您实际上可以使用表绑定的列作为覆盖多态查询中发生的转换的一种手段;这允许 Query 能够在使用连接表或具体表继承设置以及可移植子查询等之间创建优化的选择。

  • 会话现在默认自动与事务同步。 会话现在默认情况下自动与事务同步,包括自动刷新和自动过期。除非使用 autocommit 选项禁用事务,否则事务始终存在。当所有三个标志都设置为默认值时,会话在回滚后可以优雅地恢复,并且很难将过时的数据放入会话中。有关详细信息,请参阅新的会话文档。

  • 隐式 Order By 已移除。这将影响依赖 SA 的“隐式排序”行为的 ORM 用户,该行为声明所有没有 order_by() 的 Query 对象都将 ORDER BY 主映射表的“id”或“oid”列,并且所有延迟/急切加载的集合都应用类似的排序。在 0.5 中,必须在 mapper()relation() 对象上显式配置自动排序(如果需要),或者在使用 Query 时显式配置。

    要将 0.4 映射转换为 0.5,使其排序行为与 0.4 或以前的版本非常相似,请在 mapper()relation() 上使用 order_by 设置

    mapper(
        User,
        users,
        properties={"addresses": relation(Address, order_by=addresses.c.id)},
        order_by=users.c.id,
    )

    要在反向引用上设置排序,请使用 backref() 函数

    "keywords": relation(
        Keyword,
        secondary=item_keywords,
        order_by=keywords.c.name,
        backref=backref("items", order_by=items.c.id),
    )

    使用声明式?为了帮助新的 order_by 要求,order_by 和 friends 现在可以使用字符串设置,这些字符串稍后在 Python 中计算(这适用于声明式,不适用于普通映射器)

    class MyClass(MyDeclarativeBase):
        ...
        "addresses": relation("Address", order_by="Address.id")

    通常最好在加载基于列表的项集合的 relation()s 上设置 order_by,因为否则无法影响该排序。除此之外,最佳实践是使用 Query.order_by() 来控制正在加载的主实体的排序。

  • 会话现在是 autoflush=True/autoexpire=True/autocommit=False。 - 要进行设置,只需调用不带参数的 sessionmaker()。名称 transactional=True 现在是 autocommit=False。刷新发生在每个发出的查询时(使用 autoflush=False 禁用),在每个 commit() 中(始终如此),以及在每个 begin_nested() 之前(因此回滚到 SAVEPOINT 是有意义的)。所有对象在每次 commit() 和每次 rollback() 后都会过期。回滚后,挂起的对象将被清除,已删除的对象将移回持久状态。这些默认值协同工作得非常好,并且真的不再需要旧技术,如 clear()(已重命名为 expunge_all())。

    附言:会话在 rollback() 后现在是可重用的。标量和集合属性更改、添加和删除都会回滚。

  • session.add() 替换 session.save()、session.update()、session.save_or_update()。 - session.add(someitem)session.add_all([list of items]) 方法替换 save()update()save_or_update()。这些方法将在整个 0.5 版本中保持弃用状态。

  • 反向引用配置变得不那么冗长。 - backref() 函数现在使用前向 relation()primaryjoinsecondaryjoin 参数,当它们未显式声明时。不再需要在两个方向上分别指定 primaryjoin/secondaryjoin

  • 简化的多态选项。 - ORM 的“多态加载”行为已得到简化。在 0.4 中,mapper() 有一个名为 polymorphic_fetch 的参数,可以配置为 selectdeferred。此选项已移除;映射器现在只会延迟 SELECT 语句中不存在的任何列。使用的实际 SELECT 语句由 with_polymorphic 映射器参数(也在 0.4 中,并替换 select_table)以及 Query 上的 with_polymorphic() 方法(也在 0.4 中)控制。

    继承类的延迟加载的一个改进是,映射器现在在所有情况下都生成 SELECT 语句的“优化”版本;也就是说,如果类 B 继承自 A,并且只有类 B 上存在的几个属性已过期,则刷新操作将仅在 SELECT 语句中包含 B 的表,而不会 JOIN 到 A。

  • Session 上的 execute() 方法将普通字符串转换为 text() 构造,因此所有绑定参数都可以指定为“:bindname”,而无需显式调用 text()。如果此处需要“原始”SQL,请使用 session.connection().execute("raw text")

  • session.Query().iterate_instances() 已重命名为仅 instances()。返回列表而不是迭代器的旧 instances() 方法不再存在。如果您依赖该行为,则应使用 list(your_query.instances())

扩展 ORM

在 0.5 中,我们正在推进更多修改和扩展 ORM 的方法。以下是摘要

  • MapperExtension。 - 这是经典的扩展类,仍然存在。很少需要的方法是 create_instance()populate_instance()。要控制从数据库加载对象时的初始化,请使用 reconstruct_instance() 方法,或更轻松地使用文档中描述的 @reconstructor 装饰器。

  • SessionExtension。 - 这是一个易于使用的会话事件扩展类。特别是,它提供了 before_flush()after_flush()after_flush_postexec() 方法。在许多情况下,建议使用此用法而不是 MapperExtension.before_XXX,因为在 before_flush() 中,您可以自由修改会话的刷新计划,这是无法从 MapperExtension 中完成的。

  • AttributeExtension。 - 此类现在是公共 API 的一部分,允许拦截属性上的用户级事件,包括属性设置和删除操作,以及集合追加和移除。它还允许修改要设置或追加的值。文档中描述的 @validates 装饰器提供了一种快速方法,可以将任何映射的属性标记为由特定的类方法“验证”。

  • 属性检测自定义。 - 为雄心勃勃地完全替换 SQLAlchemy 的属性检测或仅在某些情况下对其进行增强提供了 API。此 API 是为 Trellis 工具包的目的而生成的,但作为公共 API 提供。在 /examples/custom_attributes 目录中的发行版中提供了一些示例。

模式/类型

  • 没有长度的 String 不再生成 TEXT,而是生成 VARCHAR - 当指定不带长度时,String 类型不再神奇地转换为 Text 类型。这仅在发出 CREATE TABLE 时才有效,因为它将发出不带长度参数的 VARCHAR,这在许多(但不是全部)数据库上无效。要创建 TEXT(或 CLOB,即无界字符串)列,请使用 Text 类型。

  • mutable=True 的 PickleType() 需要 __eq__() 方法 - 当 mutable=True 时,PickleType 类型需要比较值。比较 pickle.dumps() 的方法效率低下且不可靠。如果传入的对象未实现 __eq__() 并且也不是 None,则使用 dumps() 比较,但会引发警告。对于实现 __eq__() 的类型,包括所有字典、列表等,比较将使用 ==,并且现在默认情况下是可靠的。

  • 已移除 TypeEngine/TypeDecorator 的 convert_bind_param() 和 convert_result_value() 方法。 - O’Reilly 书籍不幸地记录了这些方法,即使它们在 0.3 后已弃用。对于用户定义的子类化 TypeEngine 的类型,bind_processor()result_processor() 方法应用于绑定/结果处理。任何用户定义的类型,无论是扩展 TypeEngine 还是 TypeDecorator,如果使用旧的 0.3 样式,都可以使用以下适配器轻松适应新样式

    class AdaptOldConvertMethods(object):
        """A mixin which adapts 0.3-style convert_bind_param and
        convert_result_value methods
    
        """
    
        def bind_processor(self, dialect):
            def convert(value):
                return self.convert_bind_param(value, dialect)
    
            return convert
    
        def result_processor(self, dialect):
            def convert(value):
                return self.convert_result_value(value, dialect)
    
            return convert
    
        def convert_result_value(self, value, dialect):
            return value
    
        def convert_bind_param(self, value, dialect):
            return value

    要使用上面的 mixin

    class MyType(AdaptOldConvertMethods, TypeEngine): ...
  • ColumnTable 上的 quote 标志以及 Table 上的 quote_schema 标志现在同时控制正向和负向的引用。默认值为 None,表示让常规引用规则生效。当 True 时,强制启用引用。当 False 时,强制禁用引用。

  • 现在可以使用 Column(..., server_default='val') 更方便地指定列 DEFAULT 值 DDL,弃用 Column(..., PassiveDefault('val'))default= 现在专门用于 Python 启动的默认值,并且可以与 server_default 共存。新的 server_default=FetchedValue() 替换了 PassiveDefault('') 习惯用法,用于将列标记为受外部触发器影响,并且没有 DDL 副作用。

  • SQLite 的 DateTimeTimeDate 类型现在仅接受 datetime 对象,而不是字符串作为绑定参数输入。如果您想创建自己的“混合”类型,该类型接受字符串并将结果作为日期对象返回(从您想要的任何格式),请创建一个基于 String 构建的 TypeDecorator。如果您只想使用基于字符串的日期,请仅使用 String

  • 此外,DateTimeTime 类型,当与 SQLite 一起使用时,现在以与 str(datetime) 相同的方式表示 Python datetime.datetime 对象的“微秒”字段 - 作为秒的小数部分,而不是微秒计数。那是

    dt = datetime.datetime(2008, 6, 27, 12, 0, 0, 125)  # 125 usec
    
    # old way
    "2008-06-27 12:00:00.125"
    
    # new way
    "2008-06-27 12:00:00.000125"

    因此,如果现有的基于 SQLite 文件的数据库打算在 0.4 和 0.5 之间使用,则您必须升级 datetime 列以存储新格式(注意:请测试这一点,我很确定它是正确的)

    UPDATE mytable SET somedatecol =
      substr(somedatecol, 0, 19) || '.' || substr((substr(somedatecol, 21, -1) / 1000000), 3, -1);

    或者,按如下方式启用“旧”模式

    from sqlalchemy.databases.sqlite import DateTimeMixin
    
    DateTimeMixin.__legacy_microseconds__ = True

连接池默认不再是线程本地

0.4 有一个不幸的默认设置“pool_threadlocal=True”,例如,当在单个线程中使用多个会话时,会导致意外行为。此标志在 0.5 中现在已关闭。要重新启用 0.4 的行为,请将 pool_threadlocal=True 指定给 create_engine(),或者使用通过 strategy="threadlocal" 的“threadlocal”策略。

接受 *args,不再接受 *args

method(\*args)method([args]) 的策略是,如果方法接受表示固定结构的可变长度的项集,则它采用 \*args。如果方法接受数据驱动的可变长度的项集,则它采用 [args]

  • 各种 Query.options() 函数 eagerload()eagerload_all()lazyload()contains_eager()defer()undefer() 现在都接受可变长度的 \*keys 作为它们的参数,这允许使用描述符制定路径,即:

    query.options(eagerload_all(User.orders, Order.items, Item.keywords))

    为了向后兼容,仍然接受单个数组参数。

  • 同样,Query.join()Query.outerjoin() 方法接受可变长度的 *args,为了向后兼容,也接受单个数组

    query.join("orders", "items")
    query.join(User.orders, Order.items)
  • 列上的 in_() 方法和类似方法现在只接受列表参数。它不再接受 \*args

已移除

  • entity_name - 此功能一直存在问题且很少使用。0.5 更深入地充实了用例,揭示了 entity_name 的进一步问题,导致其被移除。如果单个类需要不同的映射,请将该类分解为单独的子类并分别映射它们。有关示例,请参见 [wiki:UsageRecipes/EntityName]。有关基本原理的更多信息,请参见 https://groups.google.c om/group/sqlalchemy/browse_thread/thread/9e23a0641a88b96d? hl=en 。

  • get()/load() 清理

    已移除 load() 方法。它的功能有点武断,基本上是从 Hibernate 复制过来的,在 Hibernate 中它也不是一个特别有意义的方法。

    要获得等效功能

    x = session.query(SomeClass).populate_existing().get(7)

    已移除 Session.get(cls, id)Session.load(cls, id)Session.get()session.query(cls).get(id) 相比是冗余的。

    也已移除 MapperExtension.get()MapperExtension.load() 也是如此)。要覆盖 Query.get() 的功能,请使用子类

    class MyQuery(Query):
        def get(self, ident): ...
    
    
    session = sessionmaker(query_cls=MyQuery)()
    
    ad1 = session.query(Address).get(1)
  • sqlalchemy.orm.relation()

    已移除以下已弃用的关键字参数

    foreignkey、association、private、attributeext、is_backref

    特别是,attributeextextension 替换 - AttributeExtension 类现在位于公共 API 中。

  • session.Query()

    已移除以下已弃用的函数

    list、scalar、count_by、select_whereclause、get_by、select_by、join_by、selectfirst、selectone、select、execute、select_statement、select_text、join_to、join_via、selectfirst_by、selectone_by、apply_max、apply_min、apply_avg、apply_sum

    此外,已移除 join()outerjoin()add_entity()add_column()id 关键字参数。要在 Query 中将表别名定向到结果列,请使用 aliased 构造

    from sqlalchemy.orm import aliased
    
    address_alias = aliased(Address)
    print(session.query(User, address_alias).join((address_alias, User.addresses)).all())
  • sqlalchemy.orm.Mapper

    • instances()

    • get_session() - 此方法不是很引人注目,但即使父对象完全分离,当使用诸如 scoped_session() 或旧的 SessionContextExt 之类的扩展时,它也具有将延迟加载与特定会话关联的效果。某些依赖此行为的应用程序可能不再按预期工作;但此处更好的编程实践是始终确保对象存在于会话中,如果需要从其属性进行数据库访问。

  • mapper(MyClass, mytable)

    映射类不再使用“c”类属性进行检测;例如 MyClass.c

  • sqlalchemy.orm.collections

    prepare_instrumentation 的别名 _prepare_instrumentation 已被移除。

  • sqlalchemy.orm

    移除了 EXT_CONTINUE 的别名 EXT_PASS

  • sqlalchemy.engine

    DefaultDialect.preexecute_sequences.preexecute_pk_sequences 的别名已被移除。

    已移除弃用的 engine_descriptors() 函数。

  • sqlalchemy.ext.activemapper

    模块已移除。

  • sqlalchemy.ext.assignmapper

    模块已移除。

  • sqlalchemy.ext.associationproxy

    代理的 .append(item, \**kw) 上关键字参数的传递已被移除,现在只需使用 .append(item)

  • sqlalchemy.ext.selectresults, sqlalchemy.mods.selectresults

    模块已移除。

  • sqlalchemy.ext.declarative

    已移除 declared_synonym()

  • sqlalchemy.ext.sessioncontext

    模块已移除。

  • sqlalchemy.log

    sqlalchemy.exc.SADeprecationWarning 的别名 SADeprecationWarning 已被移除。

  • sqlalchemy.exc

    已移除 exc.AssertionError,其用法已替换为同名的 Python 内置异常。

  • sqlalchemy.databases.mysql

    已移除弃用的 get_version_info 方言方法。

重命名或移动

  • sqlalchemy.exceptions 现在是 sqlalchemy.exc

    在 0.6 版本之前,该模块仍然可以使用旧名称导入。

  • FlushError, ConcurrentModificationError, UnmappedColumnError -> sqlalchemy.orm.exc

    这些异常已移动到 orm 包。导入 'sqlalchemy.orm' 将在 sqlalchemy.exc 中安装别名以实现兼容性,直到 0.6 版本。

  • sqlalchemy.logging -> sqlalchemy.log

    此内部模块已重命名。当使用 py2app 和类似的扫描导入的工具打包 SA 时,不再需要特殊处理。

  • session.Query().iterate_instances() -> session.Query().instances()

已弃用

  • Session.save(), Session.update(), Session.save_or_update()

    这三个方法都已被 Session.add() 替代。

  • sqlalchemy.PassiveDefault

    使用 Column(server_default=...),底层转换为 sqlalchemy.DefaultClause()。

  • session.Query().iterate_instances()。它已被重命名为 instances()