Commits

Mike Bayer committed b7169f6

- 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.

  • Participants
  • Parent commits 009df6a

Comments (0)

Files changed (3)

doc/build/changelog/changelog_09.rst

     .. 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.

lib/sqlalchemy/orm/events.py

         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)
         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
 
         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():
+                # ...
 
         """
 

test/orm/test_events.py

         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