1# event/api.py
2# Copyright (C) 2005-2021 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: http://www.opensource.org/licenses/mit-license.php
7
8"""Public API functions for the event system.
9
10"""
11from __future__ import absolute_import
12
13from .base import _registrars
14from .registry import _EventKey
15from .. import exc
16from .. import util
17
18
19CANCEL = util.symbol("CANCEL")
20NO_RETVAL = util.symbol("NO_RETVAL")
21
22
23def _event_key(target, identifier, fn):
24 for evt_cls in _registrars[identifier]:
25 tgt = evt_cls._accept_with(target)
26 if tgt is not None:
27 return _EventKey(target, identifier, fn, tgt)
28 else:
29 raise exc.InvalidRequestError(
30 "No such event '%s' for target '%s'" % (identifier, target)
31 )
32
33
34def listen(target, identifier, fn, *args, **kw):
35 """Register a listener function for the given target.
36
37 The :func:`.listen` function is part of the primary interface for the
38 SQLAlchemy event system, documented at :ref:`event_toplevel`.
39
40 e.g.::
41
42 from sqlalchemy import event
43 from sqlalchemy.schema import UniqueConstraint
44
45 def unique_constraint_name(const, table):
46 const.name = "uq_%s_%s" % (
47 table.name,
48 list(const.columns)[0].name
49 )
50 event.listen(
51 UniqueConstraint,
52 "after_parent_attach",
53 unique_constraint_name)
54
55
56 A given function can also be invoked for only the first invocation
57 of the event using the ``once`` argument::
58
59 def on_config():
60 do_config()
61
62 event.listen(Mapper, "before_configure", on_config, once=True)
63
64 .. versionadded:: 0.9.4 Added ``once=True`` to :func:`.event.listen`
65 and :func:`.event.listens_for`.
66
67 .. warning:: The ``once`` argument does not imply automatic de-registration
68 of the listener function after it has been invoked a first time; a
69 listener entry will remain associated with the target object.
70 Associating an arbitrarily high number of listeners without explicitly
71 removing them will cause memory to grow unbounded even if ``once=True``
72 is specified.
73
74 .. note::
75
76 The :func:`.listen` function cannot be called at the same time
77 that the target event is being run. This has implications
78 for thread safety, and also means an event cannot be added
79 from inside the listener function for itself. The list of
80 events to be run are present inside of a mutable collection
81 that can't be changed during iteration.
82
83 Event registration and removal is not intended to be a "high
84 velocity" operation; it is a configurational operation. For
85 systems that need to quickly associate and deassociate with
86 events at high scale, use a mutable structure that is handled
87 from inside of a single listener.
88
89 .. versionchanged:: 1.0.0 - a ``collections.deque()`` object is now
90 used as the container for the list of events, which explicitly
91 disallows collection mutation while the collection is being
92 iterated.
93
94 .. seealso::
95
96 :func:`.listens_for`
97
98 :func:`.remove`
99
100 """
101
102 _event_key(target, identifier, fn).listen(*args, **kw)
103
104
105def listens_for(target, identifier, *args, **kw):
106 """Decorate a function as a listener for the given target + identifier.
107
108 The :func:`.listens_for` decorator is part of the primary interface for the
109 SQLAlchemy event system, documented at :ref:`event_toplevel`.
110
111 e.g.::
112
113 from sqlalchemy import event
114 from sqlalchemy.schema import UniqueConstraint
115
116 @event.listens_for(UniqueConstraint, "after_parent_attach")
117 def unique_constraint_name(const, table):
118 const.name = "uq_%s_%s" % (
119 table.name,
120 list(const.columns)[0].name
121 )
122
123 A given function can also be invoked for only the first invocation
124 of the event using the ``once`` argument::
125
126 @event.listens_for(Mapper, "before_configure", once=True)
127 def on_config():
128 do_config()
129
130
131 .. versionadded:: 0.9.4 Added ``once=True`` to :func:`.event.listen`
132 and :func:`.event.listens_for`.
133
134 .. warning:: The ``once`` argument does not imply automatic de-registration
135 of the listener function after it has been invoked a first time; a
136 listener entry will remain associated with the target object.
137 Associating an arbitrarily high number of listeners without explicitly
138 removing them will cause memory to grow unbounded even if ``once=True``
139 is specified.
140
141 .. seealso::
142
143 :func:`.listen` - general description of event listening
144
145 """
146
147 def decorate(fn):
148 listen(target, identifier, fn, *args, **kw)
149 return fn
150
151 return decorate
152
153
154def remove(target, identifier, fn):
155 """Remove an event listener.
156
157 The arguments here should match exactly those which were sent to
158 :func:`.listen`; all the event registration which proceeded as a result
159 of this call will be reverted by calling :func:`.remove` with the same
160 arguments.
161
162 e.g.::
163
164 # if a function was registered like this...
165 @event.listens_for(SomeMappedClass, "before_insert", propagate=True)
166 def my_listener_function(*arg):
167 pass
168
169 # ... it's removed like this
170 event.remove(SomeMappedClass, "before_insert", my_listener_function)
171
172 Above, the listener function associated with ``SomeMappedClass`` was also
173 propagated to subclasses of ``SomeMappedClass``; the :func:`.remove`
174 function will revert all of these operations.
175
176 .. versionadded:: 0.9.0
177
178 .. note::
179
180 The :func:`.remove` function cannot be called at the same time
181 that the target event is being run. This has implications
182 for thread safety, and also means an event cannot be removed
183 from inside the listener function for itself. The list of
184 events to be run are present inside of a mutable collection
185 that can't be changed during iteration.
186
187 Event registration and removal is not intended to be a "high
188 velocity" operation; it is a configurational operation. For
189 systems that need to quickly associate and deassociate with
190 events at high scale, use a mutable structure that is handled
191 from inside of a single listener.
192
193 .. versionchanged:: 1.0.0 - a ``collections.deque()`` object is now
194 used as the container for the list of events, which explicitly
195 disallows collection mutation while the collection is being
196 iterated.
197
198 .. seealso::
199
200 :func:`.listen`
201
202 """
203 _event_key(target, identifier, fn).remove()
204
205
206def contains(target, identifier, fn):
207 """Return True if the given target/ident/fn is set up to listen.
208
209 .. versionadded:: 0.9.0
210
211 """
212
213 return _event_key(target, identifier, fn).contains()