]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add tests for InstanceEvents.init, InstanceEvents.init_failure
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 14 Aug 2015 19:34:01 +0000 (15:34 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 14 Aug 2015 19:34:01 +0000 (15:34 -0400)
- ensure that kwargs can be modified in-place within InstanceEvents.init
and that these take effect for the __init__ method.
- improve documentation for these and related events, including
that kwargs can be modified in-place.

lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/state.py
test/orm/test_events.py

index 801701be92234679d17dab37e004893c1efbe4d6..224c9c4dd3dd7956d70f3cd9a43af037e35b1b9f 100644 (file)
@@ -216,14 +216,41 @@ class InstanceEvents(event.Events):
     def first_init(self, manager, cls):
         """Called when the first instance of a particular mapping is called.
 
+        This event is called when the ``__init__`` method of a class
+        is called the first time for that particular class.    The event
+        invokes before ``__init__`` actually proceeds as well as before
+        the :meth:`.InstanceEvents.init` event is invoked.
+
         """
 
     def init(self, target, args, kwargs):
         """Receive an instance when its constructor is called.
 
         This method is only called during a userland construction of
-        an object.  It is not called when an object is loaded from the
-        database.
+        an object, in conjunction with the object's constructor, e.g.
+        its ``__init__`` method.  It is not called when an object is
+        loaded from the database; see the :meth:`.InstanceEvents.load`
+        event in order to intercept a database load.
+
+        The event is called before the actual ``__init__`` constructor
+        of the object is called.  The ``kwargs`` dictionary may be
+        modified in-place in order to affect what is passed to
+        ``__init__``.
+
+        :param target: the mapped instance.  If
+         the event is configured with ``raw=True``, this will
+         instead be the :class:`.InstanceState` state-management
+         object associated with the instance.
+        :param args: positional arguments passed to the ``__init__`` method.
+         This is passed as a tuple and is currently immutable.
+        :param kwargs: keyword arguments passed to the ``__init__`` method.
+         This structure *can* be altered in place.
+
+        .. seealso::
+
+            :meth:`.InstanceEvents.init_failure`
+
+            :meth:`.InstanceEvents.load`
 
         """
 
@@ -232,8 +259,31 @@ class InstanceEvents(event.Events):
         and raised an exception.
 
         This method is only called during a userland construction of
-        an object.  It is not called when an object is loaded from the
-        database.
+        an object, in conjunction with the object's constructor, e.g.
+        its ``__init__`` method. It is not called when an object is loaded
+        from the database.
+
+        The event is invoked after an exception raised by the ``__init__``
+        method is caught.  After the event
+        is invoked, the original exception is re-raised outwards, so that
+        the construction of the object still raises an exception.   The
+        actual exception and stack trace raised should be present in
+        ``sys.exc_info()``.
+
+        :param target: the mapped instance.  If
+         the event is configured with ``raw=True``, this will
+         instead be the :class:`.InstanceState` state-management
+         object associated with the instance.
+        :param args: positional arguments that were passed to the ``__init__``
+         method.
+        :param kwargs: keyword arguments that were passed to the ``__init__``
+         method.
+
+        .. seealso::
+
+            :meth:`.InstanceEvents.init`
+
+            :meth:`.InstanceEvents.load`
 
         """
 
@@ -260,12 +310,21 @@ class InstanceEvents(event.Events):
          ``None`` if the load does not correspond to a :class:`.Query`,
          such as during :meth:`.Session.merge`.
 
+        .. seealso::
+
+            :meth:`.InstanceEvents.init`
+
+            :meth:`.InstanceEvents.refresh`
+
         """
 
     def refresh(self, target, context, attrs):
         """Receive an object instance after one or more attributes have
         been refreshed from a query.
 
+        Contrast this to the :meth:`.InstanceEvents.load` method, which
+        is invoked when the object is first loaded from a query.
+
         :param target: the mapped instance.  If
          the event is configured with ``raw=True``, this will
          instead be the :class:`.InstanceState` state-management
@@ -276,6 +335,10 @@ class InstanceEvents(event.Events):
          were populated, or None if all column-mapped, non-deferred
          attributes were populated.
 
+        .. seealso::
+
+            :meth:`.InstanceEvents.load`
+
         """
 
     def refresh_flush(self, target, flush_context, attrs):
index 61d1ad29db28f8cfca3165ebb12b1ae623e9e50d..3cbeed0b4eb9714bf288183b6ad80042a6e2e409 100644 (file)
@@ -294,7 +294,7 @@ class InstanceState(interfaces.InspectionAttr):
             return {}
 
     def _initialize_instance(*mixed, **kwargs):
-        self, instance, args = mixed[0], mixed[1], mixed[2:]
+        self, instance, args = mixed[0], mixed[1], mixed[2:]  # noqa
         manager = self.manager
 
         manager.dispatch.init(self, args, kwargs)
index ae7ba98c118cfde3ea1c96e8107c0114fce34f29..b9fafb105c49afd0fe81e474a00e650838110999 100644 (file)
@@ -111,6 +111,43 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
             event.listen(mapper, meth, evt(meth), **kw)
         return canary
 
+    def test_init_allow_kw_modify(self):
+        User, users = self.classes.User, self.tables.users
+        mapper(User, users)
+
+        @event.listens_for(User, 'init')
+        def add_name(obj, args, kwargs):
+            kwargs['name'] = 'ed'
+
+        u1 = User()
+        eq_(u1.name, 'ed')
+
+    def test_init_failure_hook(self):
+        users = self.tables.users
+
+        class Thing(object):
+            def __init__(self, **kw):
+                if kw.get('fail'):
+                    raise Exception("failure")
+
+        mapper(Thing, users)
+
+        canary = Mock()
+        event.listen(Thing, 'init_failure', canary)
+
+        Thing()
+        eq_(canary.mock_calls, [])
+
+        assert_raises_message(
+            Exception,
+            "failure",
+            Thing, fail=True
+        )
+        eq_(
+            canary.mock_calls,
+            [call(ANY, (), {'fail': True})]
+        )
+
     def test_listen_doesnt_force_compile(self):
         User, users = self.classes.User, self.tables.users
         m = mapper(User, users, properties={