SqlAlchemy 2.0 中文文档(七十七)

2024-08-26 16:39:47 浏览数 (3)

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

SQLAlchemy 0.9 中的新功能是什么?

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

关于本文档

本文档描述了 SQLAlchemy 版本 0.8 与版本 0.9 之间的变化,截至 2013 年 5 月,0.8 版本正在进行维护,而 0.9 版本在 2013 年 12 月 30 日首次发布。

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

介绍

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

请仔细查看行为变化 - ORM 和行为变化 - 核心,以了解可能导致不兼容的变化。

平台支持

现在,Python 2.6 及以上版本的目标是,Python 3 不再需要 2to3

0.9 版本的第一个成就是移除对 Python 3 兼容性的 2to3 工具的依赖。为了更直接,现在目标最低的 Python 版本是 2.6,它具有与 Python 3 广泛的交叉兼容性。现在,所有 SQLAlchemy 模块和单元测试都可以在从 2.6 开始的任何 Python 解释器上等效地解释,包括 3.1 和 3.2 解释器。

#2671

C 扩展在 Python 3 上得到支持

C 扩展已被移植以支持 Python 3,现在在 Python 2 和 Python 3 环境中均可构建。

#2161

行为变化 - ORM

当按属性查询时,现在会返回组合属性的对象形式

现在,将Query与组合属性结合使用时,会返回由该组合维护的对象类型,而不是被拆分为个别列。使用在组合列类型中设置的映射:

代码语言:javascript复制
>>> session.query(Vertex.start, Vertex.end).filter(Vertex.start == Point(3, 4)).all()
[(Point(x=3, y=4), Point(x=5, y=6))]

这个改变与期望个别属性扩展为个别列的代码不兼容。要获得该行为,请使用.clauses访问器:

代码语言:javascript复制
>>> session.query(Vertex.start.clauses, Vertex.end.clauses).filter(
...     Vertex.start == Point(3, 4)
... ).all()
[(3, 4, 5, 6)]

另请参阅

ORM 查询的列捆绑

#2824 ### Query.select_from()不再将子句应用于相应的实体

在最近的版本中,Query.select_from()方法已经被广泛应用,作为控制Query对象“选择自”的第一件事的手段,通常用于控制 JOIN 的渲染方式。

请考虑以下例子与通常的User映射对比:

代码语言:javascript复制
select_stmt = select([User]).where(User.id == 7).alias()

q = (
    session.query(User)
    .join(select_stmt, User.id == select_stmt.c.id)
    .filter(User.name == "ed")
)

上述语句可预见地生成类似以下的 SQL:

代码语言:javascript复制
SELECT  "user".id  AS  user_id,  "user".name  AS  user_name
FROM  "user"  JOIN  (SELECT  "user".id  AS  id,  "user".name  AS  name
FROM  "user"
WHERE  "user".id  =  :id_1)  AS  anon_1  ON  "user".id  =  anon_1.id
WHERE  "user".name  =  :name_1

如果我们想要颠倒 JOIN 的左右元素的顺序,文档会让我们相信可以使用Query.select_from()来实现:

代码语言:javascript复制
q = (
    session.query(User)
    .select_from(select_stmt)
    .join(User, User.id == select_stmt.c.id)
    .filter(User.name == "ed")
)

然而,在 0.8 版本及更早版本中,上述对Query.select_from()的使用会将select_stmt应用于替换User实体,因为它选择了与User兼容的user表:

代码语言:javascript复制
-- SQLAlchemy 0.8 and earlier...
SELECT  anon_1.id  AS  anon_1_id,  anon_1.name  AS  anon_1_name
FROM  (SELECT  "user".id  AS  id,  "user".name  AS  name
FROM  "user"
WHERE  "user".id  =  :id_1)  AS  anon_1  JOIN  "user"  ON  anon_1.id  =  anon_1.id
WHERE  anon_1.name  =  :name_1

上述语句混乱不堪,ON 子句引用了anon_1.id = anon_1.id,我们的 WHERE 子句也被替换为anon_1

这种行为是完全有意的,但与已经变得流行的Query.select_from()有不同的用例。上述行为现在可以通过一个名为Query.select_entity_from()的新方法来实现。这是一个较少使用的行为,在现代 SQLAlchemy 中大致相当于从自定义的aliased()构造中选择:

代码语言:javascript复制
select_stmt = select([User]).where(User.id == 7)
user_from_stmt = aliased(User, select_stmt.alias())

q = session.query(user_from_stmt).filter(user_from_stmt.name == "ed")

因此,在 SQLAlchemy 0.9 中,我们从select_stmt选择的查询会产生我们期望的 SQL:

代码语言:javascript复制
-- SQLAlchemy 0.9
SELECT  "user".id  AS  user_id,  "user".name  AS  user_name
FROM  (SELECT  "user".id  AS  id,  "user".name  AS  name
FROM  "user"
WHERE  "user".id  =  :id_1)  AS  anon_1  JOIN  "user"  ON  "user".id  =  id
WHERE  "user".name  =  :name_1

Query.select_entity_from()方法将在 SQLAlchemy 0.8.2中可用,因此依赖旧行为的应用程序可以首先过渡到这种方法,确保所有测试继续正常运行,然后无问题地升级到 0.9。

#2736 ### viewonly=True on relationship() prevents history from taking effect

relationship()上的viewonly标志被应用于防止对目标属性的更改在刷新过程中产生任何影响。这是通过在刷新过程中排除属性来实现的。然而,直到现在,对属性的更改仍然会将父对象标记为“脏”,并触发潜在的刷新。改变是viewonly标志现在也阻止为目标属性设置历史记录。像反向引用和用户定义事件这样的属性事件仍然会正常工作。

改变如下所示:

代码语言:javascript复制
from sqlalchemy import Column, Integer, ForeignKey, create_engine
from sqlalchemy.orm import backref, relationship, Session
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import inspect

Base = declarative_base()

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(Integer, ForeignKey("a.id"))
    a = relationship("A", backref=backref("bs", viewonly=True))

e = create_engine("sqlite://")
Base.metadata.create_all(e)

a = A()
b = B()

sess = Session(e)
sess.add_all([a, b])
sess.commit()

b.a = a

assert b in sess.dirty

# before 0.9.0
# assert a in sess.dirty
# assert inspect(a).attrs.bs.history.has_changes()

# after 0.9.0
assert a not in sess.dirty
assert not inspect(a).attrs.bs.history.has_changes()

#2833 ### 关联代理 SQL 表达式改进和修复

通过关联代理实现的==!=运算符,引用标量关系上的标量值,现在会产生更完整的 SQL 表达式,旨在考虑当比较对象为None时“关联”行是否存在。

考虑以下映射:

代码语言:javascript复制
class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    b_id = Column(Integer, ForeignKey("b.id"), primary_key=True)
    b = relationship("B")
    b_value = association_proxy("b", "value")

class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    value = Column(String)

在 0.8 之前,像下面这样的查询:

代码语言:javascript复制
s.query(A).filter(A.b_value == None).all()

会产生:

代码语言:javascript复制
SELECT  a.id  AS  a_id,  a.b_id  AS  a_b_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  a.b_id  AND  b.value  IS  NULL)

在 0.9 中,现在会产生:

代码语言:javascript复制
SELECT  a.id  AS  a_id,  a.b_id  AS  a_b_id
FROM  a
WHERE  (EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  a.b_id  AND  b.value  IS  NULL))  OR  a.b_id  IS  NULL

不同之处在于,它不仅检查 b.value,还检查 a 是否根本没有关联到任何 b 行。对于使用这种类型比较的系统,一些父行没有关联行,这将与之前的版本返回不同的结果。

更为关键的是,对于 A.b_value != None,会发出正确的表达式。在 0.8 中,对于没有 bA 行,这将返回 True

代码语言:javascript复制
SELECT  a.id  AS  a_id,  a.b_id  AS  a_b_id
FROM  a
WHERE  NOT  (EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  a.b_id  AND  b.value  IS  NULL))

现在在 0.9 中,检查已经重新设计,以确保 A.b_id 行存在,另外 B.value 不为 NULL:

代码语言:javascript复制
SELECT  a.id  AS  a_id,  a.b_id  AS  a_b_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  a.b_id  AND  b.value  IS  NOT  NULL)

此外,has() 操作符得到增强,使得你可以只针对标量列值调用它,而无需任何条件,它将生成检查关联行是否存在的条件:

代码语言:javascript复制
s.query(A).filter(A.b_value.has()).all()

输出:

代码语言:javascript复制
SELECT  a.id  AS  a_id,  a.b_id  AS  a_b_id
FROM  a
WHERE  EXISTS  (SELECT  1
FROM  b
WHERE  b.id  =  a.b_id)

这等同于 A.b.has(),但允许直接针对 b_value 进行查询。

#2751 ### 关联代理缺失标量返回 None

从标量属性到标量的关联代理现在如果被代理对象不存在将返回 None。这与 SQLAlchemy 中缺少多对一关系返回 None 的事实一致,因此代理值也应该如此。例如:

代码语言:javascript复制
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy

Base = declarative_base()

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)
    b = relationship("B", uselist=False)

    bname = association_proxy("b", "name")

class B(Base):
    __tablename__ = "b"

    id = Column(Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey("a.id"))
    name = Column(String)

a1 = A()

# this is how m2o's always have worked
assert a1.b is None

# but prior to 0.9, this would raise AttributeError,
# now returns None just like the proxied value.
assert a1.bname is None

#2810 ### attributes.get_history() 默认情况下将从数据���查询如果值不存在

修复了关于get_history()的 bug,允许基于列的属性向数据库查询未加载的值,假设 passive 标志保持默认值 PASSIVE_OFF。之前,此标志不会被遵守。此外,新增了一个方法AttributeState.load_history()来补充AttributeState.history属性,它将为未加载的属性发出加载器可调用。

这是一个小改变的示例:

代码语言:javascript复制
from sqlalchemy import Column, Integer, String, create_engine, inspect
from sqlalchemy.orm import Session, attributes
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class A(Base):
    __tablename__ = "a"
    id = Column(Integer, primary_key=True)
    data = Column(String)

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

sess = Session(e)

a1 = A(data="a1")
sess.add(a1)
sess.commit()  # a1 is now expired

# history doesn't emit loader callables
assert inspect(a1).attrs.data.history == (None, None, None)

# in 0.8, this would fail to load the unloaded state.
assert attributes.get_history(a1, "data") == (
    (),
    [
        "a1",
    ],
    (),
)

# load_history() is now equivalent to get_history() with
# passive=PASSIVE_OFF ^ INIT_OK
assert inspect(a1).attrs.data.load_history() == (
    (),
    [
        "a1",
    ],
    (),
)

#2787 ## 行为变更 - 核心

类型对象不再接受被忽略的关键字参数

在 0.8 系列中,大多数类型对象接受任意关键字参数,这些参数会被静默忽略:

代码语言:javascript复制
from sqlalchemy import Date, Integer

# storage_format argument here has no effect on any backend;
# it needs to be on the SQLite-specific type
d = Date(storage_format="%(day)02d.%(month)02d.%(year)04d")

# display_width argument here has no effect on any backend;
# it needs to be on the MySQL-specific type
i = Integer(display_width=5)

这是一个非常古老的 bug,为此在 0.8 系列中添加了一个弃用警告,但因为几乎没有人使用带有“-W”标志的 Python,所以几乎从未见过:

代码语言:javascript复制
$ python -W always::DeprecationWarning ~/dev/sqlalchemy/test.py
/Users/classic/dev/sqlalchemy/test.py:5: SADeprecationWarning: Passing arguments to
type object constructor <class 'sqlalchemy.types.Date'> is deprecated
  d = Date(storage_format="%(day)02d.%(month)02d.%(year)04d")
/Users/classic/dev/sqlalchemy/test.py:9: SADeprecationWarning: Passing arguments to
type object constructor <class 'sqlalchemy.types.Integer'> is deprecated
  i = Integer(display_width=5)

从 0.9 系列开始,TypeEngine 中的“catch all” 构造函数被移除,这些无意义的参数不再被接受。

使用方言特定参数如 storage_formatdisplay_width 的正确方法是使用适当的方言特定类型:

代码语言:javascript复制
from sqlalchemy.dialects.sqlite import DATE
from sqlalchemy.dialects.mysql import INTEGER

d = DATE(storage_format="%(day)02d.%(month)02d.%(year)04d")

i = INTEGER(display_width=5)

那么当我们还需要方言无关的类型时呢?我们使用 TypeEngine.with_variant() 方法:

代码语言:javascript复制
from sqlalchemy import Date, Integer
from sqlalchemy.dialects.sqlite import DATE
from sqlalchemy.dialects.mysql import INTEGER

d = Date().with_variant(
    DATE(storage_format="%(day)02d.%(month)02d.%(year)04d"), "sqlite"
)

i = Integer().with_variant(INTEGER(display_width=5), "mysql")

TypeEngine.with_variant() 并不是新的,它是在 SQLAlchemy 0.7.2 中添加的。所以可以将在 0.8 系列上运行的代码修改为使用这种方法,并在升级到 0.9 之前进行测试。

None 不再能够被用作 “部分 AND” 构造函数

None 不再能够被用作逐步形成 AND 条件的 “后备”。即使一些 SQLAlchemy 内部使用了这种模式,但这种模式并没有被记录在案:

代码语言:javascript复制
condition = None

for cond in conditions:
    condition = condition & cond

if condition is not None:
    stmt = stmt.where(condition)

在 0.9 上,当 conditions 不为空时,将产生 SELECT .. WHERE <condition> AND NULLNone 不再被隐式忽略,而是与在其他上下文中解释 None 时一致。

0.8 和 0.9 的正确代码应该是:

代码语言:javascript复制
from sqlalchemy.sql import and_

if conditions:
    stmt = stmt.where(and_(*conditions))

另一个变体,在 0.9 上对所有后端都有效,但在 0.8 上仅在支持布尔常量的后端上有效:

代码语言:javascript复制
from sqlalchemy.sql import true

condition = true()

for cond in conditions:
    condition = cond & condition

stmt = stmt.where(condition)

在 0.8 上,这将生成一个始终在 WHERE 子句中具有 AND true 的 SELECT 语句,这是不被不支持布尔常量的后端(MySQL,MSSQL)接受的。在 0.9 上,true 常量将在 and_() 连接中被删除。

另请参阅

布尔常量、NULL 常量、连接词的渲染已经得到改进

create_engine() 的 “password” 部分不再将 号视为已编码的空格

不知何故,Python 函数 unquote_plus() 被应用于 URL 的 password 字段,这是对 RFC 1738 中描述的编码规则的错误应用,因为它将空格转义为加号。URL 的字符串化现在只编码 “:”,“@” 或 “/”,不编码其他任何字符,并且现在应用于 usernamepassword 字段(以前只应用于密码)。在解析时,编码字符会被转换,但加号和空格会原样传递:

代码语言:javascript复制
# password: "pass word   other:words"
dbtype://user:pass word   other:words@host/dbname

# password: "apples/oranges"
dbtype://username:apples/oranges@hostspec/database

# password: "apples@oranges@@"
dbtype://username:apples@oranges@@@hostspec/database

# password: '', username is "username@"
dbtype://username@:@hostspec/database

#2873 ### COLLATE 的优先规则已经更改

以前,类似以下的表达式:

代码语言:javascript复制
print((column("x") == "somevalue").collate("en_EN"))

会产生这样的表达式:

代码语言:javascript复制
-- 0.8 behavior
(x  =  :x_1)  COLLATE  en_EN

上述情况被 MSSQL 误解,通常不是任何数据库建议的语法。现在该表达式将生成大多数数据库文档所示的语法:

代码语言:javascript复制
-- 0.9 behavior
x  =  :x_1  COLLATE  en_EN

如果 ColumnOperators.collate() 操作符被应用于右侧列,则会出现潜在的不向后兼容的更改,如下所示:

代码语言:javascript复制
print(column("x") == literal("somevalue").collate("en_EN"))

在 0.8 中,这将产生:

代码语言:javascript复制
x  =  :param_1  COLLATE  en_EN

但在 0.9 中,现在将产生更准确的,但可能不是您想要的形式:

代码语言:javascript复制
x  =  (:param_1  COLLATE  en_EN)

ColumnOperators.collate() 运算符现在在ORDER BY表达式中的使用更加恰当,因为给ASCDESC运算符指定了特定的优先级,这将再次确保不生成括号:

代码语言:javascript复制
>>> # 0.8
>>> print(column("x").collate("en_EN").desc())
(x  COLLATE  en_EN)  DESC
>>> # 0.9
>>> print(column("x").collate("en_EN").desc())
x  COLLATE  en_EN  DESC 

#2879 ### PostgreSQL CREATE TYPE AS ENUM 现在对值应用引号

ENUM 类型现在将对枚举值中的单引号符号应用转义:

代码语言:javascript复制
>>> from sqlalchemy.dialects import postgresql
>>> type = postgresql.ENUM("one", "two", "three's", name="myenum")
>>> from sqlalchemy.dialects.postgresql import base
>>> print(base.CreateEnumType(type).compile(dialect=postgresql.dialect()))
CREATE  TYPE  myenum  AS  ENUM  ('one','two','three''s') 

已经转义单引号符号的现有解决方法需要进行修改,否则它们现在会双重转义。

#2878

新特性

事件移除 API

使用listen()listens_for()建立的事件现在可以使用新的remove()函数进行移除。传递给remove()targetidentifierfn参数需要与监听时发送的完全匹配,并且事件将从其已建立的所有位置中移除:

代码语言:javascript复制
@event.listens_for(MyClass, "before_insert", propagate=True)
def my_before_insert(mapper, connection, target):
  """listen for before_insert"""
    # ...

event.remove(MyClass, "before_insert", my_before_insert)

在上面的示例中,设置了propagate=True标志。这意味着my_before_insert()被建立为MyClass以及MyClass的所有子类的监听器。系统跟踪到my_before_insert()监听函数在此调用的结果中被放置的所有位置,并在调用remove()时将其移除。

移除系统使用注册表将传递给listen()的参数与事件监听器的集合相关联,这些监听器在许多情况下是原始用户提供的函数的包装版本。此注册表大量使用弱引用,以允许所有包含的内容(如监听器目标)在其超出范围时被垃圾收集。

#2268 ### 新查询选项 API; load_only() 选项

加载器选项的系统,如joinedload()subqueryload()lazyload()defer()等,都建立在一个称为Load的新系统之上。Load提供了一种“方法链式”(又称生成式)的加载器选项方法,因此不再需要使用点号或多个属性名称将长路径连接在一起,而是为每个路径提供明确的加载器样式。

虽然新方式稍微更冗长,但更容易理解,因为在应用哪些选项到哪些路径上没有歧义;它简化了选项的方法签名,并为基于列的选项提供了更大的灵活性。旧系统将一直保持功能,并且所有样式都可以混合使用。

旧方式

要在多元素路径中的每个链接上设置特定的加载样式,必须使用_all()选项:

代码语言:javascript复制
query(User).options(joinedload_all("orders.items.keywords"))

新方式

现在加载器选项是可链式的,因此相同的joinedload(x)方法等同地应用于每个链接,无需在joinedload()joinedload_all()之间保持清晰:

代码语言:javascript复制
query(User).options(joinedload("orders").joinedload("items").joinedload("keywords"))

旧方式

在基于子类的路径上设置选项需要将路径中的所有链接拼写为类绑定属性,因为需要调用PropComparator.of_type()方法:

代码语言:javascript复制
session.query(Company).options(
    subqueryload_all(Company.employees.of_type(Engineer), Engineer.machines)
)

新方式

只有路径中实际需要PropComparator.of_type()的元素需要设置为类绑定属性,之后可以恢复使用基于字符串的名称:

代码语言:javascript复制
session.query(Company).options(
    subqueryload(Company.employees.of_type(Engineer)).subqueryload("machines")
)

旧方式

在长路径中设置加载器选项的最后一个链接使用的语法看起来很像应该为路径中的所有链接设置选项,导致混淆:

代码语言:javascript复制
query(User).options(subqueryload("orders.items.keywords"))

新方式

现在可以使用defaultload()来明确指定路径,其中现有的加载器样式不应更改。更冗长但意图更清晰:

代码语言:javascript复制
query(User).options(defaultload("orders").defaultload("items").subqueryload("keywords"))

仍然可以利用点��样式,特别是在跳过几个路径元素的情况下:

代码语言:javascript复制
query(User).options(defaultload("orders.items").subqueryload("keywords"))

旧方式

在路径上需要为每一列拼写完整路径的 defer() 选项:

代码语言:javascript复制
query(User).options(defer("orders.description"), defer("orders.isopen"))

新方式

一个到达目标路径的单个 Load 对象可以反复调用 Load.defer()

代码语言:javascript复制
query(User).options(defaultload("orders").defer("description").defer("isopen"))
加载类

Load 类可以直接用于提供“绑定”目标,特别是当存在多个父实体时:

代码语言:javascript复制
from sqlalchemy.orm import Load

query(User, Address).options(Load(Address).joinedload("entries"))
仅加载

一个新选项 load_only() 实现了“除了延迟加载其他所有内容”的加载方式,仅加载给定列并推迟其余内容:

代码语言:javascript复制
from sqlalchemy.orm import load_only

query(User).options(load_only("name", "fullname"))

# specify explicit parent entity
query(User, Address).options(Load(User).load_only("name", "fullname"))

# specify path
query(User).options(joinedload(User.addresses).load_only("email_address"))
类特定的通配符

使用 Load,可以使用通配符来设置给定实体上所有关系(或者列)的加载方式,而不影响其他实体:

代码语言:javascript复制
# lazyload all User relationships
query(User).options(Load(User).lazyload("*"))

# undefer all User columns
query(User).options(Load(User).undefer("*"))

# lazyload all Address relationships
query(User).options(defaultload(User.addresses).lazyload("*"))

# undefer all Address columns
query(User).options(defaultload(User.addresses).undefer("*"))

#1418 ### 新的 text() 功能

text() 构造获得了新的方法:

TextClause.bindparams() 允许灵活设置绑定参数类型和值:

代码语言:javascript复制
# setup values
stmt = text(
    "SELECT id, name FROM user WHERE name=:name AND timestamp=:timestamp"
).bindparams(name="ed", timestamp=datetime(2012, 11, 10, 15, 12, 35))

# setup types and/or values
stmt = (
    text("SELECT id, name FROM user WHERE name=:name AND timestamp=:timestamp")
    .bindparams(bindparam("name", value="ed"), bindparam("timestamp", type_=DateTime()))
    .bindparam(timestamp=datetime(2012, 11, 10, 15, 12, 35))
)

TextClause.columns() 取代了 text()typemap 选项,返回一个新的构造 TextAsFrom

代码语言:javascript复制
# turn a text() into an alias(), with a .c. collection:
stmt = text("SELECT id, name FROM user").columns(id=Integer, name=String)
stmt = stmt.alias()

stmt = select([addresses]).select_from(
    addresses.join(stmt), addresses.c.user_id == stmt.c.id
)

# or into a cte():
stmt = text("SELECT id, name FROM user").columns(id=Integer, name=String)
stmt = stmt.cte("x")

stmt = select([addresses]).select_from(
    addresses.join(stmt), addresses.c.user_id == stmt.c.id
)

#2877 ### 从 SELECT 插入

经过几乎多年的毫无意义的拖延,这个相对较小的语法特性已经被添加,并且也被回溯到了 0.8.3,所以在技术上并不是 0.9 中的“新”特性。可以将一个 select() 构造或其他兼容的构造传递给新方法 Insert.from_select(),它将用于渲染一个 INSERT .. SELECT 构造:

代码语言:javascript复制
>>> from sqlalchemy.sql import table, column
>>> t1 = table("t1", column("a"), column("b"))
>>> t2 = table("t2", column("x"), column("y"))
>>> print(t1.insert().from_select(["a", "b"], t2.select().where(t2.c.y == 5)))
INSERT  INTO  t1  (a,  b)  SELECT  t2.x,  t2.y
FROM  t2
WHERE  t2.y  =  :y_1 

该构造足够智能,也可以适应诸如类和 Query 对象等 ORM 对象:

代码语言:javascript复制
s = Session()
q = s.query(User.id, User.name).filter_by(name="ed")
ins = insert(Address).from_select((Address.id, Address.email_address), q)

渲染:

代码语言:javascript复制
INSERT  INTO  addresses  (id,  email_address)
SELECT  users.id  AS  users_id,  users.name  AS  users_name
FROM  users  WHERE  users.name  =  :name_1

#722 ### select()Query() 上的新 FOR UPDATE 支持

试图简化 Core 和 ORM 中对 SELECT 语句上的 FOR UPDATE 子句的规范,并支持 PostgreSQL 和 Oracle 支持的 FOR UPDATE OF SQL。

使用核心 GenerativeSelect.with_for_update(),可以单独指定 FOR SHARENOWAIT 等选项,而不是链接到任意字符串代码:

代码语言:javascript复制
stmt = select([table]).with_for_update(read=True, nowait=True, of=table)

在 Posgtresql 上述语句可能会呈现为:

代码语言:javascript复制
SELECT  table.a,  table.b  FROM  table  FOR  SHARE  OF  table  NOWAIT

Query 对象获得了一个类似的方法 Query.with_for_update(),其行为方式相同。这个方法取代了现有的 Query.with_lockmode() 方法,该方法使用不同的系统翻译 FOR UPDATE 子句。目前,“lockmode” 字符串参数仍然被 Session.refresh() 方法接受。### 可配置原生浮点类型的浮点字符串转换精度

每当 DBAPI 返回一个要转换为 Python Decimal() 的 Python 浮点类型时,SQLAlchemy 都会进行转换,这必然涉及将浮点值转换为字符串的中间步骤。此字符串转换的比例以前是硬编码为 10,现在可以配置。这个设置在 NumericFloat 类型以及所有 SQL 和方言特定的后代类型上都可用,使用参数 decimal_return_scale。如果类型支持 .scale 参数,比如 Numeric 和一些浮点类型如 DOUBLE,如果没有另外指定,.scale 的值将作为 .decimal_return_scale 的默认值。如果 .scale.decimal_return_scale 都不存在,则默认值为 10。例如:

代码语言:javascript复制
from sqlalchemy.dialects.mysql import DOUBLE
import decimal

data = Table(
    "data",
    metadata,
    Column("double_value", mysql.DOUBLE(decimal_return_scale=12, asdecimal=True)),
)

conn.execute(
    data.insert(),
    double_value=45.768392065789,
)
result = conn.scalar(select([data.c.double_value]))

# previously, this would typically be Decimal("45.7683920658"),
# e.g. trimmed to 10 decimal places

# now we get 12, as requested, as MySQL can support this
# much precision for DOUBLE
assert result == decimal.Decimal("45.768392065789")

#2867 ### ORM 查询的列捆绑

Bundle 允许查询一组列,然后将它们分组为查询返回的元组下的一个名称。 Bundle 的初始目的是 1. 允许将“复合”ORM 列作为列式结果集中的单个值返回,而不是将它们展开为单独的列,以及 2. 允许在 ORM 中创建自定义结果集构造,使用临时列和返回类型,而不涉及映射类的更重量级机制。

另请参见

组合属性现在在按属性查询时以其对象形式返回

使用 Bundles 对选定属性进行分组

#2824

服务器端版本计数

ORM 的版本控制功能(现在也在配置版本计数器中有文档)现在可以利用服务器端的版本计数方案,例如由触发器或数据库系统列生成的方案,以及版本 _id_counter 函数之外的条件编程方案。 通过向 version_id_generator 参数提供值 False,ORM 将使用已设置的版本标识符,或者在发出 INSERT 或 UPDATE 时同时从每行获取版本标识符。 当使用服务器生成的版本标识符时,强烈建议仅在具有强大 RETURNING 支持的后端上使用此功能(PostgreSQL、SQL Server;Oracle 也支持 RETURNING,但 cx_oracle 驱动程序仅具有有限的支持),否则额外的 SELECT 语句将增加显着的性能开销。 在服务器端版本计数器提供的示例中说明了使用 PostgreSQL 的 xmin 系统列以将其与 ORM 的版本控制功能集成。

另请参见

服务器端版本计数器

#2793

include_backrefs=False 选项用于 @validates

validates() 函数现在接受一个选项 include_backrefs=True,这将绕过为从 backref 发起的事件触发验证器的情况:

代码语言:javascript复制
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, validates
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)
    bs = relationship("B", backref="a")

    @validates("bs")
    def validate_bs(self, key, item):
        print("A.bs validator")
        return item

class B(Base):
    __tablename__ = "b"

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

    @validates("a", include_backrefs=False)
    def validate_a(self, key, item):
        print("B.a validator")
        return item

a1 = A()
a1.bs.append(B())  # prints only "A.bs validator"

#1535

PostgreSQL JSON 类型

PostgreSQL 方言现在具有一个 JSON 类型,以补充 HSTORE 类型。

另请参见

JSON

#2581

Automap 扩展

0.9.1 中添加了一个名为 sqlalchemy.ext.automap 的新扩展。 这是一个 实验性 扩展,它扩展了声明性的功能以及 DeferredReflection 类的功能。 本质上,该扩展提供了一个基类 AutomapBase,根据给定的表元数据自动生成映射类和它们之间的关系。

通常使用的 MetaData 可能是通过反射生成的,但不要求使用反射。 最基本的用法说明了 sqlalchemy.ext.automap 如何根据反射模式提供映射类,包括关系:

代码语言:javascript复制
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy import create_engine

Base = automap_base()

# engine, suppose it has two tables 'user' and 'address' set up
engine = create_engine("sqlite:///mydatabase.db")

# reflect the tables
Base.prepare(engine, reflect=True)

# mapped classes are now created with names matching that of the table
# name.
User = Base.classes.user
Address = Base.classes.address

session = Session(engine)

# rudimentary relationships are produced
session.add(Address(email_address="foo@bar.com", user=User(name="foo")))
session.commit()

# collection-based relationships are by default named "<classname>_collection"
print(u1.address_collection)

除此之外,AutomapBase 类是一个声明基类,并支持所有声明所支持的功能。 “自动映射”功能可用于现有的、明确声明的模式,以仅生成关系和缺失类。 命名方案和关系生成例程可以通过可调用函数添加。

希望 AutomapBase 系统提供了一个快速和现代化的解决方案,解决了非常著名的 SQLSoup 也试图解决的问题,即从现有数据库快速生成一个简单的对象模型。 通过严格在映射器配置级别解决问题,并与现有的声明类技术完全集成,AutomapBase 试图提供一个与问题紧密集成的方法,以便快速生成临时映射。

另请参阅

Automap

行为改进

应该产生没有兼容性问题的改进,除非在极为罕见和异常的假设情况下,但最好知道这些改进,以防出现意外问题。

许多 JOIN 和 LEFT OUTER JOIN 表达式将不再被包装在 (SELECT * FROM …) AS ANON_1 中

多年来,SQLAlchemy ORM 一直无法将 JOIN 嵌套在现有 JOIN 的右侧(通常是 LEFT OUTER JOIN,因为 INNER JOIN 总是可以被展平):

代码语言:javascript复制
SELECT  a.*,  b.*,  c.*  FROM  a  LEFT  OUTER  JOIN  (b  JOIN  c  ON  b.id  =  c.id)  ON  a.id

这是因为 SQLite 直到版本 3.7.16 都无法解析上述格式的语句:

代码语言:javascript复制
SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table a(id integer);
sqlite> create table b(id integer);
sqlite> create table c(id integer);
sqlite> select a.id, b.id, c.id from a left outer join (b join c on b.id=c.id) on b.id=a.id;
Error: no such column: b.id

右外连接当然是解决右侧括号化的另一种方法;这将变得非常复杂和视觉上不愉快,但幸运的是 SQLite 也不支持 RIGHT OUTER JOIN

0 人点赞