From b7169f66d7cac26713f0784713be916905f320de Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 11 Mar 2014 12:39:00 -0400 Subject: [PATCH] - 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. --- doc/build/changelog/changelog_09.rst | 8 +++++ lib/sqlalchemy/orm/events.py | 48 ++++++++++++++++++++++++++-- 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 58ac33559b..fc39ea347a 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -14,6 +14,14 @@ .. changelog:: :version: 0.9.4 + .. 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 diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 52dcca2328..b684752308 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 edafc3a8b2..5260a724a7 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 -- 2.47.3