事件

SQLAlchemy 包括一个事件 API,它发布了广泛的钩子,用于 SQLAlchemy Core 和 ORM 的内部。

事件注册

订阅事件通过一个单一的 API 入口点发生,即 listen() 函数,或者可替代地使用 listens_for() 装饰器。这些函数接受一个目标,一个标识要拦截的事件的字符串标识符,以及一个用户定义的监听函数。这些函数的其他位置和关键字参数可能被特定类型的事件支持,这些事件可能为给定的事件函数指定备用接口,或者根据给定的目标提供关于辅助事件目标的指令。

事件的名称和相应的监听器函数的参数签名是从一个类绑定的规范方法派生出来的,该方法存在于绑定到文档中描述的标记类。例如,PoolEvents.connect() 的文档表明事件名称是 "connect",并且用户定义的监听器函数应该接收两个位置参数

from sqlalchemy.event import listen
from sqlalchemy.pool import Pool


def my_on_connect(dbapi_con, connection_record):
    print("New DBAPI connection:", dbapi_con)


listen(Pool, "connect", my_on_connect)

使用 listens_for() 装饰器进行监听看起来像

from sqlalchemy.event import listens_for
from sqlalchemy.pool import Pool


@listens_for(Pool, "connect")
def my_on_connect(dbapi_con, connection_record):
    print("New DBAPI connection:", dbapi_con)

命名参数风格

监听器函数可以接受一些参数风格的变体。以 PoolEvents.connect() 为例,此函数被记录为接收 dbapi_connectionconnection_record 参数。我们可以选择通过名称接收这些参数,通过建立一个接受 **keyword 参数的监听器函数,通过传递 named=Truelisten()listens_for()

from sqlalchemy.event import listens_for
from sqlalchemy.pool import Pool


@listens_for(Pool, "connect", named=True)
def my_on_connect(**kw):
    print("New DBAPI connection:", kw["dbapi_connection"])

当使用命名参数传递时,函数参数规范中列出的名称将用作字典中的键。

命名风格通过名称传递所有参数,而不管函数签名如何,因此可以列出特定的参数,以任何顺序排列,只要名称匹配即可

from sqlalchemy.event import listens_for
from sqlalchemy.pool import Pool


@listens_for(Pool, "connect", named=True)
def my_on_connect(dbapi_connection, **kw):
    print("New DBAPI connection:", dbapi_connection)
    print("Connection record:", kw["connection_record"])

上面,**kw 的存在告诉 listens_for() 参数应该通过名称而不是位置传递给函数。

目标

listen() 函数在目标方面非常灵活。它通常接受类、这些类的实例以及可以从中派生适当目标的相关类或对象。例如,上面提到的 "connect" 事件接受 Engine 类和对象以及 Pool 类和对象

from sqlalchemy.event import listen
from sqlalchemy.pool import Pool, QueuePool
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
import psycopg2


def connect():
    return psycopg2.connect(user="ed", host="127.0.0.1", dbname="test")


my_pool = QueuePool(connect)
my_engine = create_engine("postgresql+psycopg2://ed@localhost/test")

# associate listener with all instances of Pool
listen(Pool, "connect", my_on_connect)

# associate listener with all instances of Pool
# via the Engine class
listen(Engine, "connect", my_on_connect)

# associate listener with my_pool
listen(my_pool, "connect", my_on_connect)

# associate listener with my_engine.pool
listen(my_engine, "connect", my_on_connect)

修饰符

一些监听器允许将修饰符传递给 listen()。这些修饰符有时为监听器提供备用调用签名。例如,对于 ORM 事件,一些事件监听器可以具有返回值,该返回值修改后续处理。默认情况下,没有监听器需要返回值,但是通过传递 retval=True,可以支持此值

def validate_phone(target, value, oldvalue, initiator):
    """Strip non-numeric characters from a phone number"""

    return re.sub(r"\D", "", value)


# setup listener on UserContact.phone attribute, instructing
# it to use the return value
listen(UserContact.phone, "set", validate_phone, retval=True)

事件和多进程

SQLAlchemy 的事件钩子使用 Python 函数和对象实现,因此事件通过 Python 函数调用传播。Python 多进程遵循我们考虑操作系统多进程的相同方式,例如父进程派生子进程,因此我们可以使用相同的模型来描述 SQLAlchemy 事件系统的行为。

在父进程中注册的事件钩子将存在于从该父进程派生的新子进程中,在注册钩子之后,因为子进程在生成时以父进程中所有现有 Python 结构的副本开始。在注册钩子之前已经存在的子进程将不会接收这些新的事件钩子,因为对父进程中的 Python 结构所做的更改不会传播到子进程。

对于事件本身,这些是 Python 函数调用,它们没有任何在进程之间传播的能力。SQLAlchemy 的事件系统不实现任何进程间通信。可以实现事件钩子,这些钩子在其中使用 Python 进程间消息传递,但这需要由用户实现。

事件参考

SQLAlchemy Core 和 SQLAlchemy ORM 都具有各种各样的事件钩子

  • Core 事件 - 这些在 Core 事件 中描述,包括特定于连接池生命周期、SQL 语句执行、事务生命周期以及 Schema 创建和拆卸的事件钩子。

  • ORM 事件 - 这些在 ORM 事件 中描述,包括特定于类和属性检测、对象初始化钩子、属性更改时钩子、会话状态、刷新和提交钩子、映射器初始化、对象/结果填充以及每个实例持久性钩子的事件钩子。

API 参考

对象名称 描述

contains(target, identifier, fn)

如果给定的 target/ident/fn 设置为监听,则返回 True。

listen(target, identifier, fn, *args, **kw)

为给定的目标注册一个监听器函数。

listens_for(target, identifier, *args, **kw)

将函数装饰为给定目标 + 标识符的监听器。

remove(target, identifier, fn)

移除一个事件监听器。

function sqlalchemy.event.listen(target: Any, identifier: str, fn: Callable[[...], Any], *args: Any, **kw: Any) None

为给定的目标注册一个监听器函数。

listen() 函数是 SQLAlchemy 事件系统的主要接口的一部分,记录在 事件 中。

例如:

from sqlalchemy import event
from sqlalchemy.schema import UniqueConstraint


def unique_constraint_name(const, table):
    const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)


event.listen(
    UniqueConstraint, "after_parent_attach", unique_constraint_name
)
参数:
  • insert (bool) – 事件处理程序的默认行为是在发现时将装饰的用户定义函数附加到已注册事件监听器的内部列表中。如果用户使用 insert=True 注册函数,SQLAlchemy 将在发现时将该函数插入(前置)到内部列表中。此功能通常不被 SQLAlchemy 维护者使用或推荐,但提供它是为了确保某些用户定义的函数可以在其他函数之前运行,例如当 在 MySQL 中更改 sql_mode 时。

  • named (bool) – 当使用命名参数传递时,函数参数规范中列出的名称将用作字典中的键。请参阅 命名参数风格

  • once (bool) – 私有/内部 API 用法。已弃用。此参数将提供事件函数对于给定的目标仅运行一次。然而,它并不意味着自动注销监听器函数;即使指定了 once=True,关联任意数量的监听器而不显式删除它们也会导致内存无限增长。

  • propagate (bool) – 当使用 ORM 检测和映射事件时,propagate 关键字参数可用。有关示例,请参阅 MapperEventsMapperEvents.before_mapper_configured()

  • retval (bool) –

    此标志仅适用于特定的事件监听器,每个监听器都包含解释何时应使用它的文档。默认情况下,没有监听器需要返回值。但是,某些监听器确实支持返回值的特殊行为,并在其文档中包含 retval=True 标志对于返回值被处理是必要的。

    使用 listen.retval 的事件监听器套件包括 ConnectionEventsAttributeEvents

注意

listen() 函数不能在目标事件正在运行时同时调用。这对线程安全有影响,也意味着不能从其自身的监听器函数内部添加事件。要运行的事件列表存在于迭代期间无法更改的可变集合中。

事件注册和移除不旨在成为“高速”操作;它是一种配置操作。对于需要快速关联和取消关联事件以实现高规模的系统,请使用从单个监听器内部处理的可变结构。

另请参阅

listens_for()

remove()

function sqlalchemy.event.listens_for(target: Any, identifier: str, *args: Any, **kw: Any) Callable[[Callable[[...], Any]], Callable[[...], Any]]

将函数装饰为给定目标 + 标识符的监听器。

listens_for() 装饰器是 SQLAlchemy 事件系统的主要接口的一部分,记录在 事件 中。

此函数通常与 listen() 共享相同的关键字参数。

例如:

from sqlalchemy import event
from sqlalchemy.schema import UniqueConstraint


@event.listens_for(UniqueConstraint, "after_parent_attach")
def unique_constraint_name(const, table):
    const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)

给定函数也可以仅为事件的第一次调用调用,使用 once 参数

@event.listens_for(Mapper, "before_configure", once=True)
def on_config():
    do_config()

警告

once 参数并不意味着在首次调用监听器函数后自动注销它;监听器条目将保持与目标对象关联。即使指定了 once=True,关联任意数量的监听器而不显式删除它们也会导致内存无限增长。

另请参阅

listen() - 事件监听的一般描述

function sqlalchemy.event.remove(target: Any, identifier: str, fn: Callable[[...], Any]) None

移除一个事件监听器。

此处的参数应与发送到 listen() 的参数完全匹配;所有由此调用产生的事件注册都将通过使用相同的参数调用 remove() 来撤消。

例如:

# if a function was registered like this...
@event.listens_for(SomeMappedClass, "before_insert", propagate=True)
def my_listener_function(*arg):
    pass


# ... it's removed like this
event.remove(SomeMappedClass, "before_insert", my_listener_function)

上面,与 SomeMappedClass 关联的监听器函数也传播到 SomeMappedClass 的子类;remove() 函数将撤消所有这些操作。

注意

remove() 函数不能在目标事件正在运行时同时调用。这对线程安全有影响,也意味着不能从其自身的监听器函数内部移除事件。要运行的事件列表存在于迭代期间无法更改的可变集合中。

事件注册和移除不旨在成为“高速”操作;它是一种配置操作。对于需要快速关联和取消关联事件以实现高规模的系统,请使用从单个监听器内部处理的可变结构。

另请参阅

listen()

function sqlalchemy.event.contains(target: Any, identifier: str, fn: Callable[[...], Any]) bool

如果给定的 target/ident/fn 设置为监听,则返回 True。