Merge lp:~rainct/zeitgeist/bug799199 into lp:zeitgeist/0.1

Proposed by Siegfried Gevatter
Status: Merged
Merged at revision: 1770
Proposed branch: lp:~rainct/zeitgeist/bug799199
Merge into: lp:zeitgeist/0.1
Diff against target: 248 lines (+128/-11)
3 files modified
test/client-test.py (+78/-0)
zeitgeist/client.py (+43/-6)
zeitgeist/datamodel.py (+7/-5)
To merge this branch: bzr merge lp:~rainct/zeitgeist/bug799199
Reviewer Review Type Date Requested Status
Markus Korn Approve
Review via email: mp+65125@code.launchpad.net

Description of the change

It's often convenient for Python applications using the zeitgeist module to create their own subclasses of Event and Subject (so they can add additional functions to them, etc). This branch makes it possible to register such subclasses with zeitgeist.client.ZeitgeistClient.

I'm looking forward to using this feature in Activity Log Manager and Zeitgeist Explorer :).

To post a comment you must log in.
Revision history for this message
Markus Korn (thekorn) wrote :

Good work Siegfried,
tests are running fine, code is looking good, feel free to merge.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'test/client-test.py'
2--- test/client-test.py 1970-01-01 00:00:00 +0000
3+++ test/client-test.py 2011-06-19 14:18:32 +0000
4@@ -0,0 +1,78 @@
5+#!/usr/bin/python
6+# -.- coding: utf-8 -.-
7+
8+# Update python path to use local zeitgeist module
9+import sys
10+import os
11+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
12+
13+import unittest
14+
15+from zeitgeist.client import ZeitgeistClient
16+from zeitgeist import datamodel
17+
18+import testutils
19+from testutils import parse_events
20+
21+class EventAndSubjectOverrides (testutils.RemoteTestCase):
22+ """
23+ This class tests the functionality allowing users to override the
24+ Event and Subject types instantiated by ZeitgeistClient (LP: #799199).
25+ """
26+
27+ class CustomEvent(datamodel.Event):
28+ pass
29+
30+ class CustomSubject(datamodel.Subject):
31+ pass
32+
33+ class CustomNothing(object):
34+ pass
35+
36+ def testEventOverrideWhiteBox(self):
37+ self.assertEqual(self.client._event_type, datamodel.Event)
38+ self.client.register_event_subclass(self.CustomEvent)
39+ self.assertEqual(self.client._event_type, self.CustomEvent)
40+
41+ def testSubjectOverrideWhiteBox(self):
42+ self.assertEqual(self.client._event_type._subject_type, datamodel.Subject)
43+ self.client.register_subject_subclass(self.CustomSubject)
44+ self.assertEqual(self.client._event_type._subject_type, self.CustomSubject)
45+
46+ def testEventAndSubjectOverrideWhiteBox(self):
47+ self.client.register_event_subclass(self.CustomEvent)
48+ self.client.register_subject_subclass(self.CustomSubject)
49+ self.assertTrue(issubclass(self.client._event_type, self.CustomEvent))
50+ self.assertEqual(self.client._event_type._subject_type, self.CustomSubject)
51+
52+ def testBadOverride(self):
53+ self.assertRaises(TypeError, lambda:
54+ self.client.register_event_subclass(self.CustomNothing))
55+ self.assertRaises(TypeError, lambda:
56+ self.client.register_subject_subclass(self.CustomNothing))
57+
58+ def testEventAndSubjectOverrideBlackBox(self):
59+ self.client.register_event_subclass(self.CustomEvent)
60+ self.client.register_subject_subclass(self.CustomSubject)
61+ self.insertEventsAndWait(parse_events("test/data/single_event.js"))
62+ result = self.findEventsForValuesAndWait()
63+ self.assertTrue(len(result[0].subjects) >= 1)
64+ self.assertTrue(isinstance(result[0], self.CustomEvent))
65+ self.assertTrue(isinstance(result[0].subjects[0], self.CustomSubject))
66+
67+ def testMonitorOverrideBlackBox(self):
68+ self.client.register_event_subclass(self.CustomEvent)
69+ self.client.register_subject_subclass(self.CustomSubject)
70+ mainloop = self.create_mainloop()
71+
72+ def notify_insert_handler(time_range, events):
73+ self.assertTrue(len(events[0].subjects) >= 1)
74+ self.assertTrue(isinstance(events[0], self.CustomEvent))
75+ self.assertTrue(
76+ isinstance(events[0].subjects[0], self.CustomSubject))
77+ mainloop.quit()
78+
79+ self.client.install_monitor(datamodel.TimeRange.always(), [],
80+ notify_insert_handler, notify_insert_handler)
81+ self.client.insert_events(parse_events("test/data/single_event.js"))
82+ mainloop.run()
83
84=== modified file 'zeitgeist/client.py'
85--- zeitgeist/client.py 2011-06-15 14:18:58 +0000
86+++ zeitgeist/client.py 2011-06-19 14:18:32 +0000
87@@ -257,21 +257,27 @@
88
89 # Used in Monitor._next_path() to generate unique path names
90 _last_path_id = 0
91+
92+ _event_type = Event
93
94 def __init__ (self, time_range, event_templates, insert_callback,
95- delete_callback, monitor_path=None):
96+ delete_callback, monitor_path=None, event_type=None):
97 if not monitor_path:
98 monitor_path = Monitor._next_path()
99 elif isinstance(monitor_path, (str, unicode)):
100 monitor_path = dbus.ObjectPath(monitor_path)
101
102+ if event_type:
103+ if not issubclass(event_type, Event):
104+ raise TypeError("Event subclass expected.")
105+ self._event_type = event_type
106+
107 self._time_range = time_range
108 self._templates = event_templates
109 self._path = monitor_path
110 self._insert_callback = insert_callback
111 self._delete_callback = delete_callback
112 dbus.service.Object.__init__(self, dbus.SessionBus(), monitor_path)
113-
114
115 def get_path (self): return self._path
116 path = property(get_path,
117@@ -303,7 +309,7 @@
118 See :meth:`ZeitgeistClient.install_monitor`
119 """
120 self._insert_callback(TimeRange(time_range[0], time_range[1]),
121- map(Event, events))
122+ map(self._event_type, events))
123
124 @dbus.service.method("org.gnome.zeitgeist.Monitor",
125 in_signature="(xx)au")
126@@ -350,6 +356,7 @@
127 """
128
129 _installed_monitors = []
130+ _event_type = Event
131
132 @staticmethod
133 def get_event_and_extra_arguments(arguments):
134@@ -382,6 +389,35 @@
135 "Error reinstalling monitor: %s" % err))
136 self._iface.connect_join(reconnect_monitors)
137
138+ def register_event_subclass(self, event_type):
139+ """
140+ Register a subclass of Event with this ZeiteistClient instance. When
141+ data received over D-Bus is instantiated into an Event class, the
142+ provided subclass will be used.
143+ """
144+ if not issubclass(event_type, Event):
145+ raise TypeError("Event subclass expected.")
146+ self._event_type = event_type
147+
148+ def register_subject_subclass(self, subject_type):
149+ """
150+ Register a subclass of Subject with this ZeiteistClient instance. When
151+ data received over D-Bus is instantiated into a Subject class, the
152+ provided subclass will be used.
153+
154+ Note that this method works by changing the Event type associated with
155+ this ZeitgeistClient instance, so it should always be called *after*
156+ any register_event_subclass calls.
157+
158+ Even better, if you also have a custom Event subclass, you may directly
159+ override the Subject type by changing its _subject_type class variable.
160+ """
161+ if not issubclass(subject_type, Subject):
162+ raise TypeError("Subject subclass expected.")
163+ class EventWithCustomSubject(self._event_type):
164+ _subject_type = subject_type
165+ self._event_type = EventWithCustomSubject
166+
167 def _safe_error_handler(self, error_handler, *args):
168 if error_handler is not None:
169 if callable(error_handler):
170@@ -664,7 +700,7 @@
171 num_events,
172 result_type,
173 reply_handler=lambda raw: events_reply_handler(
174- map(Event.new_for_struct, raw)),
175+ map(self._event_type.new_for_struct, raw)),
176 error_handler=self._safe_error_handler(error_handler,
177 events_reply_handler, []))
178
179@@ -725,7 +761,7 @@
180 # the raw DBus reply into a list of Event instances
181 self._iface.GetEvents(event_ids,
182 reply_handler=lambda raw: events_reply_handler(
183- map(Event.new_for_struct, raw)),
184+ map(self._event_type.new_for_struct, raw)),
185 error_handler=self._safe_error_handler(error_handler,
186 events_reply_handler, []))
187
188@@ -876,7 +912,8 @@
189
190
191 mon = Monitor(time_range, event_templates, notify_insert_handler,
192- notify_delete_handler, monitor_path=monitor_path)
193+ notify_delete_handler, monitor_path=monitor_path,
194+ event_type=self._event_type)
195 self._iface.InstallMonitor(mon.path,
196 mon.time_range,
197 mon.templates,
198
199=== modified file 'zeitgeist/datamodel.py'
200--- zeitgeist/datamodel.py 2011-05-07 13:26:49 +0000
201+++ zeitgeist/datamodel.py 2011-06-19 14:18:32 +0000
202@@ -577,6 +577,8 @@
203 SUPPORTS_NEGATION = (Interpretation, Manifestation, Actor, Origin)
204 SUPPORTS_WILDCARDS = (Actor, Origin)
205
206+ _subject_type = Subject
207+
208 def __init__(self, struct = None):
209 """
210 If 'struct' is set it must be a list containing the event
211@@ -608,11 +610,11 @@
212 self.append("")
213 elif len(struct) == 2:
214 self.append(self._check_event_struct(struct[0]))
215- self.append(map(Subject, struct[1]))
216+ self.append(map(self._subject_type, struct[1]))
217 self.append("")
218 elif len(struct) == 3:
219 self.append(self._check_event_struct(struct[0]))
220- self.append(map(Subject, struct[1]))
221+ self.append(map(self._subject_type, struct[1]))
222 self.append(struct[2])
223 else:
224 raise ValueError("Invalid struct length %s" % len(struct))
225@@ -702,7 +704,7 @@
226 if self._dict_contains_subject_keys(values):
227 if "subjects" in values:
228 raise ValueError("Subject keys, subject_*, specified together with full subject list")
229- subj = Subject()
230+ subj = self._subject_type()
231 subj.uri = values.get("subject_uri", "")
232 subj.current_uri = values.get("subject_current_uri", "")
233 subj.interpretation = values.get("subject_interpretation", "")
234@@ -737,12 +739,12 @@
235 Append a new empty Subject and return a reference to it
236 """
237 if not subject:
238- subject = Subject()
239+ subject = self._subject_type()
240 self.subjects.append(subject)
241 return subject
242
243 def get_subjects(self):
244- return self[1]
245+ return self[1]
246
247 def set_subjects(self, subjects):
248 self[1] = subjects

Subscribers

People subscribed via source and target branches