]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Added a new keyword argument ``once=True`` to :func:`.event.listen`
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 11 Mar 2014 16:27:10 +0000 (12:27 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 11 Mar 2014 16:27:10 +0000 (12:27 -0400)
and :func:`.event.listens_for`.  This is a convenience feature which
will wrap the given listener such that it is only invoked once.

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/engine/strategies.py
lib/sqlalchemy/event/api.py
lib/sqlalchemy/event/registry.py
lib/sqlalchemy/util/langhelpers.py
test/base/test_events.py

index 0e5b43b2d1fdffd4bdf8ca21914023384456c23b..58ac33559b70aec9adedaf34aa27c9aecb1e47b6 100644 (file)
     :version: 0.9.4
 
     .. change::
-        :tags: enhancement, oracle
+        :tags: feature, orm
+
+        Added a new keyword argument ``once=True`` to :func:`.event.listen`
+        and :func:`.event.listens_for`.  This is a convenience feature which
+        will wrap the given listener such that it is only invoked once.
+
+    .. change::
+        :tags: feature, oracle
         :tickets: 2911
         :pullreq: github:74
 
index f6c064033bd8a3d51d51e08701eda14caed7409a..a8a63bb3d5ef7263bea1ff61bfd998b5d69edfa7 100644 (file)
@@ -158,13 +158,12 @@ class DefaultEngineStrategy(EngineStrategy):
                 event.listen(pool, 'first_connect', on_connect)
                 event.listen(pool, 'connect', on_connect)
 
-            @util.only_once
             def first_connect(dbapi_connection, connection_record):
                 c = base.Connection(engine, connection=dbapi_connection,
                             _has_events=False)
 
                 dialect.initialize(c)
-            event.listen(pool, 'first_connect', first_connect)
+            event.listen(pool, 'first_connect', first_connect, once=True)
 
         return engine
 
index 20e74d90e411bd0a4df4d8cd1c1abb4e2197dbd6..b27ce79937480d65b48adc39056d834e132b40f9 100644 (file)
@@ -44,6 +44,18 @@ def listen(target, identifier, fn, *args, **kw):
                 "after_parent_attach",
                 unique_constraint_name)
 
+
+    A given function can also be invoked for only the first invocation
+    of the event using the ``once`` argument::
+
+        def on_config():
+            do_config()
+
+        event.listen(Mapper, "before_configure", on_config, once=True)
+
+    .. versionadded:: 0.9.3 Added ``once=True`` to :func:`.event.listen`
+       and :func:`.event.listens_for`.
+
     """
 
     _event_key(target, identifier, fn).listen(*args, **kw)
@@ -63,6 +75,18 @@ def listens_for(target, identifier, *args, **kw):
                 table.name,
                 list(const.columns)[0].name
             )
+
+    A given function can also be invoked for only the first invocation
+    of the event using the ``once`` argument::
+
+        @event.listens_for(Mapper, "before_configure", once=True)
+        def on_config():
+            do_config()
+
+
+    .. versionadded:: 0.9.3 Added ``once=True`` to :func:`.event.listen`
+       and :func:`.event.listens_for`.
+
     """
     def decorate(fn):
         listen(target, identifier, fn, *args, **kw)
index 7710ff2d29e89d78d18f6979b40f87e5ed00b0d1..6f3eb3e85bbb4630a3c8c8b62858487230eeb546 100644 (file)
@@ -19,7 +19,7 @@ from __future__ import absolute_import
 import weakref
 import collections
 import types
-from .. import exc
+from .. import exc, util
 
 
 _key_to_collection = collections.defaultdict(dict)
@@ -173,7 +173,11 @@ class _EventKey(object):
                 )
 
     def listen(self, *args, **kw):
-        self.dispatch_target.dispatch._listen(self, *args, **kw)
+        once = kw.pop("once", False)
+        if once:
+            self.with_wrapper(util.only_once(self._listen_fn)).listen(*args, **kw)
+        else:
+            self.dispatch_target.dispatch._listen(self, *args, **kw)
 
     def remove(self):
         key = self._key
@@ -234,3 +238,4 @@ class _EventKey(object):
         _stored_in_collection(self, owner)
         list_.insert(0, self._listen_fn)
 
+
index 7b97f8827086c8cc1222915e6c7ef450659881cf..8a1164e77f13f32b774d1df265a3041d94dc40a7 100644 (file)
@@ -1202,7 +1202,7 @@ def only_once(fn):
             once_fn = once.pop()
             return once_fn(*arg, **kw)
 
-    return update_wrapper(go, fn)
+    return go
 
 
 _SQLA_RE = re.compile(r'sqlalchemy/([a-z_]+/){0,2}[a-z_]+\.py')
index 8d4728a9f4a6795154537ff4e5b8918534091cfd..4ae89fe1750ee4c54e5f4d1b3cd7c63353fb9f8f 100644 (file)
@@ -1045,6 +1045,31 @@ class RemovalTest(fixtures.TestBase):
         eq_(f1.mock.mock_calls, [call("x")])
         eq_(f2.mock.mock_calls, [call("x"), call("y")])
 
+    def test_once(self):
+        Target = self._fixture()
+
+        m1 = Mock()
+        m2 = Mock()
+        m3 = Mock()
+        m4 = Mock()
+
+        event.listen(Target, "event_one", m1)
+        event.listen(Target, "event_one", m2, once=True)
+        event.listen(Target, "event_one", m3, once=True)
+
+        t1 = Target()
+        t1.dispatch.event_one("x")
+        t1.dispatch.event_one("y")
+
+        event.listen(Target, "event_one", m4, once=True)
+        t1.dispatch.event_one("z")
+        t1.dispatch.event_one("q")
+
+        eq_(m1.mock_calls, [call("x"), call("y"), call("z"), call("q")])
+        eq_(m2.mock_calls, [call("x")])
+        eq_(m3.mock_calls, [call("x")])
+        eq_(m4.mock_calls, [call("z")])
+
     def test_propagate(self):
         Target = self._fixture()