Edgewall Software

TracPluggableModules: module.py

File module.py, 6.0 KB (added by cmlenz, 4 years ago)

First example of a possible module API

Line 
1from time import time, localtime, strftime
2
3
4class __Registry(object):
5    """
6    Singleton module registry. This class manages the information about which
7    module extends which other modules.
8    """
9
10    modules = {}
11    extensions = {}
12
13    def get_extensions(self, name):
14        return self.extensions.get(name, [])
15
16    def register(self, name, cls):
17        self.modules[name] = cls
18        for extension in cls._extends:
19            if not self.extensions.has_key(extension):
20                self.extensions[extension] = []
21            self.extensions[extension].append(name)
22
23Registry = __Registry()
24
25
26class Environment:
27    """
28    Environment stub that does nothing else but keep track of which modules
29    are active, and instantiate modules when necessary.
30    """
31
32    active_modules = None
33
34    def __init__(self):
35        self.active_modules = {}
36
37    def module(self, name):
38        module = self.active_modules.get(name)
39        if not module:
40            module = Registry.modules[name](self)
41            self.active_modules[name] = module
42        return module
43
44
45class ExtensionPoint(object):
46    """
47    An extension point is basically a method name that is declared by a module
48    which offers functionality to which other modules can contribute.
49    """
50
51    module = None
52    name = None
53    env = None
54
55    def __init__(self, flatten_results=True):
56        self.flatten_results = flatten_results
57
58    def __call__(self, *kw):
59        extensions = Registry.get_extensions(self.module)
60        retvals = []
61        for extension in [e for e in extensions]:
62            module = self.env.module(extension)
63            if hasattr(module, self.name):
64                fn = getattr(module, self.name)
65                retval = fn.__call__(module, *kw)
66                if self.flatten_results:
67                    if not retval:
68                        continue
69                    if type(retval) is list:
70                        retvals.extend(retval)
71                    else:
72                        retvals.append(retval)
73                else:
74                    retvals.append(retval)
75        return retvals
76
77
78class MetaModule(type):
79    """
80    Meta class for modules. It registers the module with the Registry, collects
81    extension points and extensions declared by the module class, and adds
82    convenient ways to access the extensions.
83    """
84
85    class __Extensions(object): pass
86
87    def __new__(cls, name, bases, d):
88
89        extensions = cls.__Extensions()
90
91        module_name = d.get('_name')
92        if not module_name:
93            if name.endswith('Module'):
94                module_name = name[:-6].lower()
95            else:
96                module_name = name.lower()
97        else:
98            del d['_name']
99
100        # collect extension points
101        extension_points = []
102        for attr, value in d.items():
103            if isinstance(value, ExtensionPoint):
104                value.module = module_name
105                value.name = attr
106                extension_points.append(value)
107                setattr(extensions, attr, value)
108                del d[attr]
109
110        newClass = type.__new__(cls, name, bases, d)
111        newClass.name = name
112        newClass._extension_points = extension_points
113        newClass._extends = d.get('_extends', [])
114        newClass.extensions = extensions
115
116        if module_name:
117            Registry.register(module_name, newClass)
118
119        return newClass
120
121
122class Module(object):
123    """
124    Base class for modules.
125    """
126
127    __metaclass__ = MetaModule
128    _registry = None
129    env = None
130
131    def __init__(self, env):
132        self.env = env
133        for extension_point in self._extension_points:
134            extension_point.env = env
135
136
137# ----------------------------------------------------------------------------
138# Stuff below this point is only for demonstration purposes
139# ----------------------------------------------------------------------------
140
141
142class TimedEvent(object):
143
144    time = None
145    title = None
146    description = None
147
148    def __init__(self, time, title, description=''):
149        self.time = time
150        self.title = title or ''
151        self.description = description or ''
152
153    def __repr__(self):
154        retval = '%s -- %s' % (strftime('%x %X', localtime(self.time)), self.title)
155        if self.description:
156            retval += '\n\t%s' % self.description
157        return retval
158
159
160class TimelineModule(Module):
161
162    _name = 'timeline'
163
164    get_timeline_events = ExtensionPoint()
165    get_timeline_filters = ExtensionPoint()
166
167    def get_events(self, start, stop, filters=None):
168        if not filters:
169            filters = self.extensions.get_timeline_filters()
170        return self.extensions.get_timeline_events(start, stop, filters)
171
172
173class TicketModule(Module):
174
175    _name = 'ticket'
176    _extends = [ 'timeline' ]
177
178    def get_timeline_events(self, start, stop, filters):
179        if 'tickets' in filters:
180            return [TimedEvent(time(), 'Ticket #1'),
181                    TimedEvent(time(), 'Ticket #2')]
182
183    def get_timeline_filters(self):
184        return ['tickets']
185
186
187class ChangesetModule(Module):
188
189    _name = 'changeset'
190    _extends = [ 'timeline' ]
191
192    def get_timeline_events(self, start, stop, filters):
193        if 'changesets' in filters:
194            return [TimedEvent(time(), 'Changeset [1]'),
195                    TimedEvent(time(), 'Changeset [2]')]
196
197    def get_timeline_filters(self):
198        return ['changesets']
199
200
201if __name__ == '__main__':
202    env = Environment()
203    timeline = env.module('timeline')
204   
205    print '\nAll events:'
206    all_events = timeline.get_events(0, 0)
207    print all_events
208
209    print '\nChangeset events:'
210    changeset_events = timeline.get_events(0, 0, ['changesets'])
211    print changeset_events
212
213    print '\nTicket events:'
214    ticket_events = timeline.get_events(0, 0, ['tickets'])
215    print ticket_events