1# event/api.py
2# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8"""Public API functions for the event system.
9
10"""
11from __future__ import annotations
12
13from typing import Any
14from typing import Callable
15
16from .base import _registrars
17from .registry import _ET
18from .registry import _EventKey
19from .registry import _ListenerFnType
20from .. import exc
21from .. import util
22
23
24CANCEL = util.symbol("CANCEL")
25NO_RETVAL = util.symbol("NO_RETVAL")
26
27
28def _event_key(
29 target: _ET, identifier: str, fn: _ListenerFnType
30) -> _EventKey[_ET]:
31 for evt_cls in _registrars[identifier]:
32 tgt = evt_cls._accept_with(target, identifier)
33 if tgt is not None:
34 return _EventKey(target, identifier, fn, tgt)
35 else:
36 raise exc.InvalidRequestError(
37 "No such event '%s' for target '%s'" % (identifier, target)
38 )
39
40
41def listen(
42 target: Any, identifier: str, fn: Callable[..., Any], *args: Any, **kw: Any
43) -> None:
44 """Register a listener function for the given target.
45
46 The :func:`.listen` function is part of the primary interface for the
47 SQLAlchemy event system, documented at :ref:`event_toplevel`.
48
49 e.g.::
50
51 from sqlalchemy import event
52 from sqlalchemy.schema import UniqueConstraint
53
54
55 def unique_constraint_name(const, table):
56 const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)
57
58
59 event.listen(
60 UniqueConstraint, "after_parent_attach", unique_constraint_name
61 )
62
63 :param bool insert: The default behavior for event handlers is to append
64 the decorated user defined function to an internal list of registered
65 event listeners upon discovery. If a user registers a function with
66 ``insert=True``, SQLAlchemy will insert (prepend) the function to the
67 internal list upon discovery. This feature is not typically used or
68 recommended by the SQLAlchemy maintainers, but is provided to ensure
69 certain user defined functions can run before others, such as when
70 :ref:`Changing the sql_mode in MySQL <mysql_sql_mode>`.
71
72 :param bool named: When using named argument passing, the names listed in
73 the function argument specification will be used as keys in the
74 dictionary.
75 See :ref:`event_named_argument_styles`.
76
77 :param bool once: Private/Internal API usage. Deprecated. This parameter
78 would provide that an event function would run only once per given
79 target. It does not however imply automatic de-registration of the
80 listener function; associating an arbitrarily high number of listeners
81 without explicitly removing them will cause memory to grow unbounded even
82 if ``once=True`` is specified.
83
84 :param bool propagate: The ``propagate`` kwarg is available when working
85 with ORM instrumentation and mapping events.
86 See :class:`_ormevent.MapperEvents` and
87 :meth:`_ormevent.MapperEvents.before_mapper_configured` for examples.
88
89 :param bool retval: This flag applies only to specific event listeners,
90 each of which includes documentation explaining when it should be used.
91 By default, no listener ever requires a return value.
92 However, some listeners do support special behaviors for return values,
93 and include in their documentation that the ``retval=True`` flag is
94 necessary for a return value to be processed.
95
96 Event listener suites that make use of :paramref:`_event.listen.retval`
97 include :class:`_events.ConnectionEvents` and
98 :class:`_ormevent.AttributeEvents`.
99
100 .. note::
101
102 The :func:`.listen` function cannot be called at the same time
103 that the target event is being run. This has implications
104 for thread safety, and also means an event cannot be added
105 from inside the listener function for itself. The list of
106 events to be run are present inside of a mutable collection
107 that can't be changed during iteration.
108
109 Event registration and removal is not intended to be a "high
110 velocity" operation; it is a configurational operation. For
111 systems that need to quickly associate and deassociate with
112 events at high scale, use a mutable structure that is handled
113 from inside of a single listener.
114
115 .. seealso::
116
117 :func:`.listens_for`
118
119 :func:`.remove`
120
121 """
122
123 _event_key(target, identifier, fn).listen(*args, **kw)
124
125
126def listens_for(
127 target: Any, identifier: str, *args: Any, **kw: Any
128) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
129 """Decorate a function as a listener for the given target + identifier.
130
131 The :func:`.listens_for` decorator is part of the primary interface for the
132 SQLAlchemy event system, documented at :ref:`event_toplevel`.
133
134 This function generally shares the same kwargs as :func:`.listen`.
135
136 e.g.::
137
138 from sqlalchemy import event
139 from sqlalchemy.schema import UniqueConstraint
140
141
142 @event.listens_for(UniqueConstraint, "after_parent_attach")
143 def unique_constraint_name(const, table):
144 const.name = "uq_%s_%s" % (table.name, list(const.columns)[0].name)
145
146 A given function can also be invoked for only the first invocation
147 of the event using the ``once`` argument::
148
149 @event.listens_for(Mapper, "before_configure", once=True)
150 def on_config():
151 do_config()
152
153 .. warning:: The ``once`` argument does not imply automatic de-registration
154 of the listener function after it has been invoked a first time; a
155 listener entry will remain associated with the target object.
156 Associating an arbitrarily high number of listeners without explicitly
157 removing them will cause memory to grow unbounded even if ``once=True``
158 is specified.
159
160 .. seealso::
161
162 :func:`.listen` - general description of event listening
163
164 """
165
166 def decorate(fn: Callable[..., Any]) -> Callable[..., Any]:
167 listen(target, identifier, fn, *args, **kw)
168 return fn
169
170 return decorate
171
172
173def remove(target: Any, identifier: str, fn: Callable[..., Any]) -> None:
174 """Remove an event listener.
175
176 The arguments here should match exactly those which were sent to
177 :func:`.listen`; all the event registration which proceeded as a result
178 of this call will be reverted by calling :func:`.remove` with the same
179 arguments.
180
181 e.g.::
182
183 # if a function was registered like this...
184 @event.listens_for(SomeMappedClass, "before_insert", propagate=True)
185 def my_listener_function(*arg):
186 pass
187
188
189 # ... it's removed like this
190 event.remove(SomeMappedClass, "before_insert", my_listener_function)
191
192 Above, the listener function associated with ``SomeMappedClass`` was also
193 propagated to subclasses of ``SomeMappedClass``; the :func:`.remove`
194 function will revert all of these operations.
195
196 .. note::
197
198 The :func:`.remove` function cannot be called at the same time
199 that the target event is being run. This has implications
200 for thread safety, and also means an event cannot be removed
201 from inside the listener function for itself. The list of
202 events to be run are present inside of a mutable collection
203 that can't be changed during iteration.
204
205 Event registration and removal is not intended to be a "high
206 velocity" operation; it is a configurational operation. For
207 systems that need to quickly associate and deassociate with
208 events at high scale, use a mutable structure that is handled
209 from inside of a single listener.
210
211 .. seealso::
212
213 :func:`.listen`
214
215 """
216 _event_key(target, identifier, fn).remove()
217
218
219def contains(target: Any, identifier: str, fn: Callable[..., Any]) -> bool:
220 """Return True if the given target/ident/fn is set up to listen."""
221
222 return _event_key(target, identifier, fn).contains()