~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/plugin.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_plugin -*-
 
2
# Copyright (c) 2005 Divmod, Inc.
 
3
# Copyright (c) 2007 Twisted Matrix Laboratories.
 
4
# See LICENSE for details.
 
5
 
 
6
"""
 
7
Plugin system for Twisted.
 
8
 
 
9
@author: Jp Calderone
 
10
@author: Glyph Lefkowitz
 
11
"""
 
12
 
 
13
import os
 
14
import sys
 
15
 
 
16
from zope.interface import Interface, providedBy
 
17
 
 
18
def _determinePickleModule():
 
19
    """
 
20
    Determine which 'pickle' API module to use.
 
21
    """
 
22
    try:
 
23
        import cPickle
 
24
        return cPickle
 
25
    except ImportError:
 
26
        import pickle
 
27
        return pickle
 
28
 
 
29
pickle = _determinePickleModule()
 
30
 
 
31
from twisted.python.components import getAdapterFactory
 
32
from twisted.python.reflect import namedAny
 
33
from twisted.python import log
 
34
from twisted.python.modules import getModule
 
35
 
 
36
 
 
37
 
 
38
class IPlugin(Interface):
 
39
    """
 
40
    Interface that must be implemented by all plugins.
 
41
 
 
42
    Only objects which implement this interface will be considered for return
 
43
    by C{getPlugins}.  To be useful, plugins should also implement some other
 
44
    application-specific interface.
 
45
    """
 
46
 
 
47
 
 
48
 
 
49
class CachedPlugin(object):
 
50
    def __init__(self, dropin, name, description, provided):
 
51
        self.dropin = dropin
 
52
        self.name = name
 
53
        self.description = description
 
54
        self.provided = provided
 
55
        self.dropin.plugins.append(self)
 
56
 
 
57
    def __repr__(self):
 
58
        return '<CachedPlugin %r/%r (provides %r)>' % (
 
59
            self.name, self.dropin.moduleName,
 
60
            ', '.join([i.__name__ for i in self.provided]))
 
61
 
 
62
    def load(self):
 
63
        return namedAny(self.dropin.moduleName + '.' + self.name)
 
64
 
 
65
    def __conform__(self, interface, registry=None, default=None):
 
66
        for providedInterface in self.provided:
 
67
            if providedInterface.isOrExtends(interface):
 
68
                return self.load()
 
69
            if getAdapterFactory(providedInterface, interface, None) is not None:
 
70
                return interface(self.load(), default)
 
71
        return default
 
72
 
 
73
    # backwards compat HOORJ
 
74
    getComponent = __conform__
 
75
 
 
76
 
 
77
 
 
78
class CachedDropin(object):
 
79
    """
 
80
    A collection of L{CachedPlugin} instances from a particular module in a
 
81
    plugin package.
 
82
 
 
83
    @type moduleName: C{str}
 
84
    @ivar moduleName: The fully qualified name of the plugin module this
 
85
        represents.
 
86
 
 
87
    @type description: C{str} or C{NoneType}
 
88
    @ivar description: A brief explanation of this collection of plugins
 
89
        (probably the plugin module's docstring).
 
90
 
 
91
    @type plugins: C{list}
 
92
    @ivar plugins: The L{CachedPlugin} instances which were loaded from this
 
93
        dropin.
 
94
    """
 
95
    def __init__(self, moduleName, description):
 
96
        self.moduleName = moduleName
 
97
        self.description = description
 
98
        self.plugins = []
 
99
 
 
100
 
 
101
 
 
102
def _generateCacheEntry(provider):
 
103
    dropin = CachedDropin(provider.__name__,
 
104
                          provider.__doc__)
 
105
    for k, v in provider.__dict__.iteritems():
 
106
        plugin = IPlugin(v, None)
 
107
        if plugin is not None:
 
108
            cachedPlugin = CachedPlugin(dropin, k, v.__doc__, list(providedBy(plugin)))
 
109
    return dropin
 
110
 
 
111
try:
 
112
    fromkeys = dict.fromkeys
 
113
except AttributeError:
 
114
    def fromkeys(keys, value=None):
 
115
        d = {}
 
116
        for k in keys:
 
117
            d[k] = value
 
118
        return d
 
119
 
 
120
def getCache(module):
 
121
    """
 
122
    Compute all the possible loadable plugins, while loading as few as
 
123
    possible and hitting the filesystem as little as possible.
 
124
 
 
125
    @param module: a Python module object.  This represents a package to search
 
126
    for plugins.
 
127
 
 
128
    @return: a dictionary mapping module names to CachedDropin instances.
 
129
    """
 
130
    allCachesCombined = {}
 
131
    mod = getModule(module.__name__)
 
132
    # don't want to walk deep, only immediate children.
 
133
    lastPath = None
 
134
    buckets = {}
 
135
    # Fill buckets with modules by related entry on the given package's
 
136
    # __path__.  There's an abstraction inversion going on here, because this
 
137
    # information is already represented internally in twisted.python.modules,
 
138
    # but it's simple enough that I'm willing to live with it.  If anyone else
 
139
    # wants to fix up this iteration so that it's one path segment at a time,
 
140
    # be my guest.  --glyph
 
141
    for plugmod in mod.iterModules():
 
142
        fpp = plugmod.filePath.parent()
 
143
        if fpp not in buckets:
 
144
            buckets[fpp] = []
 
145
        bucket = buckets[fpp]
 
146
        bucket.append(plugmod)
 
147
    for pseudoPackagePath, bucket in buckets.iteritems():
 
148
        dropinPath = pseudoPackagePath.child('dropin.cache')
 
149
        try:
 
150
            lastCached = dropinPath.getModificationTime()
 
151
            dropinDotCache = pickle.load(dropinPath.open('rb'))
 
152
        except:
 
153
            dropinDotCache = {}
 
154
            lastCached = 0
 
155
 
 
156
        needsWrite = False
 
157
        existingKeys = {}
 
158
        for pluginModule in bucket:
 
159
            pluginKey = pluginModule.name.split('.')[-1]
 
160
            existingKeys[pluginKey] = True
 
161
            if ((pluginKey not in dropinDotCache) or
 
162
                (pluginModule.filePath.getModificationTime() >= lastCached)):
 
163
                needsWrite = True
 
164
                try:
 
165
                    provider = pluginModule.load()
 
166
                except:
 
167
                    # dropinDotCache.pop(pluginKey, None)
 
168
                    log.err()
 
169
                else:
 
170
                    entry = _generateCacheEntry(provider)
 
171
                    dropinDotCache[pluginKey] = entry
 
172
        # Make sure that the cache doesn't contain any stale plugins.
 
173
        for pluginKey in dropinDotCache.keys():
 
174
            if pluginKey not in existingKeys:
 
175
                del dropinDotCache[pluginKey]
 
176
                needsWrite = True
 
177
        if needsWrite:
 
178
            try:
 
179
                dropinPath.setContent(pickle.dumps(dropinDotCache))
 
180
            except:
 
181
                log.err()
 
182
        allCachesCombined.update(dropinDotCache)
 
183
    return allCachesCombined
 
184
 
 
185
 
 
186
def getPlugins(interface, package=None):
 
187
    """
 
188
    Retrieve all plugins implementing the given interface beneath the given module.
 
189
 
 
190
    @param interface: An interface class.  Only plugins which implement this
 
191
    interface will be returned.
 
192
 
 
193
    @param package: A package beneath which plugins are installed.  For
 
194
    most uses, the default value is correct.
 
195
 
 
196
    @return: An iterator of plugins.
 
197
    """
 
198
    if package is None:
 
199
        import twisted.plugins as package
 
200
    allDropins = getCache(package)
 
201
    for dropin in allDropins.itervalues():
 
202
        for plugin in dropin.plugins:
 
203
            try:
 
204
                adapted = interface(plugin, None)
 
205
            except:
 
206
                log.err()
 
207
            else:
 
208
                if adapted is not None:
 
209
                    yield adapted
 
210
 
 
211
 
 
212
# Old, backwards compatible name.  Don't use this.
 
213
getPlugIns = getPlugins
 
214
 
 
215
 
 
216
def pluginPackagePaths(name):
 
217
    """
 
218
    Return a list of additional directories which should be searched for
 
219
    modules to be included as part of the named plugin package.
 
220
 
 
221
    @type name: C{str}
 
222
    @param name: The fully-qualified Python name of a plugin package, eg
 
223
        C{'twisted.plugins'}.
 
224
 
 
225
    @rtype: C{list} of C{str}
 
226
    @return: The absolute paths to other directories which may contain plugin
 
227
        modules for the named plugin package.
 
228
    """
 
229
    package = name.split('.')
 
230
    # Note that this may include directories which do not exist.  It may be
 
231
    # preferable to remove such directories at this point, rather than allow
 
232
    # them to be searched later on.
 
233
    #
 
234
    # Note as well that only '__init__.py' will be considered to make a
 
235
    # directory a package (and thus exclude it from this list).  This means
 
236
    # that if you create a master plugin package which has some other kind of
 
237
    # __init__ (eg, __init__.pyc) it will be incorrectly treated as a
 
238
    # supplementary plugin directory.
 
239
    return [
 
240
        os.path.abspath(os.path.join(x, *package))
 
241
        for x
 
242
        in sys.path
 
243
        if
 
244
        not os.path.exists(os.path.join(x, *package + ['__init__.py']))]
 
245
 
 
246
__all__ = ['getPlugins', 'pluginPackagePaths']