Merge lp:~zeitgeist/zeitgeist/find_events into lp:zeitgeist/0.1

Proposed by Siegfried Gevatter
Status: Merged
Merged at revision: not available
Proposed branch: lp:~zeitgeist/zeitgeist/find_events
Merge into: lp:zeitgeist/0.1
Diff against target: 502 lines (+240/-101)
4 files modified
_zeitgeist/engine/main.py (+16/-6)
_zeitgeist/engine/remote.py (+107/-50)
test/engine-test.py (+2/-2)
zeitgeist/client.py (+115/-43)
To merge this branch: bzr merge lp:~zeitgeist/zeitgeist/find_events
Reviewer Review Type Date Requested Status
Mikkel Kamstrup Erlandsen code review Needs Fixing
Review via email: mp+17240@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Siegfried Gevatter (rainct) wrote :

FindEventIds is great for complex use cases, especially interfaces involving pagination (as we discussed in Bolzano), but for simple queries or uses like in the GNOME Activity Journal, where all data is requested at once (whatever the number of events there are), it's really only in the way, by forcing an additional SQL query and D-Bus request.

Considering that SQLite and D-Bus are what take most of the time in a request, we should avoid those as much as possible. This branch implements the few necessary changes in engine/ to implement this; additions to client.py can follow once it's merged.

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

I am ok with this in theory, but I have a few comments regarding the code:

1. Can you please fully document FindEvents() with Sphinx markup?

2. I don't like calling our method find_eventids() and then returning Event instances... And using a method argument to control the return type seems non-standard to me. The engine is semi-public API because we have extensions so I think we should strive to keep it clean. I suggest we have to public methods find_events() and find_event_ids().

3. The docs for FindEventIds() should explain its purpose (large result sets - keeping ordering) and cross ref FIndEvents()

4. Can we have the ZeitgeistClient methods implemented and documented before the merge also?

review: Needs Fixing (code review)
lp:~zeitgeist/zeitgeist/find_events updated
1277. By Siegfried Gevatter

Expose FindEvents in the Python module.

1278. By Siegfried Gevatter

Update FindEvents to use the new engine methods instead of return_events=True.

1279. By Siegfried Gevatter

s/ids_reply_handler/events_reply_handler/ where appropriate

1280. By Siegfried Gevatter

Fix out_signature of FindEvents.

1281. By Siegfried Gevatter

More fixing: Simplify find_events results before sending
them over D-Bus.

Revision history for this message
Siegfried Gevatter (rainct) wrote :

I've fixed your concerns and pushed into lp:zeitgeist. Please tell me if there's anything else you can spot.

I've also updated the GNOME Activity Journal to make use of this new method.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '_zeitgeist/engine/main.py'
2--- _zeitgeist/engine/main.py 2010-01-12 17:12:47 +0000
3+++ _zeitgeist/engine/main.py 2010-01-12 21:55:18 +0000
4@@ -3,7 +3,7 @@
5 # Zeitgeist
6 #
7 # Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com>
8-# Copyright © 2009 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
9+# Copyright © 2009-2010 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
10 # Copyright © 2009 Seif Lotfy <seif@lotfy.com>
11 # Copyright © 2009 Markus Korn <thekorn@gmx.net>
12 # Copyright © 2009 Alexander Gabriel <einalex@mayanna.org>
13@@ -552,12 +552,16 @@
14
15 return where
16
17- def find_eventids(self, time_range, event_templates, storage_state,
18- max_events, order, return_events=False):
19+ def _find_events(self, return_mode, time_range, event_templates,
20+ storage_state, max_events, order):
21 """
22 Accepts 'event_templates' as either a real list of Events or as
23 a list of tuples (event_data,subject_data) as we do in the
24- DBus API
25+ DBus API.
26+
27+ Return modes:
28+ - 0: IDs.
29+ - 1: Events.
30 """
31
32 t = time.time()
33@@ -567,7 +571,7 @@
34 if not where.may_have_results():
35 return []
36
37- if not return_events:
38+ if return_mode == 0:
39 sql = "SELECT DISTINCT id FROM event_view"
40 else:
41 sql = "SELECT * FROM event_view"
42@@ -587,13 +591,19 @@
43
44 result = self._cursor.execute(sql, where.arguments).fetchall()
45
46- if return_events:
47+ if return_mode == 1:
48 return self.get_events(rows=result)
49 result = [row[0] for row in result]
50
51 log.debug("Fetched %d event IDs in %fs" % (len(result), time.time()- t))
52 return result
53
54+ def find_eventids(self, *args):
55+ return self._find_events(0, *args)
56+
57+ def find_events(self, *args):
58+ return self._find_events(1, *args)
59+
60 def find_related_uris(self, timerange, event_templates, result_event_templates,
61 result_storage_state):
62 """
63
64=== modified file '_zeitgeist/engine/remote.py'
65--- _zeitgeist/engine/remote.py 2010-01-12 17:23:20 +0000
66+++ _zeitgeist/engine/remote.py 2010-01-12 21:55:18 +0000
67@@ -2,7 +2,7 @@
68
69 # Zeitgeist
70 #
71-# Copyright © 2009 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
72+# Copyright © 2009-2010 Siegfried-Angel Gevatter Pujals <rainct@ubuntu.com>
73 # Copyright © 2009 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com>
74 #
75 # This program is free software: you can redistribute it and/or modify
76@@ -53,31 +53,34 @@
77 self._mainloop = mainloop
78 self._notifications = MonitorManager()
79
80+ # Private methods
81+
82+ def _make_events_sendable(self, events):
83+ for event in events:
84+ if event is not None:
85+ event._make_dbus_sendable()
86+ return [NULL_EVENT if event is None else event for event in events]
87+
88 # Reading stuff
89
90 @dbus.service.method(constants.DBUS_INTERFACE,
91 in_signature="au", out_signature="a("+constants.SIG_EVENT+")")
92 def GetEvents(self, event_ids):
93- """Get full event data for a set of event ids
94+ """Get full event data for a set of event IDs
95
96 Each event which is not found in the event log is represented
97 by the `NULL_EVENT` struct in the resulting array.
98
99- :param event_ids: An array of event ids. Fx. obtained by calling
100+ :param event_ids: An array of event IDs. Fx. obtained by calling
101 :meth:`FindEventIds`
102 :type event_ids: Array of unsigned 32 bit integers.
103 DBus signature au
104- :returns: Full event data for all the requested ids. The
105+ :returns: Full event data for all the requested IDs. The
106 event data can be conveniently converted into a list of
107 :class:`Event` instances by calling *events = map(Event.new_for_struct, result)*
108 :rtype: A list of serialized events. DBus signature a(asaasay).
109 """
110- events = _engine.get_events(event_ids)
111- for event in events:
112- if event is not None:
113- event._make_dbus_sendable()
114- events = [NULL_EVENT if event is None else event for event in events]
115- return events
116+ return self._make_events_sendable(_engine.get_events(event_ids))
117
118 @dbus.service.method(constants.DBUS_INTERFACE,
119 in_signature="(xx)a("+constants.SIG_EVENT+")a("+constants.SIG_EVENT+")u",
120@@ -123,11 +126,14 @@
121 result_event_templates, storage_state)
122
123 @dbus.service.method(constants.DBUS_INTERFACE,
124- in_signature="(xx)a("+constants.SIG_EVENT+")uuu", out_signature="au")
125+ in_signature="(xx)a("+constants.SIG_EVENT+")uuu",
126+ out_signature="au")
127 def FindEventIds(self, time_range, event_templates, storage_state,
128 num_events, result_type):
129- """Search for events matching a given set of templates and return theids of matching events.
130- Use :meth:`GetEvents` passing in the returned ids to look up
131+ """Search for events matching a given set of templates and return
132+ the IDs of matching events.
133+
134+ Use :meth:`GetEvents` passing in the returned IDs to look up
135 the full event data.
136
137 The matching is done where unset fields in the templates
138@@ -135,52 +141,101 @@
139 subject then events will match the template if any one of their
140 subjects match any one of the subject templates.
141
142- :param time_range: two timestamps defining the timerange for
143- the query. When using the Python bindings for Zeitgeist you
144- may pass a :class:`TimeRange <zeitgeist.datamodel.TimeRange>`
145- instance directly to this method
146- :type time_range: tuple of 64 bit integers. DBus signature (xx)
147- :param event_templates: An array of event templates which the
148- returned events should match at least one of.
149- When using the Python bindings for Zeitgeist you may pass
150- a list of :class:`Event <zeitgeist.datamodel.Event>`
151- instances directly to this method.
152- :type event_templates: array of events. DBus signature a(asaasay)
153- :param storage_state: whether the item is currently known to be available. The list of possible values is enumerated in :class:`StorageState <zeitgeist.datamodel.StorageState>` class
154- :type storage_state: unsigned integer
155- :param num_events: maximal amount of returned events
156- :type num_events: unsigned integer
157- :param order: unsigned integer representing a :class:`result type <zeitgeist.datamodel.ResultType>`
158- :type order: unsigned integer
159- :returns: An array containing the ids of all matching events,
160+ This method is intended for queries potentially returning a
161+ large result set. It is especially useful in cases where only
162+ a portion of the results are to be displayed at the same time
163+ (eg., by using paging or dynamic scrollbars), as by holding a
164+ list of IDs you keep a stable ordering and you can ask for the
165+ details associated to them in hunks, when you need them. For queries
166+ yielding a small amount of results, or where you need the information
167+ about all results at once no matter how many of them there are,
168+ see :meth:`FindEvents`.
169+
170+ :param time_range: two timestamps defining the timerange for
171+ the query. When using the Python bindings for Zeitgeist you
172+ may pass a :class:`TimeRange <zeitgeist.datamodel.TimeRange>`
173+ instance directly to this method
174+ :type time_range: tuple of 64 bit integers. DBus signature (xx)
175+ :param event_templates: An array of event templates which the
176+ returned events should match at least one of.
177+ When using the Python bindings for Zeitgeist you may pass
178+ a list of :class:`Event <zeitgeist.datamodel.Event>`
179+ instances directly to this method.
180+ :type event_templates: array of events. DBus signature a(asaasay)
181+ :param storage_state: whether the item is currently known to be
182+ available. The list of possible values is enumerated in
183+ :class:`StorageState <zeitgeist.datamodel.StorageState>` class
184+ :type storage_state: unsigned integer
185+ :param num_events: maximal amount of returned events
186+ :type num_events: unsigned integer
187+ :param order: unsigned integer representing
188+ a :class:`result type <zeitgeist.datamodel.ResultType>`
189+ :type order: unsigned integer
190+ :returns: Full event data for all the requested IDs, up to a maximum
191+ of *num_events* events, sorted and grouped as defined by the
192+ *result_type* parameter. The event data can be conveniently
193+ converted into a list of :class:`Event` instances by calling
194+ *events = map(Event.new_for_struct, result)*
195+ :rtype: A list of serialized events. DBus signature a(asaasay).
196+ """
197+ time_range = TimeRange(time_range[0], time_range[1])
198+ event_templates = map(Event, event_templates)
199+ return _engine.find_eventids(time_range, event_templates, storage_state,
200+ num_events, result_type)
201+
202+ @dbus.service.method(constants.DBUS_INTERFACE,
203+ in_signature="(xx)a("+constants.SIG_EVENT+")uuu",
204+ out_signature="a("+constants.SIG_EVENT+")")
205+ def FindEvents(self, time_range, event_templates, storage_state,
206+ num_events, result_type):
207+ """Get events matching a given set of templates.
208+
209+ The matching is done where unset fields in the templates
210+ are treated as wildcards. If a template has more than one
211+ subject then events will match the template if any one of their
212+ subjects match any one of the subject templates.
213+
214+ In case you need to do a query yielding a large (or unpredictable)
215+ result set and you only want to show some of the results at the
216+ same time (eg., by paging them), use :meth:`FindEventIds`.
217+
218+ :param time_range: two timestamps defining the timerange for
219+ the query. When using the Python bindings for Zeitgeist you
220+ may pass a :class:`TimeRange <zeitgeist.datamodel.TimeRange>`
221+ instance directly to this method
222+ :type time_range: tuple of 64 bit integers. DBus signature (xx)
223+ :param event_templates: An array of event templates which the
224+ returned events should match at least one of.
225+ When using the Python bindings for Zeitgeist you may pass
226+ a list of :class:`Event <zeitgeist.datamodel.Event>`
227+ instances directly to this method.
228+ :type event_templates: array of events. DBus signature a(asaasay)
229+ :param storage_state: whether the item is currently known to be
230+ available. The list of possible values is enumerated in
231+ :class:`StorageState <zeitgeist.datamodel.StorageState>` class
232+ :type storage_state: unsigned integer
233+ :param num_events: maximal amount of returned events
234+ :type num_events: unsigned integer
235+ :param order: unsigned integer representing
236+ a :class:`result type <zeitgeist.datamodel.ResultType>`
237+ :type order: unsigned integer
238+ :returns: An array containing the IDs of all matching events,
239 up to a maximum of *num_events* events. Sorted and grouped
240 as defined by the *result_type* parameter.
241 :rtype: Array of unsigned 32 bit integers
242 """
243 time_range = TimeRange(time_range[0], time_range[1])
244 event_templates = map(Event, event_templates)
245- return _engine.find_eventids(time_range, event_templates, storage_state, num_events, result_type)
246-
247- @dbus.service.method(constants.DBUS_INTERFACE,
248- in_signature="(xx)a("+constants.SIG_EVENT+")uuu", out_signature="au")
249- def FindEvents(self, time_range, event_templates, storage_state,
250- num_events, result_type):
251- """Shorthand for GetEvents(FindEventIds()).
252-
253- Using this function is more efficient, if you don't need to deal with
254- large amounts of data in a paginated UI.
255- """
256- time_range = TimeRange(time_range[0], time_range[1])
257- event_templates = map(Event, event_templates)
258- return _engine.find_eventids(time_range, event_templates, storage_state,
259- num_events, result_type, return_events=True)
260+ return self._make_events_sendable(_engine.find_events(time_range,
261+ event_templates, storage_state, num_events, result_type))
262
263 # Writing stuff
264
265 @dbus.service.method(constants.DBUS_INTERFACE,
266 in_signature="a("+constants.SIG_EVENT+")", out_signature="au")
267 def InsertEvents(self, events):
268- """Inserts events into the log. Returns an array containing the ids of the inserted events
269+ """Inserts events into the log. Returns an array containing the IDs
270+ of the inserted events
271
272 Each event which failed to be inserted into the log (either by
273 being blocked or because of an error) will be represented by `0`
274@@ -197,9 +252,9 @@
275 If you are using the Python bindings you may pass
276 :class:`Event <zeitgeist.datamodel.Event>` instances
277 directly to this method
278- :returns: An array containing the event ids of the inserted
279+ :returns: An array containing the event IDs of the inserted
280 events. In case any of the events where already logged,
281- the id of the existing event will be returned. `0` as id
282+ the ID of the existing event will be returned. `0` as ID
283 indicates a failed insert into the log.
284 :rtype: Array of unsigned 32 bits integers. DBus signature au.
285 """
286@@ -283,7 +338,9 @@
287 @dbus.service.method(constants.DBUS_INTERFACE,
288 in_signature="o(xx)a("+constants.SIG_EVENT+")", sender_keyword="owner")
289 def InstallMonitor(self, monitor_path, time_range, event_templates, owner=None):
290- """Register a client side monitor object to receive callbacks when events matching *time_range* and *event_templates* are inserted or deleted.
291+ """Register a client side monitor object to receive callbacks when
292+ events matching *time_range* and *event_templates* are inserted or
293+ deleted.
294
295 The monitor object must implement the interface :ref:`org.gnome.zeitgeist.Monitor <org_gnome_zeitgeist_Monitor>`
296
297
298=== modified file 'test/engine-test.py'
299--- test/engine-test.py 2010-01-12 17:12:47 +0000
300+++ test/engine-test.py 2010-01-12 21:55:18 +0000
301@@ -273,8 +273,8 @@
302 import_events("test/data/five_events.js", self.engine)
303 event_template = Event.new_for_values(interpretation="stfu:OpenEvent",
304 subjects=[Subject()])
305- events = self.engine.find_eventids((0, 0), [event_template],
306- StorageState.Any, 0, 1, return_events=True)
307+ events = self.engine.find_events((0, 0), [event_template],
308+ StorageState.Any, 0, 1)
309 self.assertEquals(2, len(events))
310 for event in events:
311 self.assertEqual(event.interpretation, "stfu:OpenEvent")
312
313=== modified file 'zeitgeist/client.py'
314--- zeitgeist/client.py 2010-01-10 21:10:16 +0000
315+++ zeitgeist/client.py 2010-01-12 21:55:18 +0000
316@@ -395,6 +395,16 @@
317
318 The actual :class:`Events` can be looked up via the
319 :meth:`get_events()` method.
320+
321+ This method is intended for queries potentially returning a
322+ large result set. It is especially useful in cases where only
323+ a portion of the results are to be displayed at the same time
324+ (eg., by using paging or dynamic scrollbars), as by holding a
325+ list of IDs you keep a stable ordering, and you can ask for
326+ the details associated to them in hunks, when you need them. For
327+ queries with a small amount of results, or where you need the
328+ information about all results at once no matter how many of them
329+ there are, see :meth:`find_events_for_templates`.
330
331 In case of errors a message will be printed on stderr, and
332 an empty result passed to ids_reply_handler.
333@@ -444,28 +454,8 @@
334 def find_event_ids_for_template (self, event_template, ids_reply_handler,
335 **kwargs):
336 """
337- Send a query matching a single Event-template to the
338- Zeitgeist event log. If the event template has more
339- than one subject the query will match if any one of the subject
340- templates match.
341-
342- The query will be done via an asynchronous DBus call and
343- this method will return immediately. The return value
344- will be passed to 'ids_reply_handler' as a list
345- of integer event ids. This list must be the sole argument for
346- the callback.
347-
348- The actual :class:`Events` can be looked up via the
349- :meth:`get_events` method.
350-
351- In case of errors a message will be printed on stderr, and
352- an empty result passed to *ids_reply_handler*.
353- To override this default set the *error_handler* named argument
354- to a callable that takes a single exception as its sole
355- argument.
356-
357- In order to use this method there needs to be a mainloop
358- runnning. Both Qt and GLib mainloops are supported.
359+ Alias for :meth:`find_event_ids_for_templates`, for use when only
360+ one template is needed.
361 """
362 self.find_event_ids_for_templates([event_template],
363 ids_reply_handler,
364@@ -473,28 +463,12 @@
365
366 def find_event_ids_for_values(self, ids_reply_handler, **kwargs):
367 """
368- Send a query for events matching the keyword arguments passed
369- to this function. The allowed keywords are the same as the ones
370- allowed by
371+ Alias for :meth:`find_event_ids_for_templates`, for when only
372+ one template is needed. Instead of taking an already created
373+ template, like :meth:`find_event_ids_for_template`, this method
374+ will construct the template from the parameters it gets. The
375+ allowed keywords are the same as the ones allowed by
376 :meth:`Event.new_for_values() <zeitgeist.datamodel.Event.new_for_values>`.
377-
378- The query will be done via an asynchronous DBus call and
379- this method will return immediately. The return value
380- will be passed to *ids_reply_handler* as a list
381- of integer event ids. This list must be the sole argument for
382- the callback.
383-
384- The actual :class:`Events` can be looked up via the
385- :meth:`get_events` method.
386-
387- In case of errors a message will be printed on stderr, and
388- an empty result passed to *ids_reply_handler*.
389- To override this default set the *error_handler* named argument
390- to a callable that takes a single exception as its sole
391- argument.
392-
393- In order to use this method there needs to be a mainloop
394- runnning. Both Qt and GLib mainloops are supported.
395 """
396 ev = Event.new_for_values(**kwargs)
397
398@@ -502,6 +476,104 @@
399 ids_reply_handler,
400 **kwargs)
401
402+ def find_events_for_templates (self,
403+ event_templates,
404+ events_reply_handler,
405+ timerange = None,
406+ storage_state = StorageState.Any,
407+ num_events = 20,
408+ result_type = ResultType.MostRecentEvents,
409+ error_handler=None):
410+ """
411+ Send a query matching a collection of
412+ :class:`Event <zeitgeist.datamodel.Event>` templates to the
413+ Zeitgeist event log. The query will match if an event matches
414+ any of the templates. If an event template has more
415+ than one subject the query will match if any one of the subject
416+ templates match.
417+
418+ The query will be done via an asynchronous DBus call and
419+ this method will return immediately. The return value
420+ will be passed to 'events_reply_handler' as a list
421+ of :class:`Event`s. This list must be the sole argument for
422+ the callback.
423+
424+ If you need to do a query yielding a large (or unpredictable)
425+ result set and you only want to show some of the results at the
426+ same time (eg., by paging them), consider using
427+ :meth:`find_event_ids_for_templates`.
428+
429+ In case of errors a message will be printed on stderr, and
430+ an empty result passed to events_reply_handler.
431+ To override this default set the error_handler named argument
432+ to a callable that takes a single exception as its sole
433+ argument.
434+
435+ In order to use this method there needs to be a mainloop
436+ runnning. Both Qt and GLib mainloops are supported.
437+
438+ :param event_templates: List or tuple of
439+ :class:`Event <zeitgeist.datamodel.Event>` instances
440+ :param events_reply_handler: Callable taking a list of integers
441+ :param timerange: A
442+ :class:`TimeRange <zeitgeist.datamodel.TimeRange>` instance
443+ that the events must have occured within. Defaults to
444+ :meth:`TimeRange.until_now()`.
445+ :param storage_state: A value from the
446+ :class:`StorageState <zeitgeist.datamodel.StorageState>`
447+ enumeration. Defaults to :const:`StorageState.Any`
448+ :param num_events: The number of events to return; default is 20
449+ :param result_type: A value from the
450+ :class:`ResultType <zeitgeist.datamodel.ResultType>`
451+ enumeration. Defaults to ResultType.MostRecentEvent
452+ :param error_handler: Callback to catch error messages.
453+ Read about the default behaviour above
454+ """
455+ self._check_list_or_tuple(event_templates)
456+ self._check_members(event_templates, Event)
457+
458+ if not callable(events_reply_handler):
459+ raise TypeError(
460+ "Reply handler not callable, found %s" % events_reply_handler)
461+
462+ if timerange is None:
463+ timerange = TimeRange.until_now()
464+
465+ self._iface.FindEvents(timerange,
466+ event_templates,
467+ storage_state,
468+ num_events,
469+ result_type,
470+ reply_handler=lambda raw: events_reply_handler(
471+ map(Event.new_for_struct, raw)),
472+ error_handler=self._safe_error_handler(error_handler,
473+ events_reply_handler, []))
474+
475+ def find_events_for_template (self, event_template, events_reply_handler,
476+ **kwargs):
477+ """
478+ Alias for :meth:`find_events_for_templates`, for use when only
479+ one template is needed.
480+ """
481+ self.find_event_ids_for_templates([event_template],
482+ events_reply_handler,
483+ **kwargs)
484+
485+ def find_events_for_values(self, events_reply_handler, **kwargs):
486+ """
487+ Alias for :meth:`find_events_for_templates`, for when only
488+ one template is needed. Instead of taking an already created
489+ template, like :meth:`find_event_ids_for_template`, this method
490+ will construct the template from the parameters it gets. The
491+ allowed keywords are the same as the ones allowed by
492+ :meth:`Event.new_for_values() <zeitgeist.datamodel.Event.new_for_values>`.
493+ """
494+ ev = Event.new_for_values(**kwargs)
495+
496+ self.find_events_for_templates([ev],
497+ events_reply_handler,
498+ **kwargs)
499+
500 def get_events (self, event_ids, events_reply_handler, error_handler=None):
501 """
502 Look up a collection of :class:`Events <zeitgeist.datamodel.Event>`

Subscribers

People subscribed via source and target branches