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