summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-03-11 12:27:10 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-03-11 12:27:10 -0400
commit009df6a3d041e517cc9efa74d3c87184357a5006 (patch)
tree1f76d29b586de7052c1baaf286407d4245795788
parent043dc4a2c1eef11abc04919d0cc093f5424028e5 (diff)
downloadsqlalchemy-009df6a3d041e517cc9efa74d3c87184357a5006.tar.gz
- 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.
-rw-r--r--doc/build/changelog/changelog_09.rst9
-rw-r--r--lib/sqlalchemy/engine/strategies.py3
-rw-r--r--lib/sqlalchemy/event/api.py24
-rw-r--r--lib/sqlalchemy/event/registry.py9
-rw-r--r--lib/sqlalchemy/util/langhelpers.py2
-rw-r--r--test/base/test_events.py25
6 files changed, 66 insertions, 6 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 0e5b43b2d..58ac33559 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -15,7 +15,14 @@
: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
diff --git a/lib/sqlalchemy/engine/strategies.py b/lib/sqlalchemy/engine/strategies.py
index f6c064033..a8a63bb3d 100644
--- a/lib/sqlalchemy/engine/strategies.py
+++ b/lib/sqlalchemy/engine/strategies.py
@@ -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
diff --git a/lib/sqlalchemy/event/api.py b/lib/sqlalchemy/event/api.py
index 20e74d90e..b27ce7993 100644
--- a/lib/sqlalchemy/event/api.py
+++ b/lib/sqlalchemy/event/api.py
@@ -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)
diff --git a/lib/sqlalchemy/event/registry.py b/lib/sqlalchemy/event/registry.py
index 7710ff2d2..6f3eb3e85 100644
--- a/lib/sqlalchemy/event/registry.py
+++ b/lib/sqlalchemy/event/registry.py
@@ -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)
+
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index 7b97f8827..8a1164e77 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -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')
diff --git a/test/base/test_events.py b/test/base/test_events.py
index 8d4728a9f..4ae89fe17 100644
--- a/test/base/test_events.py
+++ b/test/base/test_events.py
@@ -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()