diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-08-14 15:34:01 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-08-14 15:34:01 -0400 |
commit | b00b430e87512d721ad30c81fdcb35a5253dfc0a (patch) | |
tree | af192e8081635834bb9120256f9655ab6c8f9914 | |
parent | 39517d10466eecb1411b2b3bd6569ae22a9ded0e (diff) | |
download | sqlalchemy-b00b430e87512d721ad30c81fdcb35a5253dfc0a.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.
-rw-r--r-- | lib/sqlalchemy/orm/events.py | 71 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/state.py | 2 | ||||
-rw-r--r-- | test/orm/test_events.py | 37 |
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={ |