Re: [notmuch] SWIG (and particularly Python) bindings
authorAdrian Perez de Castro <aperez@igalia.com>
Wed, 30 Dec 2009 10:52:23 +0000 (11:52 +0100)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:35:56 +0000 (09:35 -0800)
44/93c255737b39b2606b46681b2c144ff3435b50 [new file with mode: 0644]

diff --git a/44/93c255737b39b2606b46681b2c144ff3435b50 b/44/93c255737b39b2606b46681b2c144ff3435b50
new file mode 100644 (file)
index 0000000..b74095c
--- /dev/null
@@ -0,0 +1,727 @@
+Return-Path: <aperez@igalia.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id D052E431FBC\r
+       for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 02:51:18 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id qAbyH4mrVSfT for <notmuch@notmuchmail.org>;\r
+       Wed, 30 Dec 2009 02:51:16 -0800 (PST)\r
+Received: from alice.connectical.com (alice.connectical.com [208.89.208.235])\r
+       by olra.theworths.org (Postfix) with ESMTP id A2D76431FAE\r
+       for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 02:51:16 -0800 (PST)\r
+Received: (qmail 20357 invoked from network); 30 Dec 2009 10:51:07 -0000\r
+Received: from 97.126.60.213.dynamic.mundo-r.com (HELO hikari.localdomain)\r
+       (aperez@213.60.126.97)\r
+       by alice.connectical.com with ESMTPA; 30 Dec 2009 10:51:07 -0000\r
+Received: from hikari (localhost [127.0.0.1])\r
+       by hikari.localdomain (Postfix) with ESMTP id 8360330A5D47\r
+       for <notmuch@notmuchmail.org>; Wed, 30 Dec 2009 11:52:28 +0100 (CET)\r
+Date: Wed, 30 Dec 2009 11:52:23 +0100\r
+From: Adrian Perez de Castro <aperez@igalia.com>\r
+To: notmuch@notmuchmail.org\r
+Message-ID: <20091230115223.1b3472a1@hikari>\r
+In-Reply-To: <1262078148-sup-7891@ben-laptop>\r
+References: <1262078148-sup-7891@ben-laptop>\r
+Organization: Igalia\r
+X-Mailer: Claws Mail 3.7.3 (GTK+ 2.19.2; x86_64-redhat-linux-gnu)\r
+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
+Mime-Version: 1.0\r
+Content-Type: multipart/signed; micalg=PGP-SHA1;\r
+       boundary="Sig_/NJQn5hRW+avHgHHAlU7=Xa1";\r
+       protocol="application/pgp-signature"\r
+Subject: Re: [notmuch] SWIG (and particularly Python) bindings\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.12\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Wed, 30 Dec 2009 10:51:19 -0000\r
+\r
+--Sig_/NJQn5hRW+avHgHHAlU7=Xa1\r
+Content-Type: multipart/mixed; boundary="MP_/Qt51GuWb3Bo1f8da6MLGH4N"\r
+\r
+--MP_/Qt51GuWb3Bo1f8da6MLGH4N\r
+Content-Type: text/plain; charset=US-ASCII\r
+Content-Transfer-Encoding: quoted-printable\r
+Content-Disposition: inline\r
+\r
+On Tue, 29 Dec 2009 04:16:43 -0500, Ben wrote:\r
+\r
+> Regardless, I thought it might be nice to have access to the notmuch\r
+> backend from a language other than C (preferably my high-level language\r
+> of choice, python) [...]\r
+\r
+Funny, I was just doing the same: a Python binding. Haha, so now we have\r
+two just-backed Python bindings. What should we do?\r
+\r
+> [...] To this end, I took a few hours today acquainting\r
+> myself with SWIG and produced these bindings for Python. Unfortunately,\r
+> it doesn't appear that SWIG has particularly good support for\r
+> object-oriented C [...]\r
+\r
+I already used SWIG sometimes in the past (and did not like it a lot), so\r
+my binding is using Cython [*] (which is exactly like Pyrex plus some extra\r
+features), so the binding is partly manual.\r
+\r
+> While the bindings are currently in the form of a patch to notmuch\r
+> (creating a top-level swig directory in the source tree), they could\r
+> certainly be moved out-of-tree if the powers that be don't feel it\r
+> appropriate to include them. [...]\r
+\r
+Same here, see attached patch. It is currently unfinished, and I was just\r
+about to add support for iterating notmuch_threads_t and other similar\r
+structures. I can also publish a Git repo with the entire branch, just\r
+drop me a line if you want me to do that.\r
+\r
+> [...] Unfortunately, the build system is currently almost entirely\r
+> independent from the notmuch build system. If  these are to be\r
+> included in-tree, I would be curious to hear people have\r
+> to say about how we might integrate it into the sup build system.\r
+                                                  ^^^\r
+(Mmmh, I suppose you mean "notmuch build system" there :P)\r
+\r
+Mine is a little more cooked, as I have added a distutils "setup.py"\r
+script. The bad news is that Python modules need to be compiled as\r
+relocatable object files (-fPIC to teh rescue!), and the linker will\r
+refuse to link the generated code with "notmuch.a" -- so I am instructing\r
+distutils to compile *all* sources again. Not nice.\r
+\r
+BTW, I think that if more bindings start to appear, Notmuch might be built\r
+as a shared library, to avoid duplicating it everywhere. One option may be\r
+using *just* libtool but not the rest of auto-foo tools (for the record:\r
+I agree with Carl that they are slow and wicked).\r
+\r
+Regards,\r
+\r
+\r
+[*] http://www.cython.org/\r
+--=20\r
+Adrian Perez de Castro <aperez@igalia.com>\r
+Igalia - Free Software Engineering\r
+\r
+--MP_/Qt51GuWb3Bo1f8da6MLGH4N\r
+Content-Type: text/x-patch\r
+Content-Transfer-Encoding: quoted-printable\r
+Content-Disposition: attachment; filename=notmuch-python-wip.patch\r
+\r
+ Makefile              |    1 +\r
+ python/.gitignore     |    2 +\r
+ python/Makefile       |    6 +\r
+ python/Makefile.local |   15 ++\r
+ python/notmuch.pyx    |  397 +++++++++++++++++++++++++++++++++++++++++++++=\r
+++++\r
+ python/pyutil.h       |   16 ++\r
+ python/setup.py       |   89 +++++++++++\r
+ 7 files changed, 526 insertions(+), 0 deletions(-)\r
+\r
+diff --git a/Makefile b/Makefile\r
+index 021fdb8..081d670 100644\r
+--- a/Makefile\r
++++ b/Makefile\r
+@@ -37,6 +37,7 @@ include Makefile.config\r
+=20\r
+ include lib/Makefile.local\r
+ include compat/Makefile.local\r
++include python/Makefile.local\r
+ include Makefile.local\r
+=20\r
+ # The user has not set any verbosity, default to quiet mode and inform the\r
+diff --git a/python/.gitignore b/python/.gitignore\r
+new file mode 100644\r
+index 0000000..7f0efa8\r
+--- /dev/null\r
++++ b/python/.gitignore\r
+@@ -0,0 +1,2 @@\r
++notmuch.c\r
++build/\r
+diff --git a/python/Makefile b/python/Makefile\r
+new file mode 100644\r
+index 0000000..e1e5c43\r
+--- /dev/null\r
++++ b/python/Makefile\r
+@@ -0,0 +1,6 @@\r
++\r
++all: python\r
++\r
++%:\r
++      make -C .. $@\r
++\r
+diff --git a/python/Makefile.local b/python/Makefile.local\r
+new file mode 100644\r
+index 0000000..140a701\r
+--- /dev/null\r
++++ b/python/Makefile.local\r
+@@ -0,0 +1,15 @@\r
++dir=3Dpython\r
++\r
++python: $(dir)/build/.stamp\r
++      (cd $(dir) && python setup.py build)\r
++      touch $@\r
++\r
++$(dir)/build/.stamp: lib/notmuch.a\r
++\r
++clean: clean-python\r
++\r
++clean-python:\r
++      $(RM) -r $(dir)/build\r
++\r
++.PHONY: clean-python python\r
++\r
+diff --git a/python/notmuch.pyx b/python/notmuch.pyx\r
+new file mode 100644\r
+index 0000000..f38b719\r
+--- /dev/null\r
++++ b/python/notmuch.pyx\r
+@@ -0,0 +1,397 @@\r
++#! /usr/bin/env python\r
++# -*- coding: utf-8 -*-\r
++# vim: fenc=3Dutf-8 ft=3Dpyrex\r
++#\r
++# Copyright =C2=A9 2009 Adrian Perez <aperez@igalia.com>\r
++#\r
++# Distributed under terms of the GPLv3 license.\r
++#\r
++\r
++cdef extern from "talloc.h":\r
++      void* talloc_init(char *fmt, ...)\r
++      int   talloc_free(void *ctx)\r
++\r
++\r
++cdef extern from "pyutil.h":\r
++      #\r
++      # Utility macros\r
++      #\r
++      char** pyutil_alloc_strv(void *ctx, unsigned nitems)\r
++\r
++\r
++cdef extern from "notmuch.h":\r
++      #\r
++      # Return status handling\r
++      #\r
++      ctypedef enum notmuch_status_t:\r
++              NOTMUCH_STATUS_SUCCESS\r
++              NOTMUCH_STATUS_OUT_OF_MEMORY\r
++              NOTMUCH_STATUS_READONLY_DATABASE\r
++              NOTMUCH_STATUS_XAPIAN_EXCEPTION\r
++              NOTMUCH_STATUS_FILE_ERROR\r
++              NOTMUCH_STATUS_FILE_NOT_EMAIL\r
++              NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID\r
++              NOTMUCH_STATUS_NULL_POINTER\r
++              NOTMUCH_STATUS_TAG_TOO_LONG\r
++              NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW\r
++\r
++      char* notmuch_status_to_string(notmuch_status_t status)\r
++\r
++      #\r
++      # notmuch_database_* -> notmuch.Database\r
++      #\r
++      ctypedef enum notmuch_database_mode_t:\r
++              NOTMUCH_DATABASE_MODE_READ_ONLY\r
++              NOTMUCH_DATABASE_MODE_READ_WRITE\r
++\r
++      ctypedef enum notmuch_sort_t:\r
++              NOTMUCH_SORT_OLDEST_FIRST\r
++              NOTMUCH_SORT_NEWEST_FIRST\r
++              NOTMUCH_SORT_MESSAGE_ID\r
++\r
++      ctypedef struct notmuch_database_t\r
++      ctypedef struct notmuch_messages_t\r
++      ctypedef struct notmuch_message_t\r
++      ctypedef struct notmuch_threads_t\r
++      ctypedef struct notmuch_thread_t\r
++      ctypedef struct notmuch_query_t\r
++      ctypedef struct notmuch_tags_t\r
++      ctypedef int time_t\r
++\r
++      int notmuch_threads_has_more(notmuch_threads_t *threads)\r
++      void notmuch_threads_advance(notmuch_threads_t *threads)\r
++      void notmuch_threads_destroy(notmuch_threads_t *threads)\r
++      notmuch_thread_t* notmuch_threads_get(notmuch_threads_t *threads)\r
++\r
++      void   notmuch_thread_destroy(notmuch_thread_t *thread)\r
++      char*  notmuch_thread_get_authors(notmuch_thread_t *thread)\r
++      char*  notmuch_thread_get_subject(notmuch_thread_t *thread)\r
++      char*  notmuch_thread_get_thread_id(notmuch_thread_t *thread)\r
++      time_t notmuch_thread_get_oldest_date(notmuch_thread_t *thread)\r
++      time_t notmuch_thread_get_newest_date(notmuch_thread_t *thread)\r
++      int    notmuch_thread_get_total_messages(notmuch_thread_t *thread)\r
++      int    notmuch_thread_get_matched_messages(notmuch_thread_t *thread)\r
++      notmuch_tags_t*     notmuch_thread_get_tags(notmuch_thread_t *thread)\r
++      notmuch_messages_t* notmuch_thread_get_toplevel_messages(notmuch_thread_t=\r
+ *thread)\r
++\r
++      notmuch_database_t* notmuch_database_create(char *path)\r
++      notmuch_database_t* notmuch_database_open(char *path, notmuch_database_mo=\r
+de_t mode)\r
++      char* notmuch_database_get_path(notmuch_database_t *db)\r
++      void  notmuch_database_close(notmuch_database_t *db)\r
++\r
++      time_t notmuch_database_get_timestamp(notmuch_database_t *db, char *key)\r
++      notmuch_status_t notmuch_database_set_timestamp(\r
++                      notmuch_database_t *db, char *key, time_t timestamp)\r
++\r
++      notmuch_status_t notmuch_database_add_message(\r
++                      notmuch_database_t *db, char *filename, notmuch_message_t **mesage)\r
++\r
++      notmuch_message_t* notmuch_database_find_message(\r
++                      notmuch_database_t *db, char *message_id)\r
++\r
++      notmuch_tags_t* notmuch_database_get_all_tags(notmuch_database_t *db)\r
++\r
++      notmuch_query_t* notmuch_query_create(notmuch_database_t *db, char *qstri=\r
+ng)\r
++\r
++      void notmuch_query_destroy(notmuch_query_t *query)\r
++      void notmuch_query_set_sort(notmuch_query_t *query, notmuch_sort_t sort)\r
++      unsigned notmuch_query_count_messages(notmuch_query_t *query)\r
++      notmuch_threads_t*  notmuch_query_search_threads(notmuch_query_t *query)\r
++      notmuch_messages_t* notmuch_query_search_messages(notmuch_query_t *query)\r
++\r
++      char* notmuch_message_get_message_id(notmuch_message_t *msg)\r
++      char* notmuch_message_get_thread_id(notmuch_message_t *msg)\r
++      char* notmuch_message_get_filename(notmuch_message_t *msg)\r
++      char* notmuch_message_get_header(notmuch_message_t *msg, char *name)\r
++      notmuch_status_t notmuch_message_add_tag(notmuch_message_t *msg, char *ta=\r
+g)\r
++      notmuch_status_t notmuch_message_remove_tag(notmuch_message_t *msg, char =\r
+*tag)\r
++      void notmuch_message_remove_all_tags(notmuch_message_t *msg)\r
++      void notmuch_message_destroy(notmuch_message_t *msg)\r
++      void notmuch_message_freeze(notmuch_message_t *msg)\r
++      void notmuch_message_thaw(notmuch_message_t *msg)\r
++\r
++\r
++cdef extern from "notmuch-client.h":\r
++      #\r
++      # notmuch_config_* -> notmuch.Config\r
++      #\r
++      ctypedef struct notmuch_config_t\r
++\r
++      notmuch_config_t* notmuch_config_open(void *ctx, char *filename, int *is_=\r
+new_ret)\r
++      void   notmuch_config_close(notmuch_config_t *cfg)\r
++      int    notmuch_config_save(notmuch_config_t *cfg)\r
++      char*  notmuch_config_get_database_path(notmuch_config_t *cfg)\r
++      void   notmuch_config_set_database_path(notmuch_config_t *cfg, char *path)\r
++      char*  notmuch_config_get_user_name(notmuch_config_t *cfg)\r
++      void   notmuch_config_set_user_name(notmuch_config_t *cfg, char *name)\r
++      char*  notmuch_config_get_user_primary_email(notmuch_config_t *cfg)\r
++      void   notmuch_config_set_user_primary_email(notmuch_config_t *cfg, char =\r
+*email)\r
++      char** notmuch_config_get_user_other_email(notmuch_config_t *cfg, size_t =\r
+*length)\r
++      void   notmuch_config_set_user_other_email(notmuch_config_t *cfg, char **=\r
+other_email, size_t length)\r
++\r
++      #\r
++      # Miscellaneous\r
++      #\r
++      int debugger_is_active()\r
++\r
++\r
++#\r
++# Import needed Python built-in modules\r
++#\r
++from datetime import datetime, date\r
++\r
++#\r
++# Miscellaneous functions and information\r
++#\r
++debugger_active =3D bool(debugger_is_active())\r
++\r
++\r
++#\r
++# notmuch_database_* -> notmuch.Database\r
++#\r
++\r
++\r
++#\r
++# notmuch_config_* -> notmuch.Config\r
++#\r
++cdef class Config:\r
++      """Handles the Notmuch configuration file.\r
++      """\r
++      cdef notmuch_config_t * _cfg\r
++      cdef void * _ctx\r
++      cdef object _filename\r
++\r
++      def __init__(self, filename=3D"~/.notmuch-config"):\r
++              """Open a Notmuch configuration file.\r
++              """\r
++              cdef int newret\r
++              self._ctx =3D talloc_init("notmuch.Config")\r
++              self._cfg =3D notmuch_config_open(self._ctx, filename, &newret)\r
++              self._filename =3D filename\r
++\r
++      def __dealloc__(self):\r
++              notmuch_config_close(self._cfg)\r
++              talloc_free(self._ctx)\r
++\r
++      property filename:\r
++              """File name containing the configuration (string)"""\r
++              def __get__(self):\r
++                      return self._filename\r
++\r
++      property database_path:\r
++              """Path to the Notmuch database (string)"""\r
++              def __get__(self):\r
++                      return notmuch_config_get_database_path(self._cfg)\r
++              def __set__(self, path):\r
++                      notmuch_config_set_database_path(self._cfg, path)\r
++\r
++      property user_name:\r
++              """User name (string)"""\r
++              def __get__(self):\r
++                      return notmuch_config_get_user_name(self._cfg)\r
++              def __set__(self, name):\r
++                      notmuch_config_set_user_name(self._cfg, name)\r
++\r
++      property user_primary_email:\r
++              """Primary e-mail address of the user (string)"""\r
++              def __get__(self):\r
++                      return notmuch_config_get_user_primary_email(self._cfg)\r
++              def __set__(self, email):\r
++                      notmuch_config_set_user_primary_email(self._cfg, email)\r
++\r
++      property other_email:\r
++              """List of other e-mail addresses of the user (tuple of strings)"""\r
++              def __get__(self):\r
++                      cdef size_t length\r
++                      cdef size_t i\r
++                      cdef char **emails\r
++                      emails =3D notmuch_config_get_user_other_email(self._cfg, &length)\r
++\r
++                      result =3D []\r
++                      for i from 0 <=3D i < length:\r
++                              result.append(emails[i])\r
++\r
++                      # XXX We do not want the result to be modifiable, because the property\r
++                      #     must be assigned as a whole, and not just modified only in the\r
++                      #     Python side of the world.\r
++                      return tuple(result)\r
++\r
++              def __set__(self, emaillist):\r
++                      cdef size_t length =3D len(emaillist)\r
++                      cdef char **emails =3D pyutil_alloc_strv(self._ctx, length)\r
++                      cdef size_t i\r
++\r
++                      for i from 0 <=3D i < len(emaillist):\r
++                              emails[i] =3D emaillist[i]\r
++                      notmuch_config_set_user_other_email(self._cfg, emails, len(emaillist))\r
++\r
++\r
++      def save(self):\r
++              """Save the Notmuch configuration"""\r
++              notmuch_config_save(self._cfg)\r
++\r
++\r
++cdef class Database\r
++\r
++\r
++cdef class Message:\r
++      cdef notmuch_message_t *_msg\r
++\r
++      def __cinit__(self, object messageptr):\r
++              # XXX Counterpart of bogus cast\r
++              self._msg =3D <notmuch_message_t*> messageptr\r
++\r
++      def __dealloc__(self):\r
++              notmuch_message_destroy(self._msg)\r
++\r
++      property message_id:\r
++              def __get__(self):\r
++                      return notmuch_message_get_message_id(self._msg)\r
++\r
++      property thread_id:\r
++              def __get__(self):\r
++                      return notmuch_message_get_thread_id(self._msg)\r
++\r
++      property filename:\r
++              def __get__(self):\r
++                      return notmuch_message_get_filename(self._msg)\r
++\r
++      def get_header(self, name):\r
++              return notmuch_message_get_header(self._msg, name)\r
++\r
++      def add_tag(self, tag):\r
++              cdef notmuch_status_t ret =3D notmuch_message_add_tag(self._msg, tag)\r
++              if ret !=3D NOTMUCH_STATUS_SUCCESS:\r
++                      raise ValueError(notmuch_status_to_string(ret))\r
++\r
++      def remove_tag(self, tag):\r
++              cdef notmuch_status_t ret =3D notmuch_message_remove_tag(self._msg, tag)\r
++              if ret !=3D NOTMUCH_STATUS_SUCCESS:\r
++                      raise ValueError(notmuch_status_to_string(ret))\r
++\r
++      def remove_all_tags(self):\r
++              notmuch_message_remove_all_tags(self._msg)\r
++\r
++      def freeze(self):\r
++              notmuch_message_freeze(self._msg)\r
++\r
++      def thaw(self):\r
++              notmuch_message_thaw(self._msg)\r
++\r
++\r
++cdef class Thread:\r
++      cdef notmuch_thread_t *_thread\r
++\r
++      def __cinit__(self, object threadptr):\r
++              self._thread =3D <notmuch_thread_t*> threadptr\r
++\r
++      def __dealloc__(self):\r
++              notmuch_thread_destroy(self._thread)\r
++\r
++      property authors:\r
++              def __get__(self):\r
++                      return notmuch_thread_get_authors(self._thread)\r
++\r
++      property subject:\r
++              def __get__(self):\r
++                      return notmuch_thread_get_subject(self._thread)\r
++\r
++      property thread_id:\r
++              def __get__(self):\r
++                      return notmuch_thread_get_thread_id(self._thread)\r
++\r
++      property oldest_date:\r
++              def __get__(self):\r
++                      return datetime.fromtimestamp(notmuch_thread_get_oldest_date(self._thre=\r
+ad))\r
++\r
++      property newest_date:\r
++              def __get__(self):\r
++                      return datetime.fromtimestamp(notmuch_thread_get_newest_date(self._thre=\r
+ad))\r
++\r
++      property total_messages:\r
++              def __get__(self):\r
++                      return notmuch_thread_get_total_messages(self._thread)\r
++\r
++      property matched_messages:\r
++              def __get__(self):\r
++                      return notmuch_thread_get_matched_messages(self._thread)\r
++\r
++      def __len__(self):\r
++              return self.matched_messages\r
++\r
++\r
++cdef class Query:\r
++      cdef notmuch_query_t *_query\r
++\r
++      def __cinit__(self, Database db not None, qs):\r
++              self._query =3D notmuch_query_create(db._db, qs)\r
++\r
++      def __dealloc__(self):\r
++              notmuch_query_destroy(self._query)\r
++\r
++      def __len__(self):\r
++              return notmuch_query_count_messages(self._query)\r
++\r
++      def sort(self, notmuch_sort_t ordering):\r
++              notmuch_query_set_sort(self._query, ordering)\r
++              return self\r
++\r
++\r
++\r
++cdef class Database:\r
++      cdef notmuch_database_t *_db\r
++\r
++      def __init__(self, path=3DNone, readonly=3DTrue):\r
++              cdef notmuch_database_mode_t mode =3D NOTMUCH_DATABASE_MODE_READ_WRITE\r
++              if path is None:\r
++                      path =3D Config().database_path\r
++              if readonly:\r
++                      mode =3D NOTMUCH_DATABASE_MODE_READ_ONLY\r
++              self._db =3D notmuch_database_open(path, mode)\r
++\r
++      def __dealloc__(self):\r
++              notmuch_database_close(self._db)\r
++\r
++      def get_timestamp(self, key):\r
++              cdef time_t ts =3D notmuch_database_get_timestamp(self._db, key)\r
++              return datetime.fromtimestamp(ts)\r
++\r
++      def set_timestamp(self, key, timestamp):\r
++              cdef time_t ts\r
++              if isinstance(timestamp, date):\r
++                      ts =3D int(timestamp.strftime("%s"))\r
++              elif isinstance(timestamp, float):\r
++                      ts =3D <time_t> timestamp\r
++              elif isinstance(timestamp, int):\r
++                      ts =3D timestamp\r
++              else:\r
++                      raise ValueError("Numeric timestamp or datetime.date object expected")\r
++\r
++      def add_message(self, filename):\r
++              cdef notmuch_message_t *message =3D NULL\r
++              cdef notmuch_status_t   status\r
++              status =3D notmuch_database_add_message(self._db, filename, &message)\r
++              if status =3D=3D NOTMUCH_STATUS_SUCCESS:\r
++                      # XXX This cast seems bogus, it may work, though\r
++                      return Message(<object> message)\r
++              else:\r
++                      if message !=3D NULL:\r
++                              notmuch_message_destroy(message)\r
++                      raise ValueError(notmuch_status_to_string(status))\r
++\r
++      def find_message(self, message_id):\r
++              cdef notmuch_message_t *message\r
++              message =3D notmuch_database_find_message(self._db, message_id)\r
++              if message =3D=3D NULL:\r
++                      raise KeyError(message_id)\r
++              return Message(<object> message)\r
++\r
++      property path:\r
++              """Database path"""\r
++              def __get__(self):\r
++                      return notmuch_database_get_path(self._db)\r
++\r
++      def query(self, qstring):\r
++              return Query(self, qstring)\r
++\r
++\r
+diff --git a/python/pyutil.h b/python/pyutil.h\r
+new file mode 100644\r
+index 0000000..64d93bf\r
+--- /dev/null\r
++++ b/python/pyutil.h\r
+@@ -0,0 +1,16 @@\r
++/*\r
++ * pyutil.h\r
++ * Copyright (C) 2009 Adrian Perez <aperez@igalia.com>\r
++ *\r
++ * Distributed under terms of the GPLv3 license.\r
++ */\r
++\r
++#ifndef __pyutil_h__\r
++#define __pyutil_h__\r
++\r
++#include <talloc.h>\r
++\r
++#define pyutil_alloc_strv(_ctx, _n)  talloc_array ((_ctx), char*, (_n))\r
++\r
++#endif /* !__pyutil_h__ */\r
++\r
+diff --git a/python/setup.py b/python/setup.py\r
+new file mode 100644\r
+index 0000000..ffd43b2\r
+--- /dev/null\r
++++ b/python/setup.py\r
+@@ -0,0 +1,89 @@\r
++#! /usr/bin/env python\r
++# -*- coding: utf-8 -*-\r
++# vim:fenc=3Dutf-8\r
++#\r
++# Copyright =C2=A9 2009 Adrian Perez <aperez@igalia.com>\r
++#\r
++# Distributed under terms of the GPLv3 license.\r
++\r
++from distutils.core import setup\r
++from distutils.extension import Extension\r
++from Cython.Distutils import build_ext\r
++import commands\r
++\r
++\r
++class FlagOMatic(dict):\r
++    _KEYS =3D ("extra_link_args", "include_dirs", "library_dirs", "librari=\r
+es")\r
++\r
++    def __init__(self, *arg, **kw):\r
++        super(FlagOMatic, self).__init__(*arg, **kw)\r
++        for key in self._KEYS:\r
++            self[key] =3D set(self.get(key, []))\r
++\r
++    extra_link_args =3D property(lambda self: self["extra_link_args"])\r
++    include_dirs    =3D property(lambda self: self["include_dirs"])\r
++    library_dirs    =3D property(lambda self: self["library_dirs"])\r
++    libraries       =3D property(lambda self: self["libraries"])\r
++\r
++    _FLAG_MAP =3D {\r
++            "-I": "include_dirs",\r
++            "-L": "library_dirs",\r
++            "-l": "libraries",\r
++        }\r
++\r
++    def add_compiler_flags(self, text):\r
++        for token in text.split():\r
++            key =3D self._FLAG_MAP.get(token[:2], "extra_link_args")\r
++            if key =3D=3D "extra_link_args":\r
++                self.extra_link_args.add(token)\r
++            else:\r
++                self[key].add(token[2:])\r
++\r
++    def add_pkgconfig_flags(self, *modules):\r
++        self.add_command_output_flags(\r
++                "pkg-config --libs --cflags %s" % " ".join(modules))\r
++\r
++    def add_command_output_flags(self, command):\r
++        self.add_compiler_flags(commands.getoutput(command))\r
++\r
++    @property\r
++    def kwargs(self):\r
++        return dict((k, list(v)) for k, v in self.iteritems())\r
++\r
++\r
++# Gather compiler flags\r
++#\r
++flags =3D FlagOMatic(\r
++       #extra_link_args =3D ("../lib/notmuch.a",),\r
++        include_dirs    =3D ("..", "../lib", "../compat"))\r
++\r
++flags.add_pkgconfig_flags("gmime-2.4", "talloc")\r
++flags.add_command_output_flags("xapian-config --cxxflags --libs")\r
++\r
++# We are building a extension module\r
++#\r
++import os\r
++\r
++srcs =3D ["notmuch.pyx"]\r
++srcs.extend(\r
++    map(lambda s: "../"+s,\r
++        filter(lambda s: s.endswith(".c"),\r
++            os.listdir(".."))))\r
++srcs.extend(\r
++    map(lambda s: "../lib/"+s,\r
++        filter(lambda s: s.endswith(".cc") or s.endswith(".c"),\r
++            os.listdir("../lib"))))\r
++\r
++ext_modules =3D [Extension("notmuch", srcs, **flags.kwargs)]\r
++\r
++\r
++# And now for the easy part :-)\r
++#\r
++setup(\r
++    name         =3D "notmuch",\r
++    author       =3D "Adrian Perez",\r
++    author_email =3D "aperez@igalia.com",\r
++    cmdclass     =3D {"build_ext": build_ext},\r
++    ext_modules  =3D ext_modules,\r
++)\r
++\r
+\r
+--MP_/Qt51GuWb3Bo1f8da6MLGH4N--\r
+\r
+--Sig_/NJQn5hRW+avHgHHAlU7=Xa1\r
+Content-Type: application/pgp-signature; name=signature.asc\r
+Content-Disposition: attachment; filename=signature.asc\r
+\r
+-----BEGIN PGP SIGNATURE-----\r
+Version: GnuPG v2.0.13 (GNU/Linux)\r
+\r
+iEYEARECAAYFAks7MOsACgkQkcVZ2+TJEju5RACfQAjq3L+//wNVYgB+05f2OmI7\r
+9P4AmgKqBUP79jj4EMuuNit64u94aj3q\r
+=RI1/\r
+-----END PGP SIGNATURE-----\r
+\r
+--Sig_/NJQn5hRW+avHgHHAlU7=Xa1--\r