Merge lp:~jelmer/bzr/uninstall-hook into lp:bzr

Proposed by Jelmer Vernooij
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: 5751
Proposed branch: lp:~jelmer/bzr/uninstall-hook
Merge into: lp:bzr
Prerequisite: lp:~jelmer/bzr/more-lazy-hooks
Diff against target: 130 lines (+85/-0)
3 files modified
bzrlib/hooks.py (+33/-0)
bzrlib/tests/test_hooks.py (+50/-0)
doc/en/release-notes/bzr-2.4.txt (+2/-0)
To merge this branch: bzr merge lp:~jelmer/bzr/uninstall-hook
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
John A Meinel Approve
Jonathan Lange (community) Approve
Review via email: mp+55527@code.launchpad.net

Commit message

Add Hooks.uninstall_named_hook().

Description of the change

Add a Hooks.uninstall_named_hook(), which allows uninstalling named hooks (traditional and lazy).

To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

What happens if two calls to install_named_hook use the same label?

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

On Wed, 2011-03-30 at 12:59 +0000, Andrew Bennetts wrote:
> What happens if two calls to install_named_hook use the same label?
Bad things (basically, the first one with the label will be removed).

I guess the alternative is to allow uninstalling by callable, but that
will require loading all lazy hooks. Would that be an issue?

Cheers,

Jelmer

Revision history for this message
Jonathan Lange (jml) wrote :

Thanks! This looks pretty sensible to me.

review: Approve
Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 3/30/2011 2:41 PM, Jelmer Vernooij wrote:
> Jelmer Vernooij has proposed merging lp:~jelmer/bzr/uninstall-hook into lp:bzr with lp:~jelmer/bzr/more-lazy-hooks as a prerequisite.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> Bug #301472 in Bazaar: "need a way to uninstall a hook"
> https://bugs.launchpad.net/bzr/+bug/301472
>
> For more details, see:
> https://code.launchpad.net/~jelmer/bzr/uninstall-hook/+merge/55527
>
> Add a Hooks.uninstall_named_hook(), which allows uninstalling named hooks (traditional and lazy).

We pretty much never use getattr/except instead do:

uninstall = getattr(hook, "uninstall", None)
if uninstall is None:
  raise errors.Unsupported...
else:
  uninstall()

The rest looks good to me.

 merge: approve

(tweak)

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk2TO44ACgkQJdeBCYSNAAMVOACfY6Otldq1ltDBZZnCPJgJEHjH
fzYAoKy8gukMam7H88wNbbye7cl/1Fim
=b+Z8
-----END PGP SIGNATURE-----

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote :

Jelmer Vernooij wrote:
> On Wed, 2011-03-30 at 12:59 +0000, Andrew Bennetts wrote:
> > What happens if two calls to install_named_hook use the same label?
> Bad things (basically, the first one with the label will be removed).
>
> I guess the alternative is to allow uninstalling by callable, but that
> will require loading all lazy hooks. Would that be an issue?

Another alternative would be to return a unique handle from
install_named_hook. Possibly an object with a .uninstall() method for
extra convenience.

I suspect it'd be fiddly to extend the registry objects to support that,
though.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

> Jelmer Vernooij wrote:
> > On Wed, 2011-03-30 at 12:59 +0000, Andrew Bennetts wrote:
> > > What happens if two calls to install_named_hook use the same label?
> > Bad things (basically, the first one with the label will be removed).
> >
> > I guess the alternative is to allow uninstalling by callable, but that
> > will require loading all lazy hooks. Would that be an issue?
>
> Another alternative would be to return a unique handle from
> install_named_hook. Possibly an object with a .uninstall() method for
> extra convenience.
>
> I suspect it'd be fiddly to extend the registry objects to support that,
> though.
Yeah, that seems a bit like overkill. I think we should either:

- Prevent registration of multiple callbacks with the same name - this might break some existing plugins (?)

- Allow unregistering callables by the method object. this means it's necessary to load all callables when unregistering one, but on the other hand unregistering hooks will mostly be used for testsuites, etc.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

... or we could just remove all hooks with the specified label. I think it's reasonable to require people to specify a unique label if they want to be able to uninstall their hooks.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

updated to uninstall all hooks with a specified label, not just the first one.

Revision history for this message
Vincent Ladeuil (vila) wrote :

Yeah, I agree that labels should be unique.

On the other hand... this should then be checked at registration time ;)

review: Approve
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/hooks.py'
2--- bzrlib/hooks.py 2011-03-30 13:03:08 +0000
3+++ bzrlib/hooks.py 2011-04-02 00:14:32 +0000
4@@ -243,6 +243,24 @@
5 if name is not None:
6 self.name_hook(a_callable, name)
7
8+ def uninstall_named_hook(self, hook_name, label):
9+ """Uninstall named hooks.
10+
11+ :param hook_name: Hook point name
12+ :param label: Label of the callable to uninstall
13+ """
14+ try:
15+ hook = self[hook_name]
16+ except KeyError:
17+ raise errors.UnknownHook(self.__class__.__name__, hook_name)
18+ try:
19+ uninstall = getattr(hook, "uninstall")
20+ except AttributeError:
21+ raise errors.UnsupportedOperation(self.install_named_hook_lazy,
22+ self)
23+ else:
24+ uninstall(label)
25+
26 def name_hook(self, a_callable, name):
27 """Associate name with a_callable to show users what is running."""
28 self._callable_names[a_callable] = name
29@@ -328,6 +346,21 @@
30 obj_getter = registry._ObjectGetter(callback)
31 self._callbacks.append((obj_getter, callback_label))
32
33+ def uninstall(self, label):
34+ """Uninstall the callback with the specified label.
35+
36+ :param label: Label of the entry to uninstall
37+ """
38+ entries_to_remove = []
39+ for entry in self._callbacks:
40+ (entry_callback, entry_label) = entry
41+ if entry_label == label:
42+ entries_to_remove.append(entry)
43+ if entries_to_remove == []:
44+ raise KeyError("No entry with label %r" % label)
45+ for entry in entries_to_remove:
46+ self._callbacks.remove(entry)
47+
48 def __iter__(self):
49 return (callback.get_obj() for callback, name in self._callbacks)
50
51
52=== modified file 'bzrlib/tests/test_hooks.py'
53--- bzrlib/tests/test_hooks.py 2011-03-30 13:03:08 +0000
54+++ bzrlib/tests/test_hooks.py 2011-04-02 00:14:32 +0000
55@@ -114,6 +114,43 @@
56 hooks.install_named_hook('set_rh', None, "demo")
57 self.assertEqual("demo", hooks.get_hook_name(None))
58
59+ def test_uninstall_named_hook(self):
60+ hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
61+ hooks.add_hook('set_rh', "Set revision history", (2, 0))
62+ hooks.install_named_hook('set_rh', None, "demo")
63+ self.assertEqual(1, len(hooks["set_rh"]))
64+ hooks.uninstall_named_hook("set_rh", "demo")
65+ self.assertEqual(0, len(hooks["set_rh"]))
66+
67+ def test_uninstall_multiple_named_hooks(self):
68+ # Multiple callbacks with the same label all get removed
69+ hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
70+ hooks.add_hook('set_rh', "Set revision history", (2, 0))
71+ hooks.install_named_hook('set_rh', 1, "demo")
72+ hooks.install_named_hook('set_rh', 2, "demo")
73+ hooks.install_named_hook('set_rh', 3, "othername")
74+ self.assertEqual(3, len(hooks["set_rh"]))
75+ hooks.uninstall_named_hook("set_rh", "demo")
76+ self.assertEqual(1, len(hooks["set_rh"]))
77+
78+ def test_uninstall_named_hook_unknown_callable(self):
79+ hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
80+ hooks.add_hook('set_rh', "Set revision hsitory", (2, 0))
81+ self.assertRaises(KeyError, hooks.uninstall_named_hook, "set_rh",
82+ "demo")
83+
84+ def test_uninstall_named_hook_raises_unknown_hook(self):
85+ hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
86+ self.assertRaises(errors.UnknownHook, hooks.uninstall_named_hook,
87+ 'silly', "")
88+
89+ def test_uninstall_named_hook_old_style(self):
90+ hooks = Hooks("bzrlib.tests.test_hooks", "some_hooks")
91+ hooks["set_rh"] = []
92+ hooks.install_named_hook('set_rh', None, "demo")
93+ self.assertRaises(errors.UnsupportedOperation,
94+ hooks.uninstall_named_hook, "set_rh", "demo")
95+
96 hooks = Hooks("bzrlib.tests.test_hooks", "TestHooks.hooks")
97
98 def test_install_lazy_named_hook(self):
99@@ -202,6 +239,19 @@
100 "my callback")
101 self.assertEqual([TestHook.lazy_callback], list(hook))
102
103+ def test_uninstall(self):
104+ hook = HookPoint("foo", "no docs", None, None)
105+ hook.hook_lazy(
106+ "bzrlib.tests.test_hooks", "TestHook.lazy_callback",
107+ "my callback")
108+ self.assertEqual([TestHook.lazy_callback], list(hook))
109+ hook.uninstall("my callback")
110+ self.assertEqual([], list(hook))
111+
112+ def test_uninstall_unknown(self):
113+ hook = HookPoint("foo", "no docs", None, None)
114+ self.assertRaises(KeyError, hook.uninstall, "my callback")
115+
116 def test___repr(self):
117 # The repr should list all the callbacks, with names.
118 hook = HookPoint("foo", "no docs", None, None)
119
120=== modified file 'doc/en/release-notes/bzr-2.4.txt'
121--- doc/en/release-notes/bzr-2.4.txt 2011-04-01 01:46:42 +0000
122+++ doc/en/release-notes/bzr-2.4.txt 2011-04-02 00:14:32 +0000
123@@ -65,6 +65,8 @@
124 mysteriously silent.)
125 (Martin Pool)
126
127+* New method ``Hooks.uninstall_named_hook``. (Jelmer Vernooij, #301472)
128+
129 Internals
130 *********
131