~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/hooks.py

  • Committer: Andrew Bennetts
  • Date: 2010-10-13 00:26:41 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101013002641-9tlh9k89mlj1666m
Keep docs-plain working.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
 
18
18
"""Support for plugin hooking logic."""
19
 
 
20
 
from bzrlib import (
21
 
    registry,
22
 
    symbol_versioning,
23
 
    )
 
19
from bzrlib import registry
24
20
from bzrlib.lazy_import import lazy_import
25
21
lazy_import(globals(), """
26
22
import textwrap
27
23
 
28
24
from bzrlib import (
29
 
    _format_version_tuple,
30
 
    errors,
31
 
    pyutils,
32
 
    )
 
25
        _format_version_tuple,
 
26
        errors,
 
27
        )
 
28
from bzrlib.help_topics import help_as_plain_text
33
29
""")
34
30
 
35
31
 
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
41
 
 
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)
46
 
 
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
50
 
        instance.
51
 
 
52
 
        This is useful for resetting/restoring all the hooks to a known state,
53
 
        as is done in bzrlib.tests.TestCase._clear_hooks.
54
 
        """
55
 
        for key in self.keys():
56
 
            yield key, self.key_to_parent_and_attribute(key)
57
 
 
58
 
    def key_to_parent_and_attribute(self, (module_name, member_name)):
59
 
        """Convert a known_hooks key to a (parent_obj, attr) pair.
60
 
 
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.
65
 
        """
66
 
        parent_mod, parent_member, attr = pyutils.calc_parent_name(module_name,
67
 
            member_name)
68
 
        return pyutils.get_named_object(parent_mod, parent_member), attr
69
 
 
70
 
 
71
 
_builtin_known_hooks = (
72
 
    ('bzrlib.branch', 'Branch.hooks', 'BranchHooks'),
73
 
    ('bzrlib.bzrdir', 'BzrDir.hooks', 'BzrDirHooks'),
74
 
    ('bzrlib.commands', 'Command.hooks', 'CommandHooks'),
75
 
    ('bzrlib.config', 'ConfigHooks', '_ConfigHooks'),
76
 
    ('bzrlib.info', 'hooks', 'InfoHooks'),
77
 
    ('bzrlib.lock', 'Lock.hooks', 'LockHooks'),
78
 
    ('bzrlib.merge', 'Merger.hooks', 'MergeHooks'),
79
 
    ('bzrlib.msgeditor', 'hooks', 'MessageEditorHooks'),
80
 
    ('bzrlib.mutabletree', 'MutableTree.hooks', 'MutableTreeHooks'),
81
 
    ('bzrlib.smart.client', '_SmartClient.hooks', 'SmartClientHooks'),
82
 
    ('bzrlib.smart.server', 'SmartTCPServer.hooks', 'SmartServerHooks'),
83
 
    ('bzrlib.status', 'hooks', 'StatusHooks'),
84
 
    ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks',
85
 
        'RioVersionInfoBuilderHooks'),
86
 
    ('bzrlib.merge_directive', 'BaseMergeDirective.hooks',
87
 
        'MergeDirectiveHooks'),
88
 
    )
89
 
 
90
 
known_hooks = KnownHooksRegistry()
91
 
for (_hook_module, _hook_attribute, _hook_class) in _builtin_known_hooks:
92
 
    known_hooks.register_lazy_hook(_hook_module, _hook_attribute, _hook_class)
93
 
del _builtin_known_hooks, _hook_module, _hook_attribute, _hook_class
 
32
known_hooks = registry.Registry()
 
33
# known_hooks registry contains
 
34
# tuple of (module, member name) which is the hook point
 
35
# module where the specific hooks are defined
 
36
# callable to get the empty specific Hooks for that attribute
 
37
known_hooks.register_lazy(('bzrlib.branch', 'Branch.hooks'), 'bzrlib.branch',
 
38
    'BranchHooks')
 
39
known_hooks.register_lazy(('bzrlib.bzrdir', 'BzrDir.hooks'), 'bzrlib.bzrdir',
 
40
    'BzrDirHooks')
 
41
known_hooks.register_lazy(('bzrlib.commands', 'Command.hooks'),
 
42
    'bzrlib.commands', 'CommandHooks')
 
43
known_hooks.register_lazy(('bzrlib.info', 'hooks'),
 
44
    'bzrlib.info', 'InfoHooks')
 
45
known_hooks.register_lazy(('bzrlib.lock', 'Lock.hooks'), 'bzrlib.lock',
 
46
    'LockHooks')
 
47
known_hooks.register_lazy(('bzrlib.merge', 'Merger.hooks'), 'bzrlib.merge',
 
48
    'MergeHooks')
 
49
known_hooks.register_lazy(('bzrlib.msgeditor', 'hooks'), 'bzrlib.msgeditor',
 
50
    'MessageEditorHooks')
 
51
known_hooks.register_lazy(('bzrlib.mutabletree', 'MutableTree.hooks'),
 
52
    'bzrlib.mutabletree', 'MutableTreeHooks')
 
53
known_hooks.register_lazy(('bzrlib.smart.client', '_SmartClient.hooks'),
 
54
    'bzrlib.smart.client', 'SmartClientHooks')
 
55
known_hooks.register_lazy(('bzrlib.smart.server', 'SmartTCPServer.hooks'),
 
56
    'bzrlib.smart.server', 'SmartServerHooks')
 
57
known_hooks.register_lazy(('bzrlib.status', 'hooks'),
 
58
    'bzrlib.status', 'StatusHooks')
 
59
known_hooks.register_lazy(
 
60
    ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks'),
 
61
    'bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilderHooks')
 
62
known_hooks.register_lazy(
 
63
    ('bzrlib.merge_directive', 'BaseMergeDirective.hooks'),
 
64
    'bzrlib.merge_directive', 'MergeDirectiveHooks')
94
65
 
95
66
 
96
67
def known_hooks_key_to_object((module_name, member_name)):
100
71
        the known_hooks registry.
101
72
    :return: The object this specifies.
102
73
    """
103
 
    return pyutils.get_named_object(module_name, member_name)
104
 
 
105
 
 
106
 
@symbol_versioning.deprecated_function(symbol_versioning.deprecated_in((2, 3)))
107
 
def known_hooks_key_to_parent_and_attribute(key):
108
 
    """See KnownHooksRegistry.key_to_parent_and_attribute."""
109
 
    return known_hooks.key_to_parent_and_attribute(key)
 
74
    return registry._LazyObjectGetter(module_name, member_name).get_obj()
 
75
 
 
76
 
 
77
def known_hooks_key_to_parent_and_attribute((module_name, member_name)):
 
78
    """Convert a known_hooks key to a object.
 
79
 
 
80
    :param key: A tuple (module_name, member_name) as found in the keys of
 
81
        the known_hooks registry.
 
82
    :return: The object this specifies.
 
83
    """
 
84
    member_list = member_name.rsplit('.', 1)
 
85
    if len(member_list) == 2:
 
86
        parent_name, attribute = member_list
 
87
    else:
 
88
        parent_name = None
 
89
        attribute = member_name
 
90
    parent = known_hooks_key_to_object((module_name, parent_name))
 
91
    return parent, attribute
110
92
 
111
93
 
112
94
class Hooks(dict):
116
98
    FOO hook is triggered.
117
99
    """
118
100
 
119
 
    def __init__(self, module=None, member_name=None):
120
 
        """Create a new hooks dictionary.
121
 
 
122
 
        :param module: The module from which this hooks dictionary should be loaded
123
 
            (used for lazy hooks)
124
 
        :param member_name: Name under which this hooks dictionary should be loaded.
125
 
            (used for lazy hooks)
126
 
        """
 
101
    def __init__(self):
127
102
        dict.__init__(self)
128
103
        self._callable_names = {}
129
 
        self._module = module
130
 
        self._member_name = member_name
131
 
 
132
 
    def add_hook(self, name, doc, introduced, deprecated=None):
133
 
        """Add a hook point to this dictionary.
134
 
 
135
 
        :param name: The name of the hook, for clients to use when registering.
136
 
        :param doc: The docs for the hook.
137
 
        :param introduced: When the hook was introduced (e.g. (0, 15)).
138
 
        :param deprecated: When the hook was deprecated, None for
139
 
            not-deprecated.
140
 
        """
141
 
        if name in self:
142
 
            raise errors.DuplicateKey(name)
143
 
        if self._module:
144
 
            callbacks = _lazy_hooks.setdefault(
145
 
                (self._module, self._member_name, name), [])
146
 
        else:
147
 
            callbacks = None
148
 
        hookpoint = HookPoint(name=name, doc=doc, introduced=introduced,
149
 
                              deprecated=deprecated, callbacks=callbacks)
150
 
        self[name] = hookpoint
151
 
 
152
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4)))
 
104
 
153
105
    def create_hook(self, hook):
154
106
        """Create a hook which can have callbacks registered for it.
155
107
 
197
149
        """
198
150
        return self._callable_names.get(a_callable, "No hook name")
199
151
 
200
 
    def install_named_hook_lazy(self, hook_name, callable_module,
201
 
        callable_member, name):
202
 
        """Install a_callable in to the hook hook_name lazily, and label it.
203
 
 
204
 
        :param hook_name: A hook name. See the __init__ method for the complete
205
 
            list of hooks.
206
 
        :param callable_module: Name of the module in which the callable is
207
 
            present.
208
 
        :param callable_member: Member name of the callable.
209
 
        :param name: A name to associate the callable with, to show users what
210
 
            is running.
211
 
        """
212
 
        try:
213
 
            hook = self[hook_name]
214
 
        except KeyError:
215
 
            raise errors.UnknownHook(self.__class__.__name__, hook_name)
216
 
        try:
217
 
            hook_lazy = getattr(hook, "hook_lazy")
218
 
        except AttributeError:
219
 
            raise errors.UnsupportedOperation(self.install_named_hook_lazy,
220
 
                self)
221
 
        else:
222
 
            hook_lazy(callable_module, callable_member, name)
223
 
 
224
152
    def install_named_hook(self, hook_name, a_callable, name):
225
153
        """Install a_callable in to the hook hook_name, and label it name.
226
154
 
227
 
        :param hook_name: A hook name. See the __init__ method for the complete
228
 
            list of hooks.
 
155
        :param hook_name: A hook name. See the __init__ method of BranchHooks
 
156
            for the complete list of hooks.
229
157
        :param a_callable: The callable to be invoked when the hook triggers.
230
158
            The exact signature will depend on the hook - see the __init__
231
 
            method for details on each hook.
 
159
            method of BranchHooks for details on each hook.
232
160
        :param name: A name to associate a_callable with, to show users what is
233
161
            running.
234
162
        """
244
172
        if name is not None:
245
173
            self.name_hook(a_callable, name)
246
174
 
247
 
    def uninstall_named_hook(self, hook_name, label):
248
 
        """Uninstall named hooks.
249
 
 
250
 
        :param hook_name: Hook point name
251
 
        :param label: Label of the callable to uninstall
252
 
        """
253
 
        try:
254
 
            hook = self[hook_name]
255
 
        except KeyError:
256
 
            raise errors.UnknownHook(self.__class__.__name__, hook_name)
257
 
        try:
258
 
            uninstall = getattr(hook, "uninstall")
259
 
        except AttributeError:
260
 
            raise errors.UnsupportedOperation(self.uninstall_named_hook, self)
261
 
        else:
262
 
            uninstall(label)
263
 
 
264
175
    def name_hook(self, a_callable, name):
265
176
        """Associate name with a_callable to show users what is running."""
266
177
        self._callable_names[a_callable] = name
279
190
        should describe the recommended replacement hook to register for.
280
191
    """
281
192
 
282
 
    def __init__(self, name, doc, introduced, deprecated=None, callbacks=None):
 
193
    def __init__(self, name, doc, introduced, deprecated):
283
194
        """Create a HookPoint.
284
195
 
285
196
        :param name: The name of the hook, for clients to use when registering.
292
203
        self.__doc__ = doc
293
204
        self.introduced = introduced
294
205
        self.deprecated = deprecated
295
 
        if callbacks is None:
296
 
            self._callbacks = []
297
 
        else:
298
 
            self._callbacks = callbacks
 
206
        self._callbacks = []
 
207
        self._callback_names = {}
299
208
 
300
209
    def docs(self):
301
210
        """Generate the documentation for this HookPoint.
321
230
        return '\n'.join(strings)
322
231
 
323
232
    def __eq__(self, other):
324
 
        return (type(other) == type(self) and other.__dict__ == self.__dict__)
325
 
 
326
 
    def hook_lazy(self, callback_module, callback_member, callback_label):
327
 
        """Lazily register a callback to be called when this HookPoint fires.
328
 
 
329
 
        :param callback_module: Module of the callable to use when this
330
 
            HookPoint fires.
331
 
        :param callback_member: Member name of the callback.
332
 
        :param callback_label: A label to show in the UI while this callback is
333
 
            processing.
334
 
        """
335
 
        obj_getter = registry._LazyObjectGetter(callback_module,
336
 
            callback_member)
337
 
        self._callbacks.append((obj_getter, callback_label))
 
233
        return (type(other) == type(self) and 
 
234
            other.__dict__ == self.__dict__)
338
235
 
339
236
    def hook(self, callback, callback_label):
340
237
        """Register a callback to be called when this HookPoint fires.
343
240
        :param callback_label: A label to show in the UI while this callback is
344
241
            processing.
345
242
        """
346
 
        obj_getter = registry._ObjectGetter(callback)
347
 
        self._callbacks.append((obj_getter, callback_label))
348
 
 
349
 
    def uninstall(self, label):
350
 
        """Uninstall the callback with the specified label.
351
 
 
352
 
        :param label: Label of the entry to uninstall
353
 
        """
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)
 
243
        self._callbacks.append(callback)
 
244
        if callback_label is not None:
 
245
            self._callback_names[callback] = callback_label
363
246
 
364
247
    def __iter__(self):
365
 
        return (callback.get_obj() for callback, name in self._callbacks)
 
248
        return iter(self._callbacks)
366
249
 
367
250
    def __len__(self):
368
251
        return len(self._callbacks)
372
255
        strings.append("<%s(" % type(self).__name__)
373
256
        strings.append(self.name)
374
257
        strings.append("), callbacks=[")
375
 
        callbacks = self._callbacks
376
 
        for (callback, callback_name) in callbacks:
377
 
            strings.append(repr(callback.get_obj()))
 
258
        for callback in self._callbacks:
 
259
            strings.append(repr(callback))
378
260
            strings.append("(")
379
 
            strings.append(callback_name)
 
261
            strings.append(self._callback_names[callback])
380
262
            strings.append("),")
381
 
        if len(callbacks) == 1:
 
263
        if len(self._callbacks) == 1:
382
264
            strings[-1] = ")"
383
265
        strings.append("]>")
384
266
        return ''.join(strings)
423
305
        hooks = known_hooks_key_to_object(hook_key)
424
306
        segments.append(hooks.docs())
425
307
    return '\n'.join(segments)
426
 
 
427
 
 
428
 
# Lazily registered hooks. Maps (module, name, hook_name) tuples
429
 
# to lists of tuples with objectgetters and names
430
 
_lazy_hooks = {}
431
 
 
432
 
 
433
 
def install_lazy_named_hook(hookpoints_module, hookpoints_name, hook_name,
434
 
    a_callable, name):
435
 
    """Install a callable in to a hook lazily, and label it name.
436
 
 
437
 
    :param hookpoints_module: Module name of the hook points.
438
 
    :param hookpoints_name: Name of the hook points.
439
 
    :param hook_name: A hook name.
440
 
    :param callable: a callable to call for the hook.
441
 
    :param name: A name to associate a_callable with, to show users what is
442
 
        running.
443
 
    """
444
 
    key = (hookpoints_module, hookpoints_name, hook_name)
445
 
    obj_getter = registry._ObjectGetter(a_callable)
446
 
    _lazy_hooks.setdefault(key, []).append((obj_getter, name))