Release: 1.4.25 | Release Date: September 22, 2021

SQLAlchemy 1.4 Documentation

更改和迁移

sqlacalchemy 0.8有什么新功能?

关于此文档

本文档描述了从2012年10月开始进行维护发布的SQLAlchemy版本0.7和预计在2013年初发布的SQLAlchemy版本0.8之间的变化。

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

介绍

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

sqlacalchemy版本将在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 它自然是一些错误的来源,以及上下文管理器(即, with: )也许在某些情况下 try:/except:/else: 有助于代码可读性的块。

SQLAlchemy最终也将放弃2.5支持-当达到2.6作为基线时,SQLAlchemy将移动到使用2.6/3.3就地兼容性,删除 2to3 工具和维护同时与python 2和3一起工作的源代码库。

新ORM功能

重写 relationship() 力学

0.8提供了一个改进了很多且功能强大的系统, relationship() 确定如何在两个实体之间联接。新系统包括以下功能:

  • 这个 primaryjoin 论证是 不再需要 在构造 relationship() 针对具有多个指向目标的外键路径的类。只有 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_idfolder_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 = ?
  • 以前困难的自定义连接条件,如涉及函数和/或类型转换的条件,现在在大多数情况下都将按预期工作:

    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() 机制利用了一个称为 annotations . 这些注释也可以通过 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>

#2208

新的具有多态性()功能,可以在任何地方使用

这个 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'))

参见

与多态性一起使用 -多态加载控制的最新文档。

#2333

对于使用alias()的_type(),对于使用_多态性(),any(),has(),joinedload(),subqueryload(),contains_eager()。

这个 PropComparator.of_type() 方法用于指定在沿着 relationship() 那有 polymorphic 作为其目标的映射。此方法现在可用于 任意数 目标子类型,通过将其与新的 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

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

映射器和实例事件现在可以与未映射的超类相关联,这些事件将在映射这些子类时传播到子类。这个 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

声明性中的新延迟选择功能

“延迟反射”示例已移动到声明性中支持的功能。此功能允许只使用占位符构造声明性映射类 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)

#2485

ORM类现在被核心构造接受

当SQL表达式与 Query.filter() ,如 User.id == 5 ,始终与核心结构(如 select() ,将映射类本身传递给 select()Select.select_from()Select.correlate() . 新的SQL注册系统允许将映射类作为核心中的FROM子句接受::

from sqlalchemy import select

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

上面,地图 User 类将扩展到 Table 向哪个 User 被映射。

#2245

query.update()支持update..from

新的update..From Mechanics在query.update()中工作。下面,我们针对 SomeEntity ,根据添加FROM子句(或等效的,取决于后端) SomeOtherEntity ::

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

特别是,支持对已联接继承实体的更新,前提是更新的目标是要筛选的表的本地目标,或者如果父表和子表是混合的,则它们将在查询中显式联接。下面给出 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

roll back()将只从begin_嵌套()回滚“脏”对象。

一种行为变化,它应该通过以下方式提高使用savepoint的用户的效率 Session.begin_nested()rollback() ,只有上次刷新后变脏的对象才会过期,其余的 Session 保持完好。这是因为对保存点的回滚不会终止包含事务的隔离,所以除了那些在当前事务中未刷新的更改之外,不需要任何到期。

#2452

缓存示例现在使用dogpile.cache

缓存示例现在使用 dogpile.cache . dogpile.cache是对烧杯缓存部分的重写,它具有非常简单和快速的操作,以及对分布式锁定的支持。

请注意,doggile示例和上一个烧杯示例使用的sqlAlchemy API发生了轻微变化,特别是需要如烧杯示例所示的这种变化:

--- 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_caching

#2589

新的核心功能

完全可扩展的类型级核心操作员支持

到目前为止,除了 ColumnOperators.op() 方法,这是“刚刚足够”使事情工作。也从来没有为核心部署任何允许覆盖现有操作员行为的系统。到目前为止,唯一可以灵活地重新定义运算符的方法是在ORM层中,使用 column_property() 给定一个 comparator_factory 争论。因此,像地理炼金术这样的第三方库被迫以ORM为中心,依靠一系列的黑客来应用新的操作,并让它们正确地传播。

核心中的新操作员系统添加了一个一直缺失的钩子,它将新的和重写的操作员与 类型 . 毕竟,它并不是真正驱动当前操作类型的列、强制转换运算符或SQL函数,而是 type 表达方式。实现细节是最少的-只有几个额外的方法被添加到核心中 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的数组类型相关联的新操作。它还为现有类型获取更多特定于这些类型的运算符铺平了道路,例如更多的字符串、整数和日期运算符。

#2547

多值支持插入

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

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

#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 类型定义将在 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)

这个特性在新版本的地理计算中也被大量使用,以基于类型规则将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 等等,这使得它能够在核心和ORM结构之间流畅地工作。

新方法 Select.correlate_except()

select() 现在有了一个方法 Select.correlate_except() 它指定“除指定的子句外,对所有FROM子句进行关联”。它可用于映射相关子查询应正常关联的场景,除非与可选择的特定目标相关联:

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类型

支持PostgreSQL HSTORE 类型现在可用为 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 类型将接受可选的“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]])
)

选择中的切片访问::

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

在更新中切片更新::

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

array

#2441

sqlite的新的、可配置的日期和时间类型

sqlite没有内置的日期、时间或日期时间类型,相反,它提供了一些将日期和时间值存储为字符串或整数的支持。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

行为改变

把“悬而未决”的对象当作“孤儿”的考虑变得更具侵略性。

这是对0.8系列的后期添加,但是希望新的行为在更广泛的情况下更为一致和直观。ORM至少在0.4版中包含这样的行为:一个“挂起”的对象,意味着它与 Session 但尚未插入数据库,将自动从 Session 当它变为“孤立”时,这意味着它已与引用它的父对象取消关联 delete-orphan 在配置的 relationship() . 此行为旨在近似镜像持久(即,已插入)对象的行为,ORM将根据分离事件的拦截为成为孤立对象的此类对象发出删除。

行为变化对由不同类型的父对象引用的对象起作用,每种父对象都指定 delete-orphan ;典型的例子是 association object 以多对多的模式连接其他两种对象。以前,这种行为使得挂起的对象只有在与 all 它的父母。随着行为的改变,挂起的对象一旦从 any 以前与之有联系的父母。此行为旨在与持久对象的行为更接近,持久对象一旦从任何父对象中取消关联,就会被删除。

旧行为的基本原理至少可以追溯到0.4版,基本上是一个防御决策,当一个对象仍在为insert构建时,它试图减轻混乱。但现实是,物体与 Session 在任何情况下,只要它附属于任何新的父母。

如果对象不是首先与那些父对象相关联,或者如果它被删除,但随后又与 Session 通过后续的附件事件,但仍然没有完全关联。在这种情况下,预计数据库将发出完整性错误,因为可能不存在未填充的空外键列。ORM决定让这些插入尝试发生,基于这样一个判断:一个对象只部分地与它所需的父对象关联,但已经与其中一些父对象积极关联,而不是一个用户错误,而不是一个应该被静默跳过的故意遗漏-静默跳过插入这会使这种性质的用户错误很难调试。

对于可能依赖它的应用程序,可以为任何 Mapper 通过指定标志 legacy_is_orphan 作为映射器选项。

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

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事件在项与会话关联后激发,而不是在添加之前激发

在附加之后使用的事件处理程序现在可以假定给定实例与给定会话关联:

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

有些用例要求它以这种方式工作。但是,其他用例要求 not 然而,会话的一部分,例如当一个查询打算加载某个实例所需的某种状态时,会先发出autoflush,否则会过早地刷新目标对象。这些用例应该使用新的“在附加”事件:

@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

查询现在像select()一样自动关联

以前有必要打电话 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 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

create_all()和drop_all()现在可以使用空列表了。

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

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

#2664

已修复的事件目标 InstrumentationEvents

这个 InstrumentationEvents 一系列事件目标已经记录了事件将只根据作为目标传递的实际类被触发。通过0.7,情况并非如此,任何事件侦听器都应用于 InstrumentationEvents 将为所有映射的类调用。在0.8中,添加了额外的逻辑,这样事件将只对中发送的那些类调用。这个 propagate 此处的标志设置为 True 默认情况下,类检测事件通常用于截取尚未创建的类。

#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”的比较中也会发生同样的事情,并且总体而言,这一级别的猜测超出了sqlacalchemy的常规范围,因此行为被删除。

#2277

修正了 Session.is_modified()

这个 Session.is_modified() 方法接受参数 passive 基本上不需要,所有情况下的参数都应该是值 True -当其违约时 False 它会影响数据库,并经常触发自动刷新,这本身会改变结果。0.8 passive 参数将不起作用,并且卸载的属性将永远不会检查历史记录,因为根据定义,卸载的属性上不能有挂起的状态更改。

#2320

Column.key is honored in the Select.c attribute of select() with Select.apply_labels()

表达式系统的用户知道 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 .

#2397

单亲警告现在是一个错误

A relationship() 这是多对一或多对多,并且指定了“cascade='all,delete orphan'”,这是一个尴尬的但仍然受支持的用例(带有限制),如果关系没有指定 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_约束

这两种方法 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

这是为了一些早期需要这种语言的方言的好处,比如甲骨文和火鸟,但在现代用法中,我们有更准确的方法来处理这两个平台的不区分大小写的行为。

接下来,只有通过传递标志,此行为才是可选的 `case_sensitive=False`create_engine() `但是,否则,从行请求的列名必须尽可能与大小写匹配。

#2423

InstrumentationManager 替代类工具现在是一个扩展

这个 sqlalchemy.orm.interfaces.InstrumentationManager class is moved to sqlalchemy.ext.instrumentation.InstrumentationManager. The "alternate instrumentation" system was built for the benefit of a very small number of installations that needed to work with existing or unusual class instrumentation systems, and generally is very seldom used. The complexity of this system has been exported to an ext. 模块。它在导入之前保持未使用状态,通常是在第三方库导入时 InstrumentationManager ,此时注入回 sqlalchemy.orm 通过替换默认值 InstrumentationFactory 具有 ExtendedInstrumentationRegistry .

远离的

SQLSoup

sqlsoup是一个方便的包,它在sqlAlchemy ORM之上提供了一个可选的接口。现在,sqlsoup被转移到自己的项目中,并单独记录/发布;请参见https://bitback.org/zzzeek/sqlsoup。

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

#2262

MutableType

SQLAlChemy ORM中较旧的“可变”系统已被删除。这指的是 MutableType 接口,该接口应用于诸如 PickleType 并有条件地 TypeDecorator 因为非常早的SQLAlChemy版本已经为ORM提供了一种检测所谓的“可变”数据结构(如JSON结构和PICKED对象)中的更改的方法。然而,该实现从来都不合理,并且强制在工作单元上采用非常低效的使用模式,从而导致在刷新期间对所有对象进行昂贵的扫描。在0.7中, sqlalchemy.ext.mutable 引入了扩展,以便用户定义的数据类型可以在发生更改时适当地将事件发送到工作单元。

今天,使用 MutableType 预计这一数字会很低,因为几年来一直有关于其效率低下的警告。

#2442

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

我们留下了化名 sqlalchemy.exceptions 试图使一些尚未升级使用的非常老的库变得更容易一些。 sqlalchemy.exc . 但是,一些用户仍然对它感到困惑,所以在0.8中,我们将完全消除这种困惑。

#2433

Previous: SQLAlchemy 0.9有什么新功能? Next: SQLAlchemy 0.7有什么新功能?