summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-03-11 12:39:00 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-03-11 12:39:00 -0400
commitb7169f66d7cac26713f0784713be916905f320de (patch)
tree361d671d4eabf20e0b95f682de51c1d49f259ee2
parent009df6a3d041e517cc9efa74d3c87184357a5006 (diff)
downloadsqlalchemy-b7169f66d7cac26713f0784713be916905f320de.tar.gz
- A warning is emitted if the :meth:`.MapperEvents.before_configured`
or :meth:`.MapperEvents.after_configured` events are applied to a specific mapper or mapped class, as the events are only invoked for the :class:`.Mapper` target at the general level.
-rw-r--r--doc/build/changelog/changelog_09.rst8
-rw-r--r--lib/sqlalchemy/orm/events.py48
-rw-r--r--test/orm/test_events.py36
3 files changed, 90 insertions, 2 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 58ac33559..fc39ea347 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -17,6 +17,14 @@
.. change::
:tags: feature, orm
+ A warning is emitted if the :meth:`.MapperEvents.before_configured`
+ or :meth:`.MapperEvents.after_configured` events are applied to a
+ specific mapper or mapped class, as the events are only invoked
+ for the :class:`.Mapper` target at the general level.
+
+ .. change::
+ :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.
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 52dcca232..b68475230 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -508,6 +508,13 @@ class MapperEvents(event.Events):
target, identifier, fn = \
event_key.dispatch_target, event_key.identifier, event_key.fn
+ if identifier in ("before_configured", "after_configured") and \
+ target is not mapperlib.Mapper:
+ util.warn(
+ "'before_configured' and 'after_configured' ORM events "
+ "only invoke with the mapper() function or Mapper class "
+ "as the target.")
+
if not raw or not retval:
if not raw:
meth = getattr(cls, identifier)
@@ -590,11 +597,30 @@ class MapperEvents(event.Events):
note is usually called automatically as mappings are first
used.
+ This event can **only** be applied to the :class:`.Mapper` class
+ or :func:`.mapper` function, and not to individual mappings or
+ mapped classes. It is only invoked for all mappings as a whole::
+
+ from sqlalchemy.orm import mapper
+
+ @event.listens_for(mapper, "before_configured")
+ def go():
+ # ...
+
Theoretically this event is called once per
application, but is actually called any time new mappers
are to be affected by a :func:`.orm.configure_mappers`
call. If new mappings are constructed after existing ones have
- already been used, this event can be called again.
+ already been used, this event can be called again. To ensure
+ that a particular event is only called once and no further, the
+ ``once=True`` argument (new in 0.9.4) can be applied::
+
+ from sqlalchemy.orm import mapper
+
+ @event.listens_for(mapper, "before_configured", once=True)
+ def go():
+ # ...
+
.. versionadded:: 0.9.3
@@ -607,11 +633,29 @@ class MapperEvents(event.Events):
note is usually called automatically as mappings are first
used.
+ This event can **only** be applied to the :class:`.Mapper` class
+ or :func:`.mapper` function, and not to individual mappings or
+ mapped classes. It is only invoked for all mappings as a whole::
+
+ from sqlalchemy.orm import mapper
+
+ @event.listens_for(mapper, "after_configured")
+ def go():
+ # ...
+
Theoretically this event is called once per
application, but is actually called any time new mappers
have been affected by a :func:`.orm.configure_mappers`
call. If new mappings are constructed after existing ones have
- already been used, this event can be called again.
+ already been used, this event can be called again. To ensure
+ that a particular event is only called once and no further, the
+ ``once=True`` argument (new in 0.9.4) can be applied::
+
+ from sqlalchemy.orm import mapper
+
+ @event.listens_for(mapper, "after_configured", once=True)
+ def go():
+ # ...
"""
diff --git a/test/orm/test_events.py b/test/orm/test_events.py
index edafc3a8b..5260a724a 100644
--- a/test/orm/test_events.py
+++ b/test/orm/test_events.py
@@ -283,6 +283,42 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
eq_(canary1, ['before_update', 'after_update'])
eq_(canary2, [])
+ def test_before_after_configured_warn_on_non_mapper(self):
+ User, users = self.classes.User, self.tables.users
+
+ m1 = Mock()
+
+ mapper(User, users)
+ assert_raises_message(
+ sa.exc.SAWarning,
+ "before_configured' and 'after_configured' ORM events only "
+ "invoke with the mapper\(\) function or Mapper class as the target.",
+ event.listen, User, 'before_configured', m1
+ )
+
+ assert_raises_message(
+ sa.exc.SAWarning,
+ "before_configured' and 'after_configured' ORM events only "
+ "invoke with the mapper\(\) function or Mapper class as the target.",
+ event.listen, User, 'after_configured', m1
+ )
+
+ def test_before_after_configured(self):
+ User, users = self.classes.User, self.tables.users
+
+ m1 = Mock()
+ m2 = Mock()
+
+ mapper(User, users)
+
+ event.listen(mapper, "before_configured", m1)
+ event.listen(mapper, "after_configured", m2)
+
+ s = Session()
+ s.query(User)
+
+ eq_(m1.mock_calls, [call()])
+ eq_(m2.mock_calls, [call()])
def test_retval(self):
User, users = self.classes.User, self.tables.users