diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-03-11 12:39:00 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-03-11 12:39:00 -0400 |
commit | b7169f66d7cac26713f0784713be916905f320de (patch) | |
tree | 361d671d4eabf20e0b95f682de51c1d49f259ee2 | |
parent | 009df6a3d041e517cc9efa74d3c87184357a5006 (diff) | |
download | sqlalchemy-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.rst | 8 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/events.py | 48 | ||||
-rw-r--r-- | test/orm/test_events.py | 36 |
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 |