收集配置和技术

优质
小牛编辑
134浏览
2023-12-01

这个 relationship() 函数定义两个类之间的链接。当链接定义了一对多或多对多关系时,当加载和操作对象时,它被表示为一个Python集合。本节介绍有关收集配置和技术的其他信息。

处理大型集合

的默认行为 relationship() 是将完全加载项集合中,根据加载策略的关系。另外, Session 默认情况下,只知道如何删除会话中实际存在的对象。当父实例标记为删除并刷新时, Session 在中加载其子项的完整列表,以便也可以删除它们,或者将它们的外键值设置为空;这是为了避免违反约束。对于大量子项集合,有几种策略可以在加载时和删除时绕过子项的完全加载。

动态关系加载器

注解

这是一项传统功能。使用 with_parent() 过滤与联合 select() 是不是 2.0 style 使用方法。对于不应加载的关系,请设置 relationship.lazynoload

注解

此加载程序在一般情况下与 异步I/O(异步) 分机。它的使用有一些限制,如中所示 Asyncio dynamic guidelines

A relationship() 可以配置对应于大型集合的 Query 对象,这允许根据条件筛选关系。这个班级是一个特殊的班级。 AppenderQuery 在访问时代替集合返回。可以显式或通过数组切片应用过滤标准以及限制和偏移量:

class User(Base):
    __tablename__ = 'user'

    posts = relationship(Post, lazy="dynamic")

jack = session.query(User).get(id)

# filter Jack's blog posts
posts = jack.posts.filter(Post.headline=='this is a post')

# apply array slices
posts = jack.posts[5:20]

动态关系支持有限的写操作,通过 AppenderQuery.append()AppenderQuery.remove() 方法::

oldpost = jack.posts.filter(Post.headline=='old post').one()
jack.posts.remove(oldpost)

jack.posts.append(Post('new post'))

由于动态关系的读取端总是查询数据库,因此在刷新数据之前,对基础集合的更改将不可见。但是,只要在 Session 在使用中,每次集合即将发出查询时,都会自动发生这种情况。

要在backref上放置动态关系,请使用 backref() 功能与 lazy='dynamic' ::

class Post(Base):
    __table__ = posts_table

    user = relationship(User,
                backref=backref('posts', lazy='dynamic')
            )

请注意,此时不能将热切/懒惰加载选项与动态关系结合使用。

Object NameDescription

AppenderQuery

支持基本集合存储操作的动态查询。

class sqlalchemy.orm.AppenderQuery(attr, state)

“noload”关系从不从数据库加载,即使在访问时也是如此。它是使用 lazy='noload' ::

class MyClass(Base):
    __tablename__ = 'some_table'

    children = relationship(MyOtherClass, lazy='noload')

上面, children 集合是完全可写的,对它所做的更改将被持久化到数据库中,并且在添加时本地可供读取。但是,当 MyClass 是从数据库中新加载的, children 集合保持为空。noload策略也可以在查询选项的基础上使用 noload() 装入程序选项。

或者,“提升”加载的关系将提升 InvalidRequestError 其中,属性通常会发出延迟加载:

class MyClass(Base):
    __tablename__ = 'some_table'

    children = relationship(MyOtherClass, lazy='raise')

上面,属性访问 children 如果集合之前未被预先加载,则将引发异常。这包括读取访问,但对于集合,也会影响写入访问,因为如果不首先加载集合,就无法对其进行更改。其基本原理是确保应用程序不会在特定上下文中发出任何意外的延迟负载。“提升”策略不必通过读取SQL日志来确定所有必要的属性都是预先加载的,“提升”策略将导致卸载的属性在被访问时立即提升。在查询选项的基础上,还可以使用 raiseload() 装入程序选项。

1.1 新版功能: 增加了“提升”装载机策略。

参见

使用raiseload防止不需要的懒惰负载

使用被动删除

在具有ORM关系的DELETE cascade中使用外键 对于这个部分。

自定义集合访问

将一对多或多对多关系映射会导致通过父实例上的属性可访问的值集合。默认情况下,此集合是 list ::

class Parent(Base):
    __tablename__ = 'parent'
    parent_id = Column(Integer, primary_key=True)

    children = relationship(Child)

parent = Parent()
parent.children.append(Child())
print(parent.children[0])

集合不限于列表。通过指定 relationship.collection_class 选择权 relationship() ::

class Parent(Base):
    __tablename__ = 'parent'
    parent_id = Column(Integer, primary_key=True)

    # use a set
    children = relationship(Child, collection_class=set)

parent = Parent()
child = Child()
parent.children.add(child)
assert child in parent.children

词典集

当使用字典作为集合时,需要一些额外的细节。这是因为对象总是以列表的形式从数据库中加载,并且密钥生成策略必须可用于正确填充字典。这个 attribute_mapped_collection() 函数是实现简单字典集合的最常见方法。它生成一个字典类,将映射类的特定属性应用为键。下面我们映射一个 Item 包含字典的类 Note 键入到的项 Note.keyword 属性:

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

Base = declarative_base()

class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    notes = relationship("Note",
                collection_class=attribute_mapped_collection('keyword'),
                cascade="all, delete-orphan")

class Note(Base):
    __tablename__ = 'note'
    id = Column(Integer, primary_key=True)
    item_id = Column(Integer, ForeignKey('item.id'), nullable=False)
    keyword = Column(String)
    text = Column(String)

    def __init__(self, keyword, text):
        self.keyword = keyword
        self.text = text

Item.notes 是一本字典:

>>> item = Item()
>>> item.notes['a'] = Note('a', 'atext')
>>> item.notes.items()
{'a': <__main__.Note object at 0x2eaaf0>}

attribute_mapped_collection() 将确保 .keyword 每个的属性 Note 符合字典中的键。例如,当分配给 Item.notes ,我们提供的字典键必须与实际的字典键匹配。 Note 对象:

item = Item()
item.notes = {
            'a': Note('a', 'atext'),
            'b': Note('b', 'btext')
        }

属性 attribute_mapped_collection() 用作密钥根本不需要映射!使用常规的python @property 允许将对象的任何细节或细节组合用作键,如下所示,当我们将其建立为 Note.keyword 前十个字母 Note.text 领域:

class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    notes = relationship("Note",
                collection_class=attribute_mapped_collection('note_key'),
                backref="item",
                cascade="all, delete-orphan")

class Note(Base):
    __tablename__ = 'note'
    id = Column(Integer, primary_key=True)
    item_id = Column(Integer, ForeignKey('item.id'), nullable=False)
    keyword = Column(String)
    text = Column(String)

    @property
    def note_key(self):
        return (self.keyword, self.text[0:10])

    def __init__(self, keyword, text):
        self.keyword = keyword
        self.text = text

上面我们加了一个 Note.item 后盾。分配给这个反向关系, Note 被添加到 Item.notes 字典和密钥自动为我们生成:

>>> item = Item()
>>> n1 = Note("a", "atext")
>>> n1.item = item
>>> item.notes
{('a', 'atext'): <__main__.Note object at 0x2eaaf0>}

其他内置字典类型包括 column_mapped_collection() 就像是 attribute_mapped_collection() 除非给出 Column 直接对象:

from sqlalchemy.orm.collections import column_mapped_collection

class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    notes = relationship("Note",
                collection_class=column_mapped_collection(Note.__table__.c.keyword),
                cascade="all, delete-orphan")

以及 mapped_collection() 它传递给任何可调用函数。注意,它通常更容易使用 attribute_mapped_collection() 伴随着 @property 如前所述:

from sqlalchemy.orm.collections import mapped_collection

class Item(Base):
    __tablename__ = 'item'
    id = Column(Integer, primary_key=True)
    notes = relationship("Note",
                collection_class=mapped_collection(lambda note: note.text[0:10]),
                cascade="all, delete-orphan")

字典映射通常与“关联代理”扩展结合使用,以生成简化的字典视图。见 代理到基于字典的集合复合关联代理 举个例子。

处理关键突变和字典集合的回填充

使用时 attribute_mapped_collection() ,字典的“键”取自目标对象的属性。 不跟踪对该键的更改 . 这意味着密钥必须在第一次使用时分配给,如果密钥更改,则集合将不会发生变化。当依赖backrefs填充属性映射集合时,这可能是一个典型的问题。考虑到以下因素:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)
    bs = relationship(
        "B",
        collection_class=attribute_mapped_collection("data"),
        back_populates="a",
    )


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))
    data = Column(String)

    a = relationship("A", back_populates="bs")

如果我们创建一个 B() 指的是 A() ,后面的填充将添加 B()A.bs 但是,如果 B.data 尚未设置,关键在于 None ::

>>> a1 = A()
>>> b1 = B(a=a1)
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}

设置 b1.data 事后不更新集合:

>>> b1.data = 'the key'
>>> a1.bs
{None: <test3.B object at 0x7f7b1023ef70>}

如果一个人试图建立 B() 在构造函数中。参数的顺序会改变结果:

>>> B(a=a1, data='the key')
<test3.B object at 0x7f7b10114280>
>>> a1.bs
{None: <test3.B object at 0x7f7b10114280>}

vs::

>>> B(data='the key', a=a1)
<test3.B object at 0x7f7b10114340>
>>> a1.bs
{'the key': <test3.B object at 0x7f7b10114340>}

如果以这种方式使用backref,请确保使用 __init__ 方法。

以下事件处理程序也可用于跟踪集合中的更改:

from sqlalchemy import event

from sqlalchemy.orm import attributes

@event.listens_for(B.data, "set")
def set_item(obj, value, previous, initiator):
    if obj.a is not None:
        previous = None if previous == attributes.NO_VALUE else previous
        obj.a.bs[value] = obj
        obj.a.bs.pop(previous)
Object NameDescription

attribute_mapped_collection(attr_name)

基于字典的集合类型,具有基于属性的键控。

column_mapped_collection(mapping_spec)

基于字典的集合类型,具有基于列的键控。

mapped_collection(keyfunc)

具有任意键控的基于字典的集合类型。

function sqlalchemy.orm.collections.attribute_mapped_collection(attr_name)

您也可以为集合使用自己的类型。在简单情况下,继承自 listset 只需要添加自定义行为。在其他情况下,需要特殊的装饰器来告诉SQLAlchemy关于集合如何操作的更多细节。

我需要自定义集合实现吗?

在大多数情况下根本不是!“自定义”集合最常见的用例是将传入值验证或封送到新表单中的用例,例如成为类实例的字符串,或者以某种方式超越并表示内部数据的用例,在不同表单的外部显示该数据的“视图”。

对于第一个用例, validates() 在所有情况下,为了验证和简单的封送,decorator是截取传入值的最简单方法。见 简单验证器 举个例子。

对于第二个用例, 关联代理 扩展是一个测试良好、广泛使用的系统,它根据目标对象上的某些属性提供集合的读/写“视图”。因为目标属性可以是 @property 它实际上返回任何内容,集合的大量“可选”视图可以用几个函数构造。这种方法使底层的映射集合不受影响,并且避免了需要在一个方法一个方法的基础上仔细地调整集合行为。

当集合需要在访问或突变操作时具有特殊的行为,而这些操作不能在集合外部建模时,定制的集合非常有用。当然,它们可以与上述两种方法相结合。

SQLAlchemy中的集合是透明的 仪器化的 . 检测意味着跟踪对集合的正常操作,并导致在刷新时将更改写入数据库。此外,收集操作可能会触发 事件 这表明必须进行一些二次操作。辅助操作的示例包括将子项保存在父项的 Session (即 save-update 级联),以及同步双向关系的状态(即 backref()

collections包了解列表、集合和dict的基本接口,并将自动将instruction应用于这些内置类型及其子类。通过duck类型检测和检测实现基本集合接口的对象派生类型:

class ListLike(object):
    def __init__(self):
        self.data = []
    def append(self, item):
        self.data.append(item)
    def remove(self, item):
        self.data.remove(item)
    def extend(self, items):
        self.data.extend(items)
    def __iter__(self):
        return iter(self.data)
    def foo(self):
        return 'foo'

appendremoveextend 是已知的类似列表的方法,将自动检测。 __iter__ 不是mutator方法,不会被检测,并且 foo 也不会。

当然,duck类型(即猜测)并不可靠,因此您可以通过提供 __emulates__ 类属性:

class SetLike(object):
    __emulates__ = set

    def __init__(self):
        self.data = set()
    def append(self, item):
        self.data.add(item)
    def remove(self, item):
        self.data.remove(item)
    def __iter__(self):
        return iter(self.data)

这个类看起来像列表,因为 append ,但是 __emulates__ 强制设置为。 remove 是集合接口的一部分,将被检测。

但是这个类还不能很好地工作:需要一点胶水来调整它以供SQLAlchemy使用。ORM需要知道在集合成员上附加、删除和迭代要使用哪些方法。使用类似类型时 listset 适当的方法是众所周知的,并在出现时自动使用。此类集不提供预期的 add 方法,因此我们必须通过修饰符为ORM提供显式映射。

通过装饰器注释自定义集合

decorator可用于标记ORM管理集合所需的各个方法。当您的类不完全符合其容器类型的常规接口时,或者当您希望使用其他方法来完成该任务时,可以使用它们。

from sqlalchemy.orm.collections import collection

class SetLike(object):
    __emulates__ = set

    def __init__(self):
        self.data = set()

    @collection.appender
    def append(self, item):
        self.data.add(item)

    def remove(self, item):
        self.data.remove(item)

    def __iter__(self):
        return iter(self.data)

这就是完成这个例子所需要的全部内容。SQLAlchemy将通过 append 方法。 remove__iter__ 是集合的默认方法,将用于移除和迭代。也可以更改默认方法:

from sqlalchemy.orm.collections import collection

class MyList(list):
    @collection.remover
    def zark(self, item):
        # do something special...

    @collection.iterator
    def hey_use_this_instead_for_iteration(self):
        # ...

根本不需要列出或设置成这样。集合类可以是任何形状,只要它们具有标记为供SQLAlchemy使用的追加、删除和迭代接口。将使用映射的实体作为单个参数调用append和remove方法,并且不使用参数调用迭代器方法,并且必须返回迭代器。

Object NameDescription

collection

实体集合类的修饰符。

class sqlalchemy.orm.collections.collection

这个 MappedCollection 类可以用作自定义类型的基类,也可以用作快速添加 dict 对其他类的集合支持。它使用键控函数委托给 __setitem____delitem__

from sqlalchemy.util import OrderedDict
from sqlalchemy.orm.collections import MappedCollection

class NodeMap(OrderedDict, MappedCollection):
    """Holds 'Node' objects, keyed by the 'name' attribute with insert order maintained."""

    def __init__(self, *args, **kw):
        MappedCollection.__init__(self, keyfunc=lambda node: node.name)
        OrderedDict.__init__(self, *args, **kw)

子类化时 MappedCollection ,的用户定义版本 __setitem__()__delitem__() 应该用 collection.internally_instrumented()if 它们调用相同的方法 MappedCollection . 这是因为方法 MappedCollection 已检测到-从已检测到的呼叫中调用它们可能导致事件重复或不适当地触发,在极少数情况下导致内部国家腐败:

from sqlalchemy.orm.collections import MappedCollection,\
                                    collection

class MyMappedCollection(MappedCollection):
    """Use @internally_instrumented when your methods
    call down to already-instrumented methods.

    """

    @collection.internally_instrumented
    def __setitem__(self, key, value, _sa_initiator=None):
        # do something with key, value
        super(MyMappedCollection, self).__setitem__(key, value, _sa_initiator)

    @collection.internally_instrumented
    def __delitem__(self, key, _sa_initiator=None):
        # do something with key
        super(MyMappedCollection, self).__delitem__(key, _sa_initiator)

ORM了解 dict 接口与列表和集合类似,如果您选择子类,将自动检测所有类似dict的方法 dict 或者在duck类型的类中提供类似dict的收集行为。但是,您必须修饰appender和remover方法,因为在基本字典接口中没有可供SQLAlchemy默认使用的兼容方法。迭代将经历 itervalues() 除非另有装饰。

注解

由于0.7.6版之前的mappedCollection中存在错误,通常需要在自定义子类 MappedCollection 其中使用 collection.internally_instrumented() 可用于:

from sqlalchemy.orm.collections import _instrument_class, MappedCollection
_instrument_class(MappedCollection)

这将确保 MappedCollection 已用自定义正确初始化 __setitem__()__delitem__() 方法。

Object NameDescription

MappedCollection

基于字典的基本集合类。

class sqlalchemy.orm.collections.MappedCollection(keyfunc)

许多自定义类型和现有库类可以用作实体集合类型,而无需进一步的ADO。但是,需要注意的是,检测过程将修改类型,并自动在方法周围添加修饰符。

装饰是轻量级的,在关系之外没有任何操作,但在其他地方触发时,它们确实增加了不必要的开销。当使用库类作为集合时,使用“琐碎的子类”技巧将修饰限制为仅在关系中使用是一种很好的做法。例如:

class MyAwesomeList(some.great.library.AwesomeList):
    pass

# ... relationship(..., collection_class=MyAwesomeList)

ORM将此方法用于内置项,当 listsetdict 直接使用。

收集内部

各种内部方法。

Object NameDescription

bulk_replace(values, existing_adapter, new_adapter[, initiator])

加载一个新的集合,根据以前类似的成员身份触发事件。

collection

实体集合类的修饰符。

collection_adapter

拿来 CollectionAdapter 收藏。

CollectionAdapter

ORM和任意Python集合之间的桥梁。

InstrumentedDict

内置dict的检测版本。

InstrumentedList

内置列表的检测版本。

InstrumentedSet

内置集的检测版本。

prepare_instrumentation(factory)

准备一个可调用文件,以备将来用作集合类工厂。

function sqlalchemy.orm.collections.bulk_replace(values, existing_adapter, new_adapter, initiator=None)

加载一个新的集合,根据以前类似的成员身份触发事件。

在中附加实例 valuesnew_adapter . 将为不在中的任何实例激发事件 existing_adapter . 中的任何实例 existing_adapter 不存在于 values 将删除对其激发的事件。

参数
class sqlalchemy.orm.collections.collection

实体集合类的修饰符。

装饰师分为两组:注释和拦截配方。

注释修饰符(appender、remover、iterator、converter、internal-instrumented)指示方法的用途,不带参数。它们不是用帕伦斯写的:

@collection.appender
def append(self, append): ...

配方装饰师都需要帕伦斯,甚至那些不带参数的:

@collection.adds('entity')
def insert(self, position, entity): ...

@collection.removes_return()
def popitem(self): ...
sqlalchemy.orm.collections.collection_adapter = operator.attrgetter('_sa_adapter')

拿来 CollectionAdapter 收藏。

class sqlalchemy.orm.collections.CollectionAdapter(attr, owner_state, data)

ORM和任意Python集合之间的桥梁。

将基本级别的集合操作(append、remove、iterate)代理到基础python集合,并为进入或离开集合的实体发出添加/删除事件。

ORM使用 CollectionAdapter 仅用于与实体集合的交互。

class sqlalchemy.orm.collections.InstrumentedDict

内置dict的检测版本。

类签名

class sqlalchemy.orm.collections.InstrumentedDict (builtins.dict)

class sqlalchemy.orm.collections.InstrumentedList(iterable=(), /)

内置列表的检测版本。

类签名

class sqlalchemy.orm.collections.InstrumentedList (builtins.list)

class sqlalchemy.orm.collections.InstrumentedSet

内置集的检测版本。

类签名

class sqlalchemy.orm.collections.InstrumentedSet (builtins.set)

function sqlalchemy.orm.collections.prepare_instrumentation(factory)

准备一个可调用文件,以备将来用作集合类工厂。

给定一个集合类工厂(类型或无arg可调用),返回另一个工厂,该工厂在调用时将生成兼容的实例。

此函数负责将collection_class=list转换为collection_class=instructedList的运行时行为。