summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-08-14 15:34:01 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-08-14 15:34:57 -0400
commit42281066ca82067e752ea0293506f3e50fada843 (patch)
tree881a0ab10af018f7a07164e3eb78598c4598016a
parent7f3548909e5d72a7a910d2629d53f7ed44625c08 (diff)
downloadsqlalchemy-42281066ca82067e752ea0293506f3e50fada843.tar.gz
- add tests for InstanceEvents.init, InstanceEvents.init_failure
- 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. (cherry picked from commit b00b430e87512d721ad30c81fdcb35a5253dfc0a)
-rw-r--r--lib/sqlalchemy/orm/events.py71
-rw-r--r--lib/sqlalchemy/orm/state.py2
-rw-r--r--test/orm/test_events.py37
3 files changed, 105 insertions, 5 deletions
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index 801701be9..224c9c4dd 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -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):
diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py
index 61d1ad29d..3cbeed0b4 100644
--- a/lib/sqlalchemy/orm/state.py
+++ b/lib/sqlalchemy/orm/state.py
@@ -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)
diff --git a/test/orm/test_events.py b/test/orm/test_events.py
index ae7ba98c1..b9fafb105 100644
--- a/test/orm/test_events.py
+++ b/test/orm/test_events.py
@@ -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={