Merge lp:~thekorn/zeitgeist/zeitgeist-resonance_extensions into lp:zeitgeist/0.1
- zeitgeist-resonance_extensions
- Merge into 0.8-python
Proposed by
Markus Korn
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~thekorn/zeitgeist/zeitgeist-resonance_extensions |
Merge into: | lp:zeitgeist/0.1 |
Diff against target: |
385 lines (+276/-23) 6 files modified
_zeitgeist/engine/extension.py (+82/-0) _zeitgeist/engine/relevancy_provider.py (+34/-0) _zeitgeist/engine/resonance_engine.py (+11/-22) test/resonance-engine-extension-test.py (+39/-0) test/resonance-engine-test.py (+6/-1) test/test-engine-extension.rst (+104/-0) |
To merge this branch: | bzr merge lp:~thekorn/zeitgeist/zeitgeist-resonance_extensions |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zeitgeist Framework Team | Pending | ||
Review via email: mp+15143@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Markus Korn (thekorn) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file '_zeitgeist/engine/extension.py' |
2 | --- _zeitgeist/engine/extension.py 1970-01-01 00:00:00 +0000 |
3 | +++ _zeitgeist/engine/extension.py 2009-11-23 08:25:19 +0000 |
4 | @@ -0,0 +1,82 @@ |
5 | +# -.- coding: utf-8 -.- |
6 | + |
7 | +# Zeitgeist |
8 | +# |
9 | +# Copyright © 2009 Markus Korn <thekorn@gmx.de> |
10 | +# |
11 | +# This program is free software: you can redistribute it and/or modify |
12 | +# it under the terms of the GNU Lesser General Public License as published by |
13 | +# the Free Software Foundation, either version 3 of the License, or |
14 | +# (at your option) any later version. |
15 | +# |
16 | +# This program is distributed in the hope that it will be useful, |
17 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | +# GNU Lesser General Public License for more details. |
20 | +# |
21 | +# You should have received a copy of the GNU Lesser General Public License |
22 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
23 | + |
24 | +class Extension(object): |
25 | + """ Base class for all extensions |
26 | + |
27 | + Every extension has to define a list of accessible methods as |
28 | + 'PUBLIC_METHODS'. The constructor of an Extension object takes the |
29 | + engine object it extends as the only argument |
30 | + """ |
31 | + PUBLIC_METHODS = None |
32 | + |
33 | + def __init__(self, engine): |
34 | + self.engine = engine |
35 | + |
36 | + |
37 | +class ExtensionsCollection(object): |
38 | + """ Collection to manage all extensions """ |
39 | + |
40 | + def __init__(self, engine, defaults=None): |
41 | + self.__extensions = dict() |
42 | + self.__engine = engine |
43 | + self.__methods = dict() |
44 | + if defaults is not None: |
45 | + for extension in defaults: |
46 | + self.load(extension) |
47 | + |
48 | + def __repr__(self): |
49 | + return "%s(%r)" %(self.__class__.__name__, sorted(self.__methods.keys())) |
50 | + |
51 | + def load(self, extension): |
52 | + if not issubclass(extension, Extension): |
53 | + raise TypeError( |
54 | + "Unable to load %r, all extensions have to be subclasses of %r" %(extension, Extension) |
55 | + ) |
56 | + if getattr(extension, "PUBLIC_METHODS", None) is None: |
57 | + raise ValueError("Unable to load %r, this extension has not defined any methods" %extension) |
58 | + obj = extension(self.__engine) |
59 | + for method in obj.PUBLIC_METHODS: |
60 | + self._register_method(method, getattr(obj, method)) |
61 | + self.__extensions[obj.__class__.__name__] = obj |
62 | + |
63 | + def unload(self, extension): |
64 | + obj = self.__extensions[extension.__name__] |
65 | + for method in obj.PUBLIC_METHODS: |
66 | + del self.methods[method] |
67 | + del self.__extensions[extension.__name__] |
68 | + |
69 | + def __len__(self): |
70 | + return len(self.__extensions) |
71 | + |
72 | + @property |
73 | + def methods(self): |
74 | + return self.__methods |
75 | + |
76 | + def _register_method(self, name, method): |
77 | + if name in self.methods: |
78 | + raise ValueError("There is already an extension which provides a method called %r" %name) |
79 | + self.methods[name] = method |
80 | + |
81 | + def __getattr__(self, name): |
82 | + try: |
83 | + return self.methods[name] |
84 | + except KeyError: |
85 | + raise AttributeError("%s instance has no attribute %r" %(self.__class__.__name__, name)) |
86 | + |
87 | |
88 | === modified file '_zeitgeist/engine/relevancy_provider.py' |
89 | --- _zeitgeist/engine/relevancy_provider.py 2009-11-16 22:05:31 +0000 |
90 | +++ _zeitgeist/engine/relevancy_provider.py 2009-11-23 08:25:19 +0000 |
91 | @@ -27,6 +27,8 @@ |
92 | from dbutils import * |
93 | from __init__ import DB_PATH |
94 | |
95 | +from extension import Extension |
96 | + |
97 | class FocusSwitchRegister(object): |
98 | |
99 | def __init__(self, cursor): |
100 | @@ -235,3 +237,35 @@ |
101 | for row in self.cursor: |
102 | apps.append(self.app_table.lookup_by_id(row[0])) |
103 | return apps |
104 | + |
105 | +class RelevancyProvider(Extension): |
106 | + PUBLIC_METHODS = [ |
107 | + "get_subject_focus_duration", |
108 | + "get_actor_focus_duration", |
109 | + "get_longest_used_actors", |
110 | + "get_longest_used_subjects", |
111 | + "register_focus" |
112 | + ] |
113 | + |
114 | + def __init__(self, engine): |
115 | + super(RelevancyProvider, self).__init__(engine) |
116 | + self.focus_vertices = FocusSwitchRegister(self.engine._cursor) |
117 | + self.focus_duration = FocusDurationRegister(self.engine._cursor) |
118 | + |
119 | + def get_subject_focus_duration(self, subject, start, end): |
120 | + return self.focus_duration.get_subject_focus_duration(subject, start, end) |
121 | + |
122 | + def get_actor_focus_duration(self, actor, start, end): |
123 | + return self.focus_duration.get_actor_focus_duration(actor, start, end) |
124 | + |
125 | + def get_longest_used_actors(self, number, start, end): |
126 | + return self.focus_duration.get_longest_used_actors(number, start, end) |
127 | + |
128 | + def get_longest_used_subjects(self, number, start, end): |
129 | + return self.focus_duration.get_longest_used_subjects(number, start, end) |
130 | + |
131 | + def register_focus(self, actor, subject): |
132 | + now = time.time() |
133 | + self.focus_duration.focus_change(now, actor, subject) |
134 | + self.focus_vertices.focus_change(now, actor, subject) |
135 | + |
136 | |
137 | === modified file '_zeitgeist/engine/resonance_engine.py' |
138 | --- _zeitgeist/engine/resonance_engine.py 2009-11-19 14:22:14 +0000 |
139 | +++ _zeitgeist/engine/resonance_engine.py 2009-11-23 08:25:19 +0000 |
140 | @@ -30,13 +30,14 @@ |
141 | from xdg import BaseDirectory |
142 | from xdg.DesktopEntry import DesktopEntry |
143 | |
144 | +from extension import ExtensionsCollection |
145 | + |
146 | from zeitgeist.datamodel import Subject as _Subject, Event as _Event |
147 | from zeitgeist.datamodel import Interpretation, Manifestation, Mimetype, Category, StorageState, TimeRange |
148 | import _zeitgeist.engine |
149 | from _zeitgeist.engine.dbutils import * |
150 | from _zeitgeist.engine.querymancer import * |
151 | from _zeitgeist.lrucache import * |
152 | -from _zeitgeist.engine.relevancy_provider import FocusSwitchRegister, FocusDurationRegister |
153 | |
154 | logging.basicConfig(level=logging.DEBUG) |
155 | log = logging.getLogger("zeitgeist.engine") |
156 | @@ -373,7 +374,6 @@ |
157 | global _event |
158 | self._cursor = get_default_cursor() |
159 | |
160 | - #Load extensions |
161 | # Find the last event id we used, and start generating |
162 | # new ids from that offset |
163 | row = _event.find("max(id)").fetchone() |
164 | @@ -381,9 +381,14 @@ |
165 | self._last_event_id = row[0] |
166 | else: |
167 | self._last_event_id = 0 |
168 | - |
169 | - self.focus_switch = FocusSwitchRegister(_cursor) |
170 | - self.focus_duration = FocusDurationRegister(_cursor) |
171 | + |
172 | + #Load extensions |
173 | + # right now we don't load any default extension |
174 | + self.__extensions = ExtensionsCollection(self) |
175 | + |
176 | + @property |
177 | + def extensions(self): |
178 | + return self.__extensions |
179 | |
180 | def close(self): |
181 | global _cursor |
182 | @@ -603,23 +608,7 @@ |
183 | ORDER BY timestamp DESC LIMIT 1 |
184 | """, (actor,)).fetchone() |
185 | return query["timestamp"] if query else 0 |
186 | - |
187 | - def get_subject_focus_duration(self, subject, start, end): |
188 | - return self.focus_duration.get_subject_focus_duration(subject, start, end) |
189 | - |
190 | - def get_actor_focus_duration(self, actor, start, end): |
191 | - return self.focus_duration.get_actor_focus_duration(actor, start, end) |
192 | - |
193 | - def get_longest_used_actors(self, number, start, end): |
194 | - return self.focus_duration.get_longest_used_actors(number, start, end) |
195 | - |
196 | - def get_longest_used_subjects(self, number, start, end): |
197 | - return self.focus_duration.get_longest_used_subjects(number, start, end) |
198 | - |
199 | - def register_focus(self, actor, subject): |
200 | - now = time.time() |
201 | - self.focus_duration.focus_change(now, actor, subject) |
202 | - self.focus_switch.focus_change(now, actor, subject) |
203 | + |
204 | |
205 | class WhereClause: |
206 | |
207 | |
208 | === added file 'test/resonance-engine-extension-test.py' |
209 | --- test/resonance-engine-extension-test.py 1970-01-01 00:00:00 +0000 |
210 | +++ test/resonance-engine-extension-test.py 2009-11-23 08:25:19 +0000 |
211 | @@ -0,0 +1,39 @@ |
212 | +#!/usr/bin/python |
213 | +# -.- coding: utf-8 -.- |
214 | + |
215 | +# Update python path to use local zeitgeist module |
216 | +import sys |
217 | +import os |
218 | +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) |
219 | + |
220 | +from _zeitgeist.engine.resonance_engine import ZeitgeistEngine |
221 | +from _zeitgeist.engine.extension import Extension |
222 | + |
223 | +import unittest |
224 | + |
225 | +class _Extension1(Extension): |
226 | + PUBLIC_METHODS = ["return_hallo", "return_engine"] |
227 | + |
228 | + def return_hallo(self): |
229 | + return "Hallo" |
230 | + |
231 | + def return_boo(self): |
232 | + return "boo" |
233 | + |
234 | + def return_engine(self): |
235 | + return self.engine |
236 | + |
237 | + |
238 | +class TestExtensions(unittest.TestCase): |
239 | + |
240 | + def testCreateEngine(self): |
241 | + engine = ZeitgeistEngine() |
242 | + self.assertEqual(len(engine.extensions), 0) |
243 | + self.assertRaises(AttributeError, engine.extensions.__getattr__, "return_hallo") |
244 | + engine.extensions.load(_Extension1) |
245 | + self.assertEqual(engine.extensions.return_hallo(), "Hallo") |
246 | + self.assertRaises(AttributeError, engine.extensions.__getattr__, "return_boo") |
247 | + self.assertEqual(engine.extensions.return_engine(), engine) |
248 | + |
249 | +if __name__ == "__main__": |
250 | + unittest.main() |
251 | |
252 | === modified file 'test/resonance-engine-test.py' |
253 | --- test/resonance-engine-test.py 2009-11-19 14:22:14 +0000 |
254 | +++ test/resonance-engine-test.py 2009-11-23 08:25:19 +0000 |
255 | @@ -9,6 +9,7 @@ |
256 | import _zeitgeist.engine |
257 | from _zeitgeist.engine import create_engine |
258 | from zeitgeist.datamodel import * |
259 | +from _zeitgeist.engine.relevancy_provider import RelevancyProvider |
260 | from testutils import import_events |
261 | |
262 | import unittest |
263 | @@ -335,8 +336,12 @@ |
264 | |
265 | class ZeitgeistRelevancyProviderTest(_engineTestClass): |
266 | |
267 | + def setUp(self): |
268 | + super(ZeitgeistRelevancyProviderTest, self).setUp() |
269 | + self.engine.extensions.load(RelevancyProvider) |
270 | + |
271 | def testInsertFocusEvent(self): |
272 | - self.engine.register_focus("boo","boo") |
273 | + self.engine.extensions.register_focus("boo","boo") |
274 | #self.assertRaises(ValueError, self.engine.insert_events, [ev]) |
275 | |
276 | if __name__ == "__main__": |
277 | |
278 | === added file 'test/test-engine-extension.rst' |
279 | --- test/test-engine-extension.rst 1970-01-01 00:00:00 +0000 |
280 | +++ test/test-engine-extension.rst 2009-11-23 08:25:19 +0000 |
281 | @@ -0,0 +1,104 @@ |
282 | +How do extensions to the engine work? |
283 | +===================================== |
284 | + |
285 | +Extensions are objects which are maintained in external python modules |
286 | +with optional methods which are accessible from a ZeitgeistEngine object. |
287 | +There is only one extension right now called RelevancyProvider, which |
288 | +is not enable by default (for now). |
289 | + |
290 | +Per default there is no extension enabled |
291 | + |
292 | + >>> from _zeitgeist.engine.resonance_engine import ZeitgeistEngine |
293 | + >>> engine = ZeitgeistEngine() |
294 | + >>> len(engine.extensions) |
295 | + 0 |
296 | + |
297 | +To create a new extension you have to subclass the Extension class and |
298 | +provide a list of accessible methods in PUBLIC_METHODS |
299 | + |
300 | + >>> from _zeitgeist.engine.extension import Extension |
301 | + >>> class SampleExtension(Extension): |
302 | + ... PUBLIC_METHODS = ["add_value", "get_engine"] |
303 | + ... |
304 | + ... def __init__(self, engine): |
305 | + ... super(SampleExtension, self).__init__(engine) |
306 | + ... self.counter = 0 |
307 | + ... |
308 | + ... def add_value(self, value): |
309 | + ... self.counter += value |
310 | + ... return self.counter |
311 | + ... |
312 | + ... def get_engine(self): |
313 | + ... return self.engine |
314 | + ... |
315 | + ... def internal_method(self): |
316 | + ... return 0 |
317 | + ... |
318 | + |
319 | +This example adds to new methods to the engine 'add_value' and 'get_engine'. |
320 | +On the other hand the method called 'internal_method' is not available as |
321 | +a method of the engine object. The constructor of an Extension object takes |
322 | +one parameter, the engine object. Per default this engine object is accessible |
323 | +as the 'engine' attribute of the extension object, like 'self.engine'. |
324 | +Now we have to load this extension to the engine |
325 | + |
326 | + >>> engine.extensions.load(SampleExtension) |
327 | + >>> len(engine.extensions) |
328 | + 1 |
329 | + >>> print engine.extensions |
330 | + ExtensionsCollection(['add_value', 'get_engine']) |
331 | + >>> sorted(engine.extensions.methods) |
332 | + ['add_value', 'get_engine'] |
333 | + |
334 | +In the last line you can see all method which are added to the engine by |
335 | +an extension. |
336 | +This methods are now accessible like |
337 | + |
338 | + >>> engine.extensions.add_value(5) |
339 | + 5 |
340 | + >>> engine.extensions.add_value(1) |
341 | + 6 |
342 | + >>> engine.extensions.get_engine() # doctest:+ELLIPSIS |
343 | + <_zeitgeist.engine.resonance_engine.ZeitgeistEngine instance at 0x...> |
344 | + |
345 | +However, there is also a private method which is not accessible as a member |
346 | +of the engine |
347 | + |
348 | + >>> engine.extensions.internal_method() |
349 | + Traceback (most recent call last): |
350 | + ... |
351 | + AttributeError: ExtensionsCollection instance has no attribute 'internal_method' |
352 | + |
353 | +It is also possible to unload an extension |
354 | + |
355 | + >>> engine.extensions.unload(SampleExtension) |
356 | + >>> sorted(engine.extensions.methods) |
357 | + [] |
358 | + |
359 | +Now its methods are not accessible anymore |
360 | + |
361 | + >>> engine.extensions.add_value(5) |
362 | + Traceback (most recent call last): |
363 | + ... |
364 | + AttributeError: ExtensionsCollection instance has no attribute 'add_value' |
365 | + |
366 | +If you try to load an extension which is not a subclass if `Extension` a |
367 | +TypeError is raised |
368 | + |
369 | + >>> engine.extensions.load(set) # doctest:+ELLIPSIS |
370 | + Traceback (most recent call last): |
371 | + ... |
372 | + TypeError: Unable to load <type 'set'>, all extensions have to be subclasses of <...Extension'> |
373 | + |
374 | +Also, if an extension does not define any public method a ValueErro is raised |
375 | + |
376 | + >>> class FailExtension(Extension): |
377 | + ... |
378 | + ... def get_boo(self): |
379 | + ... return "boo" |
380 | + ... |
381 | + >>> engine.extensions.load(FailExtension) # doctest:+ELLIPSIS |
382 | + Traceback (most recent call last): |
383 | + ... |
384 | + ValueError: Unable to load <...FailExtension'>, this extension has not defined any methods |
385 | + |
This branch fixes bug 483556 by adding a framework to load extensions to ZeitgeistEngine. extensions. some_method_ name() engine- extension. rst [0] for more information how this framework is working.
Extension methods are accessible as
engine.
For now there is one such extension (RelevancyProvider) which is not loaded by default. See test/test-
[0] http:// bazaar. launchpad. net/~thekorn/ zeitgeist/ zeitgeist- resonance_ extensions/ annotate/ head%3A/ test/test- engine- extension. rst