1 Return-Path: <aperez@igalia.com>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id D052E431FBC
\r
6 for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 02:51:18 -0800 (PST)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
8 Received: from olra.theworths.org ([127.0.0.1])
\r
9 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
10 with ESMTP id qAbyH4mrVSfT for <notmuch@notmuchmail.org>;
\r
11 Wed, 30 Dec 2009 02:51:16 -0800 (PST)
\r
12 Received: from alice.connectical.com (alice.connectical.com [208.89.208.235])
\r
13 by olra.theworths.org (Postfix) with ESMTP id A2D76431FAE
\r
14 for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 02:51:16 -0800 (PST)
\r
15 Received: (qmail 20357 invoked from network); 30 Dec 2009 10:51:07 -0000
\r
16 Received: from 97.126.60.213.dynamic.mundo-r.com (HELO hikari.localdomain)
\r
17 (aperez@213.60.126.97)
\r
18 by alice.connectical.com with ESMTPA; 30 Dec 2009 10:51:07 -0000
\r
19 Received: from hikari (localhost [127.0.0.1])
\r
20 by hikari.localdomain (Postfix) with ESMTP id 8360330A5D47
\r
21 for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 11:52:28 +0100 (CET)
\r
22 Date: Wed, 30 Dec 2009 11:52:23 +0100
\r
23 From: Adrian Perez de Castro <aperez@igalia.com>
\r
24 To: notmuch@notmuchmail.org
\r
25 Message-ID: <20091230115223.1b3472a1@hikari>
\r
26 In-Reply-To: <1262078148-sup-7891@ben-laptop>
\r
27 References: <1262078148-sup-7891@ben-laptop>
\r
28 Organization: Igalia
\r
29 X-Mailer: Claws Mail 3.7.3 (GTK+ 2.19.2; x86_64-redhat-linux-gnu)
\r
30 Face: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAAXNSR0IArs4c6QAAADBQTFRFBwcHFhYWKCgoNzc3SEhIV1dXaGhod3d3iIiIlpaWqKiouLi4x8fH2NjY5+fn/v7+rSjDkgAAAjVJREFUOE9l07tvE0EQwOHfrkV9O+eko7g701BBfECJsIigT2IpooIqaSiRUEB0REj00FBQgYSCkhry+gecUPJybJeIxLumTbilsH2PMNXufDOa3ZVW+1JkpbUmD/8+vXR3c7or4Gz93mH309Kz8/C9/RQge7VfhW/LW+PF8IkrQ7Z6OKmQr1tl+LU/yWP9mxJka9O88fZHPwf/7u0kLyCnX3I4fQhgjAgIfi+HHw5A1Y2ggIMcFKAEnRoL0M3BosI4TI2IATjuT8DvSNJoNNJgkIhxlr9TUHeSpDnfohlIrMBlU+BGmsZqfr69FMfGMw4NoG835+J62riWyjQ/uXlTQjNUIoYegMsBM0pCD8oDas7n4HQsBghXFxJTW42KDs+4XLfjsN0wOYgABqARjMKIHIaAQnmHjsI5Cvi9Cf6k03OoWBkpIP3Q7354+dEimFBKHbMP9oKjwfd9gbrxR5KDToczK4uPF8UgNomKU2GaENRi77zyDKICxKBS4xXYbONPMQMdYZTBwMiMWiUg9g6UJ3OBogzjV8E7sBVwyvfAOYdQhsABzuOxI1MGZbs98Q6Md5UOfbbR2R0eWOesrnRw5ajT6f60LrNhWIHZpBnUWv2s14ukArWWTqTes3YQxRXgFkcMu70TPYqqUBs0YwmO967OVIdTG4bY4a7WLaqgLm5vbHdH5np0Dri//fmg7y8scB4u3+zsuNlH0X+g19bby69b+TYH6isvns8VdQWgxj9tHP8AR5/hSdYqkwsAAAAASUVORK5CYII=
\r
32 Content-Type: multipart/signed; micalg=PGP-SHA1;
\r
33 boundary="Sig_/NJQn5hRW+avHgHHAlU7=Xa1";
\r
34 protocol="application/pgp-signature"
\r
35 Subject: Re: [notmuch] SWIG (and particularly Python) bindings
\r
36 X-BeenThere: notmuch@notmuchmail.org
\r
37 X-Mailman-Version: 2.1.12
\r
39 List-Id: "Use and development of the notmuch mail system."
\r
40 <notmuch.notmuchmail.org>
\r
41 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
42 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
43 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
44 List-Post: <mailto:notmuch@notmuchmail.org>
\r
45 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
46 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
47 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
48 X-List-Received-Date: Wed, 30 Dec 2009 10:51:19 -0000
\r
50 --Sig_/NJQn5hRW+avHgHHAlU7=Xa1
\r
51 Content-Type: multipart/mixed; boundary="MP_/Qt51GuWb3Bo1f8da6MLGH4N"
\r
53 --MP_/Qt51GuWb3Bo1f8da6MLGH4N
\r
54 Content-Type: text/plain; charset=US-ASCII
\r
55 Content-Transfer-Encoding: quoted-printable
\r
56 Content-Disposition: inline
\r
58 On Tue, 29 Dec 2009 04:16:43 -0500, Ben wrote:
\r
60 > Regardless, I thought it might be nice to have access to the notmuch
\r
61 > backend from a language other than C (preferably my high-level language
\r
62 > of choice, python) [...]
\r
64 Funny, I was just doing the same: a Python binding. Haha, so now we have
\r
65 two just-backed Python bindings. What should we do?
\r
67 > [...] To this end, I took a few hours today acquainting
\r
68 > myself with SWIG and produced these bindings for Python. Unfortunately,
\r
69 > it doesn't appear that SWIG has particularly good support for
\r
70 > object-oriented C [...]
\r
72 I already used SWIG sometimes in the past (and did not like it a lot), so
\r
73 my binding is using Cython [*] (which is exactly like Pyrex plus some extra
\r
74 features), so the binding is partly manual.
\r
76 > While the bindings are currently in the form of a patch to notmuch
\r
77 > (creating a top-level swig directory in the source tree), they could
\r
78 > certainly be moved out-of-tree if the powers that be don't feel it
\r
79 > appropriate to include them. [...]
\r
81 Same here, see attached patch. It is currently unfinished, and I was just
\r
82 about to add support for iterating notmuch_threads_t and other similar
\r
83 structures. I can also publish a Git repo with the entire branch, just
\r
84 drop me a line if you want me to do that.
\r
86 > [...] Unfortunately, the build system is currently almost entirely
\r
87 > independent from the notmuch build system. If these are to be
\r
88 > included in-tree, I would be curious to hear people have
\r
89 > to say about how we might integrate it into the sup build system.
\r
91 (Mmmh, I suppose you mean "notmuch build system" there :P)
\r
93 Mine is a little more cooked, as I have added a distutils "setup.py"
\r
94 script. The bad news is that Python modules need to be compiled as
\r
95 relocatable object files (-fPIC to teh rescue!), and the linker will
\r
96 refuse to link the generated code with "notmuch.a" -- so I am instructing
\r
97 distutils to compile *all* sources again. Not nice.
\r
99 BTW, I think that if more bindings start to appear, Notmuch might be built
\r
100 as a shared library, to avoid duplicating it everywhere. One option may be
\r
101 using *just* libtool but not the rest of auto-foo tools (for the record:
\r
102 I agree with Carl that they are slow and wicked).
\r
107 [*] http://www.cython.org/
\r
109 Adrian Perez de Castro <aperez@igalia.com>
\r
110 Igalia - Free Software Engineering
\r
112 --MP_/Qt51GuWb3Bo1f8da6MLGH4N
\r
113 Content-Type: text/x-patch
\r
114 Content-Transfer-Encoding: quoted-printable
\r
115 Content-Disposition: attachment; filename=notmuch-python-wip.patch
\r
118 python/.gitignore | 2 +
\r
119 python/Makefile | 6 +
\r
120 python/Makefile.local | 15 ++
\r
121 python/notmuch.pyx | 397 +++++++++++++++++++++++++++++++++++++++++++++=
\r
123 python/pyutil.h | 16 ++
\r
124 python/setup.py | 89 +++++++++++
\r
125 7 files changed, 526 insertions(+), 0 deletions(-)
\r
127 diff --git a/Makefile b/Makefile
\r
128 index 021fdb8..081d670 100644
\r
131 @@ -37,6 +37,7 @@ include Makefile.config
\r
133 include lib/Makefile.local
\r
134 include compat/Makefile.local
\r
135 +include python/Makefile.local
\r
136 include Makefile.local
\r
138 # The user has not set any verbosity, default to quiet mode and inform the
\r
139 diff --git a/python/.gitignore b/python/.gitignore
\r
140 new file mode 100644
\r
141 index 0000000..7f0efa8
\r
143 +++ b/python/.gitignore
\r
147 diff --git a/python/Makefile b/python/Makefile
\r
148 new file mode 100644
\r
149 index 0000000..e1e5c43
\r
151 +++ b/python/Makefile
\r
159 diff --git a/python/Makefile.local b/python/Makefile.local
\r
160 new file mode 100644
\r
161 index 0000000..140a701
\r
163 +++ b/python/Makefile.local
\r
167 +python: $(dir)/build/.stamp
\r
168 + (cd $(dir) && python setup.py build)
\r
171 +$(dir)/build/.stamp: lib/notmuch.a
\r
173 +clean: clean-python
\r
176 + $(RM) -r $(dir)/build
\r
178 +.PHONY: clean-python python
\r
180 diff --git a/python/notmuch.pyx b/python/notmuch.pyx
\r
181 new file mode 100644
\r
182 index 0000000..f38b719
\r
184 +++ b/python/notmuch.pyx
\r
186 +#! /usr/bin/env python
\r
187 +# -*- coding: utf-8 -*-
\r
188 +# vim: fenc=3Dutf-8 ft=3Dpyrex
\r
190 +# Copyright =C2=A9 2009 Adrian Perez <aperez@igalia.com>
\r
192 +# Distributed under terms of the GPLv3 license.
\r
195 +cdef extern from "talloc.h":
\r
196 + void* talloc_init(char *fmt, ...)
\r
197 + int talloc_free(void *ctx)
\r
200 +cdef extern from "pyutil.h":
\r
204 + char** pyutil_alloc_strv(void *ctx, unsigned nitems)
\r
207 +cdef extern from "notmuch.h":
\r
209 + # Return status handling
\r
211 + ctypedef enum notmuch_status_t:
\r
212 + NOTMUCH_STATUS_SUCCESS
\r
213 + NOTMUCH_STATUS_OUT_OF_MEMORY
\r
214 + NOTMUCH_STATUS_READONLY_DATABASE
\r
215 + NOTMUCH_STATUS_XAPIAN_EXCEPTION
\r
216 + NOTMUCH_STATUS_FILE_ERROR
\r
217 + NOTMUCH_STATUS_FILE_NOT_EMAIL
\r
218 + NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID
\r
219 + NOTMUCH_STATUS_NULL_POINTER
\r
220 + NOTMUCH_STATUS_TAG_TOO_LONG
\r
221 + NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW
\r
223 + char* notmuch_status_to_string(notmuch_status_t status)
\r
226 + # notmuch_database_* -> notmuch.Database
\r
228 + ctypedef enum notmuch_database_mode_t:
\r
229 + NOTMUCH_DATABASE_MODE_READ_ONLY
\r
230 + NOTMUCH_DATABASE_MODE_READ_WRITE
\r
232 + ctypedef enum notmuch_sort_t:
\r
233 + NOTMUCH_SORT_OLDEST_FIRST
\r
234 + NOTMUCH_SORT_NEWEST_FIRST
\r
235 + NOTMUCH_SORT_MESSAGE_ID
\r
237 + ctypedef struct notmuch_database_t
\r
238 + ctypedef struct notmuch_messages_t
\r
239 + ctypedef struct notmuch_message_t
\r
240 + ctypedef struct notmuch_threads_t
\r
241 + ctypedef struct notmuch_thread_t
\r
242 + ctypedef struct notmuch_query_t
\r
243 + ctypedef struct notmuch_tags_t
\r
244 + ctypedef int time_t
\r
246 + int notmuch_threads_has_more(notmuch_threads_t *threads)
\r
247 + void notmuch_threads_advance(notmuch_threads_t *threads)
\r
248 + void notmuch_threads_destroy(notmuch_threads_t *threads)
\r
249 + notmuch_thread_t* notmuch_threads_get(notmuch_threads_t *threads)
\r
251 + void notmuch_thread_destroy(notmuch_thread_t *thread)
\r
252 + char* notmuch_thread_get_authors(notmuch_thread_t *thread)
\r
253 + char* notmuch_thread_get_subject(notmuch_thread_t *thread)
\r
254 + char* notmuch_thread_get_thread_id(notmuch_thread_t *thread)
\r
255 + time_t notmuch_thread_get_oldest_date(notmuch_thread_t *thread)
\r
256 + time_t notmuch_thread_get_newest_date(notmuch_thread_t *thread)
\r
257 + int notmuch_thread_get_total_messages(notmuch_thread_t *thread)
\r
258 + int notmuch_thread_get_matched_messages(notmuch_thread_t *thread)
\r
259 + notmuch_tags_t* notmuch_thread_get_tags(notmuch_thread_t *thread)
\r
260 + notmuch_messages_t* notmuch_thread_get_toplevel_messages(notmuch_thread_t=
\r
263 + notmuch_database_t* notmuch_database_create(char *path)
\r
264 + notmuch_database_t* notmuch_database_open(char *path, notmuch_database_mo=
\r
266 + char* notmuch_database_get_path(notmuch_database_t *db)
\r
267 + void notmuch_database_close(notmuch_database_t *db)
\r
269 + time_t notmuch_database_get_timestamp(notmuch_database_t *db, char *key)
\r
270 + notmuch_status_t notmuch_database_set_timestamp(
\r
271 + notmuch_database_t *db, char *key, time_t timestamp)
\r
273 + notmuch_status_t notmuch_database_add_message(
\r
274 + notmuch_database_t *db, char *filename, notmuch_message_t **mesage)
\r
276 + notmuch_message_t* notmuch_database_find_message(
\r
277 + notmuch_database_t *db, char *message_id)
\r
279 + notmuch_tags_t* notmuch_database_get_all_tags(notmuch_database_t *db)
\r
281 + notmuch_query_t* notmuch_query_create(notmuch_database_t *db, char *qstri=
\r
284 + void notmuch_query_destroy(notmuch_query_t *query)
\r
285 + void notmuch_query_set_sort(notmuch_query_t *query, notmuch_sort_t sort)
\r
286 + unsigned notmuch_query_count_messages(notmuch_query_t *query)
\r
287 + notmuch_threads_t* notmuch_query_search_threads(notmuch_query_t *query)
\r
288 + notmuch_messages_t* notmuch_query_search_messages(notmuch_query_t *query)
\r
290 + char* notmuch_message_get_message_id(notmuch_message_t *msg)
\r
291 + char* notmuch_message_get_thread_id(notmuch_message_t *msg)
\r
292 + char* notmuch_message_get_filename(notmuch_message_t *msg)
\r
293 + char* notmuch_message_get_header(notmuch_message_t *msg, char *name)
\r
294 + notmuch_status_t notmuch_message_add_tag(notmuch_message_t *msg, char *ta=
\r
296 + notmuch_status_t notmuch_message_remove_tag(notmuch_message_t *msg, char =
\r
298 + void notmuch_message_remove_all_tags(notmuch_message_t *msg)
\r
299 + void notmuch_message_destroy(notmuch_message_t *msg)
\r
300 + void notmuch_message_freeze(notmuch_message_t *msg)
\r
301 + void notmuch_message_thaw(notmuch_message_t *msg)
\r
304 +cdef extern from "notmuch-client.h":
\r
306 + # notmuch_config_* -> notmuch.Config
\r
308 + ctypedef struct notmuch_config_t
\r
310 + notmuch_config_t* notmuch_config_open(void *ctx, char *filename, int *is_=
\r
312 + void notmuch_config_close(notmuch_config_t *cfg)
\r
313 + int notmuch_config_save(notmuch_config_t *cfg)
\r
314 + char* notmuch_config_get_database_path(notmuch_config_t *cfg)
\r
315 + void notmuch_config_set_database_path(notmuch_config_t *cfg, char *path)
\r
316 + char* notmuch_config_get_user_name(notmuch_config_t *cfg)
\r
317 + void notmuch_config_set_user_name(notmuch_config_t *cfg, char *name)
\r
318 + char* notmuch_config_get_user_primary_email(notmuch_config_t *cfg)
\r
319 + void notmuch_config_set_user_primary_email(notmuch_config_t *cfg, char =
\r
321 + char** notmuch_config_get_user_other_email(notmuch_config_t *cfg, size_t =
\r
323 + void notmuch_config_set_user_other_email(notmuch_config_t *cfg, char **=
\r
324 other_email, size_t length)
\r
329 + int debugger_is_active()
\r
333 +# Import needed Python built-in modules
\r
335 +from datetime import datetime, date
\r
338 +# Miscellaneous functions and information
\r
340 +debugger_active =3D bool(debugger_is_active())
\r
344 +# notmuch_database_* -> notmuch.Database
\r
349 +# notmuch_config_* -> notmuch.Config
\r
351 +cdef class Config:
\r
352 + """Handles the Notmuch configuration file.
\r
354 + cdef notmuch_config_t * _cfg
\r
356 + cdef object _filename
\r
358 + def __init__(self, filename=3D"~/.notmuch-config"):
\r
359 + """Open a Notmuch configuration file.
\r
362 + self._ctx =3D talloc_init("notmuch.Config")
\r
363 + self._cfg =3D notmuch_config_open(self._ctx, filename, &newret)
\r
364 + self._filename =3D filename
\r
366 + def __dealloc__(self):
\r
367 + notmuch_config_close(self._cfg)
\r
368 + talloc_free(self._ctx)
\r
370 + property filename:
\r
371 + """File name containing the configuration (string)"""
\r
372 + def __get__(self):
\r
373 + return self._filename
\r
375 + property database_path:
\r
376 + """Path to the Notmuch database (string)"""
\r
377 + def __get__(self):
\r
378 + return notmuch_config_get_database_path(self._cfg)
\r
379 + def __set__(self, path):
\r
380 + notmuch_config_set_database_path(self._cfg, path)
\r
382 + property user_name:
\r
383 + """User name (string)"""
\r
384 + def __get__(self):
\r
385 + return notmuch_config_get_user_name(self._cfg)
\r
386 + def __set__(self, name):
\r
387 + notmuch_config_set_user_name(self._cfg, name)
\r
389 + property user_primary_email:
\r
390 + """Primary e-mail address of the user (string)"""
\r
391 + def __get__(self):
\r
392 + return notmuch_config_get_user_primary_email(self._cfg)
\r
393 + def __set__(self, email):
\r
394 + notmuch_config_set_user_primary_email(self._cfg, email)
\r
396 + property other_email:
\r
397 + """List of other e-mail addresses of the user (tuple of strings)"""
\r
398 + def __get__(self):
\r
399 + cdef size_t length
\r
401 + cdef char **emails
\r
402 + emails =3D notmuch_config_get_user_other_email(self._cfg, &length)
\r
405 + for i from 0 <=3D i < length:
\r
406 + result.append(emails[i])
\r
408 + # XXX We do not want the result to be modifiable, because the property
\r
409 + # must be assigned as a whole, and not just modified only in the
\r
410 + # Python side of the world.
\r
411 + return tuple(result)
\r
413 + def __set__(self, emaillist):
\r
414 + cdef size_t length =3D len(emaillist)
\r
415 + cdef char **emails =3D pyutil_alloc_strv(self._ctx, length)
\r
418 + for i from 0 <=3D i < len(emaillist):
\r
419 + emails[i] =3D emaillist[i]
\r
420 + notmuch_config_set_user_other_email(self._cfg, emails, len(emaillist))
\r
424 + """Save the Notmuch configuration"""
\r
425 + notmuch_config_save(self._cfg)
\r
428 +cdef class Database
\r
431 +cdef class Message:
\r
432 + cdef notmuch_message_t *_msg
\r
434 + def __cinit__(self, object messageptr):
\r
435 + # XXX Counterpart of bogus cast
\r
436 + self._msg =3D <notmuch_message_t*> messageptr
\r
438 + def __dealloc__(self):
\r
439 + notmuch_message_destroy(self._msg)
\r
441 + property message_id:
\r
442 + def __get__(self):
\r
443 + return notmuch_message_get_message_id(self._msg)
\r
445 + property thread_id:
\r
446 + def __get__(self):
\r
447 + return notmuch_message_get_thread_id(self._msg)
\r
449 + property filename:
\r
450 + def __get__(self):
\r
451 + return notmuch_message_get_filename(self._msg)
\r
453 + def get_header(self, name):
\r
454 + return notmuch_message_get_header(self._msg, name)
\r
456 + def add_tag(self, tag):
\r
457 + cdef notmuch_status_t ret =3D notmuch_message_add_tag(self._msg, tag)
\r
458 + if ret !=3D NOTMUCH_STATUS_SUCCESS:
\r
459 + raise ValueError(notmuch_status_to_string(ret))
\r
461 + def remove_tag(self, tag):
\r
462 + cdef notmuch_status_t ret =3D notmuch_message_remove_tag(self._msg, tag)
\r
463 + if ret !=3D NOTMUCH_STATUS_SUCCESS:
\r
464 + raise ValueError(notmuch_status_to_string(ret))
\r
466 + def remove_all_tags(self):
\r
467 + notmuch_message_remove_all_tags(self._msg)
\r
469 + def freeze(self):
\r
470 + notmuch_message_freeze(self._msg)
\r
473 + notmuch_message_thaw(self._msg)
\r
476 +cdef class Thread:
\r
477 + cdef notmuch_thread_t *_thread
\r
479 + def __cinit__(self, object threadptr):
\r
480 + self._thread =3D <notmuch_thread_t*> threadptr
\r
482 + def __dealloc__(self):
\r
483 + notmuch_thread_destroy(self._thread)
\r
485 + property authors:
\r
486 + def __get__(self):
\r
487 + return notmuch_thread_get_authors(self._thread)
\r
489 + property subject:
\r
490 + def __get__(self):
\r
491 + return notmuch_thread_get_subject(self._thread)
\r
493 + property thread_id:
\r
494 + def __get__(self):
\r
495 + return notmuch_thread_get_thread_id(self._thread)
\r
497 + property oldest_date:
\r
498 + def __get__(self):
\r
499 + return datetime.fromtimestamp(notmuch_thread_get_oldest_date(self._thre=
\r
502 + property newest_date:
\r
503 + def __get__(self):
\r
504 + return datetime.fromtimestamp(notmuch_thread_get_newest_date(self._thre=
\r
507 + property total_messages:
\r
508 + def __get__(self):
\r
509 + return notmuch_thread_get_total_messages(self._thread)
\r
511 + property matched_messages:
\r
512 + def __get__(self):
\r
513 + return notmuch_thread_get_matched_messages(self._thread)
\r
515 + def __len__(self):
\r
516 + return self.matched_messages
\r
520 + cdef notmuch_query_t *_query
\r
522 + def __cinit__(self, Database db not None, qs):
\r
523 + self._query =3D notmuch_query_create(db._db, qs)
\r
525 + def __dealloc__(self):
\r
526 + notmuch_query_destroy(self._query)
\r
528 + def __len__(self):
\r
529 + return notmuch_query_count_messages(self._query)
\r
531 + def sort(self, notmuch_sort_t ordering):
\r
532 + notmuch_query_set_sort(self._query, ordering)
\r
537 +cdef class Database:
\r
538 + cdef notmuch_database_t *_db
\r
540 + def __init__(self, path=3DNone, readonly=3DTrue):
\r
541 + cdef notmuch_database_mode_t mode =3D NOTMUCH_DATABASE_MODE_READ_WRITE
\r
543 + path =3D Config().database_path
\r
545 + mode =3D NOTMUCH_DATABASE_MODE_READ_ONLY
\r
546 + self._db =3D notmuch_database_open(path, mode)
\r
548 + def __dealloc__(self):
\r
549 + notmuch_database_close(self._db)
\r
551 + def get_timestamp(self, key):
\r
552 + cdef time_t ts =3D notmuch_database_get_timestamp(self._db, key)
\r
553 + return datetime.fromtimestamp(ts)
\r
555 + def set_timestamp(self, key, timestamp):
\r
557 + if isinstance(timestamp, date):
\r
558 + ts =3D int(timestamp.strftime("%s"))
\r
559 + elif isinstance(timestamp, float):
\r
560 + ts =3D <time_t> timestamp
\r
561 + elif isinstance(timestamp, int):
\r
564 + raise ValueError("Numeric timestamp or datetime.date object expected")
\r
566 + def add_message(self, filename):
\r
567 + cdef notmuch_message_t *message =3D NULL
\r
568 + cdef notmuch_status_t status
\r
569 + status =3D notmuch_database_add_message(self._db, filename, &message)
\r
570 + if status =3D=3D NOTMUCH_STATUS_SUCCESS:
\r
571 + # XXX This cast seems bogus, it may work, though
\r
572 + return Message(<object> message)
\r
574 + if message !=3D NULL:
\r
575 + notmuch_message_destroy(message)
\r
576 + raise ValueError(notmuch_status_to_string(status))
\r
578 + def find_message(self, message_id):
\r
579 + cdef notmuch_message_t *message
\r
580 + message =3D notmuch_database_find_message(self._db, message_id)
\r
581 + if message =3D=3D NULL:
\r
582 + raise KeyError(message_id)
\r
583 + return Message(<object> message)
\r
586 + """Database path"""
\r
587 + def __get__(self):
\r
588 + return notmuch_database_get_path(self._db)
\r
590 + def query(self, qstring):
\r
591 + return Query(self, qstring)
\r
594 diff --git a/python/pyutil.h b/python/pyutil.h
\r
595 new file mode 100644
\r
596 index 0000000..64d93bf
\r
598 +++ b/python/pyutil.h
\r
602 + * Copyright (C) 2009 Adrian Perez <aperez@igalia.com>
\r
604 + * Distributed under terms of the GPLv3 license.
\r
607 +#ifndef __pyutil_h__
\r
608 +#define __pyutil_h__
\r
610 +#include <talloc.h>
\r
612 +#define pyutil_alloc_strv(_ctx, _n) talloc_array ((_ctx), char*, (_n))
\r
614 +#endif /* !__pyutil_h__ */
\r
616 diff --git a/python/setup.py b/python/setup.py
\r
617 new file mode 100644
\r
618 index 0000000..ffd43b2
\r
620 +++ b/python/setup.py
\r
622 +#! /usr/bin/env python
\r
623 +# -*- coding: utf-8 -*-
\r
624 +# vim:fenc=3Dutf-8
\r
626 +# Copyright =C2=A9 2009 Adrian Perez <aperez@igalia.com>
\r
628 +# Distributed under terms of the GPLv3 license.
\r
630 +from distutils.core import setup
\r
631 +from distutils.extension import Extension
\r
632 +from Cython.Distutils import build_ext
\r
636 +class FlagOMatic(dict):
\r
637 + _KEYS =3D ("extra_link_args", "include_dirs", "library_dirs", "librari=
\r
640 + def __init__(self, *arg, **kw):
\r
641 + super(FlagOMatic, self).__init__(*arg, **kw)
\r
642 + for key in self._KEYS:
\r
643 + self[key] =3D set(self.get(key, []))
\r
645 + extra_link_args =3D property(lambda self: self["extra_link_args"])
\r
646 + include_dirs =3D property(lambda self: self["include_dirs"])
\r
647 + library_dirs =3D property(lambda self: self["library_dirs"])
\r
648 + libraries =3D property(lambda self: self["libraries"])
\r
651 + "-I": "include_dirs",
\r
652 + "-L": "library_dirs",
\r
653 + "-l": "libraries",
\r
656 + def add_compiler_flags(self, text):
\r
657 + for token in text.split():
\r
658 + key =3D self._FLAG_MAP.get(token[:2], "extra_link_args")
\r
659 + if key =3D=3D "extra_link_args":
\r
660 + self.extra_link_args.add(token)
\r
662 + self[key].add(token[2:])
\r
664 + def add_pkgconfig_flags(self, *modules):
\r
665 + self.add_command_output_flags(
\r
666 + "pkg-config --libs --cflags %s" % " ".join(modules))
\r
668 + def add_command_output_flags(self, command):
\r
669 + self.add_compiler_flags(commands.getoutput(command))
\r
672 + def kwargs(self):
\r
673 + return dict((k, list(v)) for k, v in self.iteritems())
\r
676 +# Gather compiler flags
\r
678 +flags =3D FlagOMatic(
\r
679 + #extra_link_args =3D ("../lib/notmuch.a",),
\r
680 + include_dirs =3D ("..", "../lib", "../compat"))
\r
682 +flags.add_pkgconfig_flags("gmime-2.4", "talloc")
\r
683 +flags.add_command_output_flags("xapian-config --cxxflags --libs")
\r
685 +# We are building a extension module
\r
689 +srcs =3D ["notmuch.pyx"]
\r
691 + map(lambda s: "../"+s,
\r
692 + filter(lambda s: s.endswith(".c"),
\r
693 + os.listdir(".."))))
\r
695 + map(lambda s: "../lib/"+s,
\r
696 + filter(lambda s: s.endswith(".cc") or s.endswith(".c"),
\r
697 + os.listdir("../lib"))))
\r
699 +ext_modules =3D [Extension("notmuch", srcs, **flags.kwargs)]
\r
702 +# And now for the easy part :-)
\r
705 + name =3D "notmuch",
\r
706 + author =3D "Adrian Perez",
\r
707 + author_email =3D "aperez@igalia.com",
\r
708 + cmdclass =3D {"build_ext": build_ext},
\r
709 + ext_modules =3D ext_modules,
\r
713 --MP_/Qt51GuWb3Bo1f8da6MLGH4N--
\r
715 --Sig_/NJQn5hRW+avHgHHAlU7=Xa1
\r
716 Content-Type: application/pgp-signature; name=signature.asc
\r
717 Content-Disposition: attachment; filename=signature.asc
\r
719 -----BEGIN PGP SIGNATURE-----
\r
720 Version: GnuPG v2.0.13 (GNU/Linux)
\r
722 iEYEARECAAYFAks7MOsACgkQkcVZ2+TJEju5RACfQAjq3L+//wNVYgB+05f2OmI7
\r
723 9P4AmgKqBUP79jj4EMuuNit64u94aj3q
\r
725 -----END PGP SIGNATURE-----
\r
727 --Sig_/NJQn5hRW+avHgHHAlU7=Xa1--
\r