18
18
"""Support for plugin hooking logic."""
19
from bzrlib import registry
20
24
from bzrlib.lazy_import import lazy_import
21
from bzrlib.symbol_versioning import deprecated_method
22
25
lazy_import(globals(), """
25
28
from bzrlib import (
26
_format_version_tuple,
29
from bzrlib.help_topics import help_as_plain_text
29
_format_version_tuple,
33
known_hooks = registry.Registry()
34
# known_hooks registry contains
35
# tuple of (module, member name) which is the hook point
36
# module where the specific hooks are defined
37
# callable to get the empty specific Hooks for that attribute
38
known_hooks.register_lazy(('bzrlib.branch', 'Branch.hooks'), 'bzrlib.branch',
40
known_hooks.register_lazy(('bzrlib.bzrdir', 'BzrDir.hooks'), 'bzrlib.bzrdir',
42
known_hooks.register_lazy(('bzrlib.commands', 'Command.hooks'),
43
'bzrlib.commands', 'CommandHooks')
44
known_hooks.register_lazy(('bzrlib.info', 'hooks'),
45
'bzrlib.info', 'InfoHooks')
46
known_hooks.register_lazy(('bzrlib.lock', 'Lock.hooks'), 'bzrlib.lock',
48
known_hooks.register_lazy(('bzrlib.msgeditor', 'hooks'), 'bzrlib.msgeditor',
50
known_hooks.register_lazy(('bzrlib.mutabletree', 'MutableTree.hooks'),
51
'bzrlib.mutabletree', 'MutableTreeHooks')
52
known_hooks.register_lazy(('bzrlib.smart.client', '_SmartClient.hooks'),
53
'bzrlib.smart.client', 'SmartClientHooks')
54
known_hooks.register_lazy(('bzrlib.smart.server', 'SmartTCPServer.hooks'),
55
'bzrlib.smart.server', 'SmartServerHooks')
56
known_hooks.register_lazy(
57
('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks'),
58
'bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilderHooks')
59
known_hooks.register_lazy(
60
('bzrlib.merge_directive', '_BaseMergeDirective.hooks'),
61
'bzrlib.merge_directive', 'MergeDirectiveHooks')
36
class KnownHooksRegistry(registry.Registry):
37
# known_hooks registry contains
38
# tuple of (module, member name) which is the hook point
39
# module where the specific hooks are defined
40
# callable to get the empty specific Hooks for that attribute
42
def register_lazy_hook(self, hook_module_name, hook_member_name,
43
hook_factory_member_name):
44
self.register_lazy((hook_module_name, hook_member_name),
45
hook_module_name, hook_factory_member_name)
47
def iter_parent_objects(self):
48
"""Yield (hook_key, (parent_object, attr)) tuples for every registered
49
hook, where 'parent_object' is the object that holds the hook
52
This is useful for resetting/restoring all the hooks to a known state,
53
as is done in bzrlib.tests.TestCase._clear_hooks.
55
for key in self.keys():
56
yield key, self.key_to_parent_and_attribute(key)
58
def key_to_parent_and_attribute(self, (module_name, member_name)):
59
"""Convert a known_hooks key to a (parent_obj, attr) pair.
61
:param key: A tuple (module_name, member_name) as found in the keys of
62
the known_hooks registry.
63
:return: The parent_object of the hook and the name of the attribute on
64
that parent object where the hook is kept.
66
parent_mod, parent_member, attr = pyutils.calc_parent_name(module_name,
68
return pyutils.get_named_object(parent_mod, parent_member), attr
71
_builtin_known_hooks = (
72
('bzrlib.branch', 'Branch.hooks', 'BranchHooks'),
73
('bzrlib.bzrdir', 'BzrDir.hooks', 'BzrDirHooks'),
74
('bzrlib.commands', 'Command.hooks', 'CommandHooks'),
75
('bzrlib.info', 'hooks', 'InfoHooks'),
76
('bzrlib.lock', 'Lock.hooks', 'LockHooks'),
77
('bzrlib.merge', 'Merger.hooks', 'MergeHooks'),
78
('bzrlib.msgeditor', 'hooks', 'MessageEditorHooks'),
79
('bzrlib.mutabletree', 'MutableTree.hooks', 'MutableTreeHooks'),
80
('bzrlib.smart.client', '_SmartClient.hooks', 'SmartClientHooks'),
81
('bzrlib.smart.server', 'SmartTCPServer.hooks', 'SmartServerHooks'),
82
('bzrlib.status', 'hooks', 'StatusHooks'),
83
('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks',
84
'RioVersionInfoBuilderHooks'),
85
('bzrlib.merge_directive', 'BaseMergeDirective.hooks',
86
'MergeDirectiveHooks'),
89
known_hooks = KnownHooksRegistry()
90
for (_hook_module, _hook_attribute, _hook_class) in _builtin_known_hooks:
91
known_hooks.register_lazy_hook(_hook_module, _hook_attribute, _hook_class)
92
del _builtin_known_hooks, _hook_module, _hook_attribute, _hook_class
64
95
def known_hooks_key_to_object((module_name, member_name)):
68
99
the known_hooks registry.
69
100
:return: The object this specifies.
71
return registry._LazyObjectGetter(module_name, member_name).get_obj()
74
def known_hooks_key_to_parent_and_attribute((module_name, member_name)):
75
"""Convert a known_hooks key to a object.
77
:param key: A tuple (module_name, member_name) as found in the keys of
78
the known_hooks registry.
79
:return: The object this specifies.
81
member_list = member_name.rsplit('.', 1)
82
if len(member_list) == 2:
83
parent_name, attribute = member_list
86
attribute = member_name
87
parent = known_hooks_key_to_object((module_name, parent_name))
88
return parent, attribute
102
return pyutils.get_named_object(module_name, member_name)
105
@symbol_versioning.deprecated_function(symbol_versioning.deprecated_in((2, 3)))
106
def known_hooks_key_to_parent_and_attribute(key):
107
"""See KnownHooksRegistry.key_to_parent_and_attribute."""
108
return known_hooks.key_to_parent_and_attribute(key)
91
111
class Hooks(dict):
95
115
FOO hook is triggered.
118
def __init__(self, module=None, member_name=None):
119
"""Create a new hooks dictionary.
121
:param module: The module from which this hooks dictionary should be loaded
122
(used for lazy hooks)
123
:param member_name: Name under which this hooks dictionary should be loaded.
124
(used for lazy hooks)
99
126
dict.__init__(self)
100
127
self._callable_names = {}
128
self._module = module
129
self._member_name = member_name
131
def add_hook(self, name, doc, introduced, deprecated=None):
132
"""Add a hook point to this dictionary.
134
:param name: The name of the hook, for clients to use when registering.
135
:param doc: The docs for the hook.
136
:param introduced: When the hook was introduced (e.g. (0, 15)).
137
:param deprecated: When the hook was deprecated, None for
141
raise errors.DuplicateKey(name)
143
callbacks = _lazy_hooks.setdefault(
144
(self._module, self._member_name, name), [])
147
hookpoint = HookPoint(name=name, doc=doc, introduced=introduced,
148
deprecated=deprecated, callbacks=callbacks)
149
self[name] = hookpoint
151
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4)))
102
152
def create_hook(self, hook):
103
153
"""Create a hook which can have callbacks registered for it.
147
197
return self._callable_names.get(a_callable, "No hook name")
199
def install_named_hook_lazy(self, hook_name, callable_module,
200
callable_member, name):
201
"""Install a_callable in to the hook hook_name lazily, and label it.
203
:param hook_name: A hook name. See the __init__ method for the complete
205
:param callable_module: Name of the module in which the callable is
207
:param callable_member: Member name of the callable.
208
:param name: A name to associate the callable with, to show users what
212
hook = self[hook_name]
214
raise errors.UnknownHook(self.__class__.__name__, hook_name)
216
hook_lazy = getattr(hook, "hook_lazy")
217
except AttributeError:
218
raise errors.UnsupportedOperation(self.install_named_hook_lazy,
221
hook_lazy(callable_module, callable_member, name)
149
223
def install_named_hook(self, hook_name, a_callable, name):
150
224
"""Install a_callable in to the hook hook_name, and label it name.
152
:param hook_name: A hook name. See the __init__ method of BranchHooks
153
for the complete list of hooks.
226
:param hook_name: A hook name. See the __init__ method for the complete
154
228
:param a_callable: The callable to be invoked when the hook triggers.
155
229
The exact signature will depend on the hook - see the __init__
156
method of BranchHooks for details on each hook.
230
method for details on each hook.
157
231
:param name: A name to associate a_callable with, to show users what is
239
343
:param callback_label: A label to show in the UI while this callback is
242
self._callbacks.append(callback)
243
if callback_label is not None:
244
self._callback_names[callback] = callback_label
346
obj_getter = registry._ObjectGetter(callback)
347
self._callbacks.append((obj_getter, callback_label))
349
def uninstall(self, label):
350
"""Uninstall the callback with the specified label.
352
:param label: Label of the entry to uninstall
354
entries_to_remove = []
355
for entry in self._callbacks:
356
(entry_callback, entry_label) = entry
357
if entry_label == label:
358
entries_to_remove.append(entry)
359
if entries_to_remove == []:
360
raise KeyError("No entry with label %r" % label)
361
for entry in entries_to_remove:
362
self._callbacks.remove(entry)
246
364
def __iter__(self):
247
return iter(self._callbacks)
365
return (callback.get_obj() for callback, name in self._callbacks)
249
367
def __len__(self):
250
368
return len(self._callbacks)