事件

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=True 传递给 listen()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 多进程遵循我们对 OS 多进程的思考方式,例如父进程派生子进程,因此我们可以使用相同的模型描述 SQLAlchemy 事件系统的行为。

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

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

事件参考

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

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

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

API 参考

对象名称 描述

contains(target, identifier, fn)

如果给定的目标/标识符/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() 函数。这对线程安全性有影响,也意味着无法从监听器函数本身中添加事件。要运行的事件列表存在于一个可变集合中,该集合在迭代期间无法更改。

事件注册和删除并非旨在作为“高速度”操作;它是一个配置操作。对于需要快速关联和分离高规模事件的系统,请使用从单个监听器内部处理的可变结构。

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

如果给定的目标/标识符/fn 被设置为监听,则返回 True。