Rev 5669: (jelmer) Allow installing hooks without importing the hook point. (Jelmer in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Feb 18 20:16:51 UTC 2011


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 5669 [merge]
revision-id: pqm at pqm.ubuntu.com-20110218201649-woj0pygeukoq6z6h
parent: pqm at pqm.ubuntu.com-20110218181345-on7vdomzesup6tos
parent: jelmer at samba.org-20110218190143-zx87oqko2o0o4q1s
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2011-02-18 20:16:49 +0000
message:
  (jelmer) Allow installing hooks without importing the hook point. (Jelmer
   Vernooij)
modified:
  bzrlib/hooks.py                hooks.py-20070325015548-ix4np2q0kd8452au-1
  bzrlib/tests/test_hooks.py     test_hooks.py-20070628030849-89rtsbe5dmer5npz-1
  bzrlib/version_info_formats/format_rio.py format_rio.py-20060809202444-ike7i9ub03gb432p-2
  doc/en/user-guide/hooks.txt    hooks.txt-20070829200551-7nr6e5a1io6x78uf-1
=== modified file 'bzrlib/hooks.py'
--- a/bzrlib/hooks.py	2011-02-18 17:18:56 +0000
+++ b/bzrlib/hooks.py	2011-02-18 17:44:53 +0000
@@ -115,9 +115,39 @@
     FOO hook is triggered.
     """
 
-    def __init__(self):
+    def __init__(self, module=None, member_name=None):
+        """Create a new hooks dictionary.
+
+        :param module: The module from which this hooks dictionary should be loaded
+            (used for lazy hooks)
+        :param member_name: Name under which this hooks dictionary should be loaded.
+            (used for lazy hooks)
+        """
         dict.__init__(self)
         self._callable_names = {}
+        self._module = module
+        self._member_name = member_name
+
+    def add_hook(self, name, doc, introduced, deprecated=None):
+        """Add a hook point to this dictionary.
+
+        :param name: The name of the hook, for clients to use when registering.
+        :param doc: The docs for the hook.
+        :param introduced: When the hook was introduced (e.g. (0, 15)).
+        :param deprecated: When the hook was deprecated, None for
+            not-deprecated.
+        """
+        if name in self:
+            raise errors.DuplicateKey(name)
+        if self._module:
+            callbacks = _lazy_hooks.setdefault(
+                (self._module, self._member_name, name), [])
+        else:
+            callbacks = None
+        hookpoint = HookPoint(name=name, doc=doc, introduced=introduced,
+                              deprecated=deprecated,
+                              callbacks=callbacks)
+        self[name] = hookpoint
 
     def create_hook(self, hook):
         """Create a hook which can have callbacks registered for it.
@@ -231,7 +261,7 @@
         should describe the recommended replacement hook to register for.
     """
 
-    def __init__(self, name, doc, introduced, deprecated):
+    def __init__(self, name, doc, introduced, deprecated=None, callbacks=None):
         """Create a HookPoint.
 
         :param name: The name of the hook, for clients to use when registering.
@@ -244,8 +274,10 @@
         self.__doc__ = doc
         self.introduced = introduced
         self.deprecated = deprecated
-        self._callbacks = []
-        self._callback_names = {}
+        if callbacks is None:
+            self._callbacks = []
+        else:
+            self._callbacks = callbacks
 
     def docs(self):
         """Generate the documentation for this HookPoint.
@@ -271,8 +303,7 @@
         return '\n'.join(strings)
 
     def __eq__(self, other):
-        return (type(other) == type(self) and 
-            other.__dict__ == self.__dict__)
+        return (type(other) == type(self) and other.__dict__ == self.__dict__)
 
     def hook_lazy(self, callback_module, callback_member, callback_label):
         """Lazily register a callback to be called when this HookPoint fires.
@@ -285,9 +316,7 @@
         """
         obj_getter = registry._LazyObjectGetter(callback_module,
             callback_member)
-        self._callbacks.append(obj_getter)
-        if callback_label is not None:
-            self._callback_names[obj_getter] = callback_label
+        self._callbacks.append((obj_getter, callback_label))
 
     def hook(self, callback, callback_label):
         """Register a callback to be called when this HookPoint fires.
@@ -297,12 +326,10 @@
             processing.
         """
         obj_getter = registry._ObjectGetter(callback)
-        self._callbacks.append(obj_getter)
-        if callback_label is not None:
-            self._callback_names[obj_getter] = callback_label
+        self._callbacks.append((obj_getter, callback_label))
 
     def __iter__(self):
-        return (callback.get_obj() for callback in self._callbacks)
+        return (callback.get_obj() for callback, name in self._callbacks)
 
     def __len__(self):
         return len(self._callbacks)
@@ -313,10 +340,10 @@
         strings.append(self.name)
         strings.append("), callbacks=[")
         callbacks = self._callbacks
-        for callback in callbacks:
+        for (callback, callback_name) in callbacks:
             strings.append(repr(callback.get_obj()))
             strings.append("(")
-            strings.append(self._callback_names[callback])
+            strings.append(callback_name)
             strings.append("),")
         if len(callbacks) == 1:
             strings[-1] = ")"
@@ -380,4 +407,5 @@
         running.
     """
     key = (hookpoints_module, hookpoints_name, hook_name)
-    _lazy_hooks.setdefault(key, []).append((a_callable, name))
+    obj_getter = registry._ObjectGetter(a_callable)
+    _lazy_hooks.setdefault(key, []).append((obj_getter, name))

=== modified file 'bzrlib/tests/test_hooks.py'
--- a/bzrlib/tests/test_hooks.py	2011-01-20 21:05:23 +0000
+++ b/bzrlib/tests/test_hooks.py	2011-01-23 00:24:17 +0000
@@ -19,14 +19,17 @@
 from bzrlib import (
     branch,
     errors,
+    pyutils,
     tests,
     )
 from bzrlib.hooks import (
     HookPoint,
     Hooks,
+    install_lazy_named_hook,
     known_hooks,
     known_hooks_key_to_object,
     known_hooks_key_to_parent_and_attribute,
+    _lazy_hooks,
     )
 from bzrlib.symbol_versioning import (
     deprecated_in,
@@ -112,6 +115,22 @@
         hooks.install_named_hook('set_rh', None, "demo")
         self.assertEqual("demo", hooks.get_hook_name(None))
 
+    hooks = Hooks("bzrlib.tests.test_hooks", "TestHooks.hooks")
+
+    def test_install_lazy_named_hook(self):
+        # When the hook points are not yet registered the hook is
+        # added to the _lazy_hooks dictionary in bzrlib.hooks.
+        self.hooks.add_hook('set_rh', "doc", (0, 15))
+        set_rh = lambda: None
+        install_lazy_named_hook('bzrlib.tests.test_hooks',
+            'TestHooks.hooks', 'set_rh', set_rh, "demo")
+        set_rh_lazy_hooks = _lazy_hooks[
+            ('bzrlib.tests.test_hooks', 'TestHooks.hooks', 'set_rh')]
+        self.assertEquals(1, len(set_rh_lazy_hooks))
+        self.assertEquals(set_rh, set_rh_lazy_hooks[0][0].get_obj())
+        self.assertEquals("demo", set_rh_lazy_hooks[0][1])
+        self.assertEqual(list(TestHooks.hooks['set_rh']), [set_rh])
+
     set_rh = lambda: None
 
     def test_install_named_hook_lazy(self):
@@ -131,6 +150,17 @@
             'set_rh', 'bzrlib.tests.test_hooks', 'TestHooks.set_rh',
             "demo")
 
+    def test_valid_lazy_hooks(self):
+        # Make sure that all the registered lazy hooks are referring to existing
+        # hook points which allow lazy registration.
+        for key, callbacks in _lazy_hooks.iteritems():
+            (module_name, member_name, hook_name) = key
+            obj = pyutils.get_named_object(module_name, member_name)
+            self.assertEquals(obj._module, module_name)
+            self.assertEquals(obj._member_name, member_name)
+            self.assertTrue(hook_name in obj)
+            self.assertIs(callbacks, obj[hook_name]._callbacks)
+
 
 class TestHook(tests.TestCase):
 

=== modified file 'bzrlib/version_info_formats/format_rio.py'
--- a/bzrlib/version_info_formats/format_rio.py	2010-02-17 17:11:16 +0000
+++ b/bzrlib/version_info_formats/format_rio.py	2011-02-18 19:01:43 +0000
@@ -84,10 +84,12 @@
     """Hooks for rio-formatted version-info output."""
 
     def __init__(self):
-        super(RioVersionInfoBuilderHooks, self).__init__()
-        self.create_hook(hooks.HookPoint('revision',
+        super(RioVersionInfoBuilderHooks, self).__init__(
+            "bzrlib.version_info_formats.format_rio", "RioVersionInfoBuilder.hooks")
+        self.add_hook('revision',
             "Invoked when adding information about a revision to the"
             " RIO stanza that is printed. revision is called with a"
-            " revision object and a RIO stanza.", (1, 15), None))
+            " revision object and a RIO stanza.", (1, 15), None)
+
 
 RioVersionInfoBuilder.hooks = RioVersionInfoBuilderHooks()

=== modified file 'doc/en/user-guide/hooks.txt'
--- a/doc/en/user-guide/hooks.txt	2010-10-08 10:50:51 +0000
+++ b/doc/en/user-guide/hooks.txt	2011-02-18 18:19:50 +0000
@@ -54,6 +54,20 @@
 itself.  The third argument is a name ``'My post_push hook'``, which can be
 used in progress messages and error messages.
 
+To reduce the start-up time of Bazaar it is also possible to "lazily" install hooks,
+using the ``bzrlib.hooks.install_lazy_named_hook`` function. This removes the need
+to load the module that contains the hook point just to install the hook. Here's lazy
+version of the example above::
+
+    from bzrlib import hooks
+
+    def post_push_hook(push_result):
+        print "The new revno is %d" % push_result.new_revno
+
+
+    hooks.install_lazy_named_hook('bzrlib.branch', 'Branch.hooks',
+        'post_push', post_push_hook, 'My post_push hook')
+
 Debugging hooks
 ---------------
 




More information about the bazaar-commits mailing list