diff options
Diffstat (limited to 'bindings/python')
-rwxr-xr-x | bindings/python/notmuch.py | 7 | ||||
-rw-r--r-- | bindings/python/notmuch/database.py | 17 | ||||
-rw-r--r-- | bindings/python/notmuch/filename.py | 9 | ||||
-rw-r--r-- | bindings/python/notmuch/globals.py | 21 | ||||
-rw-r--r-- | bindings/python/notmuch/message.py | 80 | ||||
-rw-r--r-- | bindings/python/notmuch/tag.py | 10 | ||||
-rw-r--r-- | bindings/python/notmuch/thread.py | 16 | ||||
-rw-r--r-- | bindings/python/setup.py | 2 |
8 files changed, 99 insertions, 63 deletions
diff --git a/bindings/python/notmuch.py b/bindings/python/notmuch.py index 8d118595..3ff53ec8 100755 --- a/bindings/python/notmuch.py +++ b/bindings/python/notmuch.py @@ -17,7 +17,12 @@ import stat import email from notmuch import Database, Query, NotmuchError, STATUS -from ConfigParser import SafeConfigParser +try: + # python3.x + from configparser import SafeConfigParser +except ImportError: + # python2.x + from ConfigParser import SafeConfigParser from cStringIO import StringIO PREFIX = re.compile('(\w+):(.*$)') diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 7923f768..24da8e99 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -430,7 +430,7 @@ class Database(object): removed. """ self._assert_db_is_initialized() - return self._remove_message(self._db, filename) + return self._remove_message(self._db, _str(filename)) def find_message(self, msgid): """Returns a :class:`Message` as identified by its message ID @@ -543,7 +543,13 @@ class Database(object): """ Reads a user's notmuch config and returns his db location Throws a NotmuchError if it cannot find it""" - from ConfigParser import SafeConfigParser + try: + # python3.x + from configparser import SafeConfigParser + except ImportError: + # python2.x + from ConfigParser import SafeConfigParser + config = SafeConfigParser() conf_f = os.getenv('NOTMUCH_CONFIG', os.path.expanduser('~/.notmuch-config')) @@ -919,7 +925,7 @@ class Filenames(object): _move_to_next.argtypes = [NotmuchFilenamesP] _move_to_next.restype = None - def next(self): + def __next__(self): if self._files_p is None: raise NotmuchError(STATUS.NOT_INITIALIZED) @@ -927,9 +933,10 @@ class Filenames(object): self._files_p = None raise StopIteration - file = Filenames._get(self._files_p) + file_ = Filenames._get(self._files_p) self._move_to_next(self._files_p) - return file + return file_.decode('utf-8', 'ignore') + next = __next__ # python2.x iterator protocol compatibility def __len__(self): """len(:class:`Filenames`) returns the number of contained files diff --git a/bindings/python/notmuch/filename.py b/bindings/python/notmuch/filename.py index a7cd7e63..51dae202 100644 --- a/bindings/python/notmuch/filename.py +++ b/bindings/python/notmuch/filename.py @@ -18,10 +18,10 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ from ctypes import c_char_p from notmuch.globals import (nmlib, STATUS, NotmuchError, - NotmuchFilenamesP, NotmuchMessageP) + NotmuchFilenamesP, NotmuchMessageP, _str, Python3StringMixIn) -class Filenames(object): +class Filenames(Python3StringMixIn): """Represents a list of filenames as returned by notmuch This object contains the Filenames iterator. The main function is @@ -93,14 +93,11 @@ class Filenames(object): raise NotmuchError(STATUS.NOT_INITIALIZED) while self._valid(self._files): - yield Filenames._get(self._files) + yield Filenames._get(self._files).decode('utf-8', 'ignore') self._move_to_next(self._files) self._files = None - def __str__(self): - return unicode(self).encode('utf-8') - def __unicode__(self): """Represent Filenames() as newline-separated list of full paths diff --git a/bindings/python/notmuch/globals.py b/bindings/python/notmuch/globals.py index 54a49b2d..32ed9ae4 100644 --- a/bindings/python/notmuch/globals.py +++ b/bindings/python/notmuch/globals.py @@ -16,7 +16,7 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ - +import sys from ctypes import CDLL, c_char_p, c_int, Structure, POINTER #----------------------------------------------------------------------------- @@ -27,6 +27,16 @@ except: raise ImportError("Could not find shared 'notmuch' library.") +if sys.version_info[0] == 2: + class Python3StringMixIn(object): + def __str__(self): + return unicode(self).encode('utf-8') +else: + class Python3StringMixIn(object): + def __str__(self): + return self.__unicode__() + + class Enum(object): """Provides ENUMS as "code=Enum(['a','b','c'])" where code.a=0 etc...""" def __init__(self, names): @@ -51,7 +61,7 @@ class Status(Enum): """Get a (unicode) string representation of a notmuch_status_t value.""" # define strings for custom error messages if status == STATUS.NOT_INITIALIZED: - return u"Operation on uninitialized object impossible." + return "Operation on uninitialized object impossible." return unicode(Status._status2str(status)) STATUS = Status(['SUCCESS', @@ -89,7 +99,7 @@ argument to receive a human readable string""" STATUS.__name__ = 'STATUS' -class NotmuchError(Exception): +class NotmuchError(Exception, Python3StringMixIn): """Is initiated with a (notmuch.STATUS[, message=None]). It will not return an instance of the class NotmuchError, but a derived instance of a more specific Error Message, e.g. OutOfMemoryError. Each status @@ -133,16 +143,13 @@ class NotmuchError(Exception): self.status = status self.message = message - def __str__(self): - return unicode(self).encode('utf-8') - def __unicode__(self): if self.message is not None: return self.message elif self.status is not None: return STATUS.status2str(self.status) else: - return u'Unknown error' + return 'Unknown error' # List of Subclassed exceptions that correspond to STATUS values and are diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index ce8e7181..d40a575d 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -21,7 +21,8 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' from ctypes import c_char_p, c_long, c_uint, c_int from datetime import date -from notmuch.globals import (nmlib, STATUS, NotmuchError, Enum, _str, +from notmuch.globals import ( + nmlib, STATUS, NotmuchError, Enum, _str, Python3StringMixIn, NotmuchTagsP, NotmuchMessagesP, NotmuchMessageP, NotmuchFilenamesP) from notmuch.tag import Tags from notmuch.filename import Filenames @@ -158,7 +159,7 @@ class Messages(object): _move_to_next.argtypes = [NotmuchMessagesP] _move_to_next.restype = None - def next(self): + def __next__(self): if self._msgs is None: raise NotmuchError(STATUS.NOT_INITIALIZED) @@ -169,6 +170,7 @@ class Messages(object): msg = Message(Messages._get(self._msgs), self) self._move_to_next(self._msgs) return msg + next = __next__ # python2.x iterator protocol compatibility def __nonzero__(self): """ @@ -186,14 +188,17 @@ class Messages(object): if self._msgs is not None: self._destroy(self._msgs) - def print_messages(self, format, indent=0, entire_thread=False): - """Outputs messages as needed for 'notmuch show' to sys.stdout + def format_messages(self, format, indent=0, entire_thread=False): + """Formats messages as needed for 'notmuch show'. :param format: A string of either 'text' or 'json'. :param indent: A number indicating the reply depth of these messages. :param entire_thread: A bool, indicating whether we want to output whole threads or only the matching messages. + :return: a list of lines """ + result = list() + if format.lower() == "text": set_start = "" set_end = "" @@ -207,38 +212,61 @@ class Messages(object): first_set = True - sys.stdout.write(set_start) + result.append(set_start) # iterate through all toplevel messages in this thread for msg in self: # if not msg: # break if not first_set: - sys.stdout.write(set_sep) + result.append(set_sep) first_set = False - sys.stdout.write(set_start) + result.append(set_start) match = msg.is_match() next_indent = indent if (match or entire_thread): if format.lower() == "text": - sys.stdout.write(msg.format_message_as_text(indent)) + result.append(msg.format_message_as_text(indent)) else: - sys.stdout.write(msg.format_message_as_json(indent)) + result.append(msg.format_message_as_json(indent)) next_indent = indent + 1 # get replies and print them also out (if there are any) - replies = msg.get_replies() - if not replies is None: - sys.stdout.write(set_sep) - replies.print_messages(format, next_indent, entire_thread) + replies = msg.get_replies().format_messages(format, next_indent, entire_thread) + if replies: + result.append(set_sep) + result.extend(replies) + + result.append(set_end) + result.append(set_end) + + return result + + def print_messages(self, format, indent=0, entire_thread=False, handle=sys.stdout): + """Outputs messages as needed for 'notmuch show' to a file like object. + + :param format: A string of either 'text' or 'json'. + :param handle: A file like object to print to (default is sys.stdout). + :param indent: A number indicating the reply depth of these messages. + :param entire_thread: A bool, indicating whether we want to output + whole threads or only the matching messages. + """ + handle.write(''.join(self.format_messages(format, indent, entire_thread))) + + +class EmptyMessagesResult(Messages): + def __init__(self, parent): + self._msgs = None + self._parent = parent - sys.stdout.write(set_end) - sys.stdout.write(set_end) + def __next__(self): + raise StopIteration() + next = __next__ -class Message(object): +class Message(Python3StringMixIn): """Represents a single Email message Technically, this wraps the underlying *notmuch_message_t* @@ -336,7 +364,7 @@ class Message(object): """ if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - return Message._get_message_id(self._msg) + return Message._get_message_id(self._msg).decode('utf-8', 'ignore') def get_thread_id(self): """Returns the thread ID @@ -354,7 +382,7 @@ class Message(object): if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - return Message._get_thread_id(self._msg) + return Message._get_thread_id(self._msg).decode('utf-8', 'ignore') def get_replies(self): """Gets all direct replies to this message as :class:`Messages` @@ -368,10 +396,9 @@ class Message(object): number of subsequent calls to :meth:`get_replies`). If this message was obtained through some non-thread means, (such as by a call to :meth:`Query.search_messages`), then this function will return - `None`. + an empty Messages iterator. - :returns: :class:`Messages` or `None` if there are no replies to - this message. + :returns: :class:`Messages`. :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message is not initialized. """ @@ -381,7 +408,7 @@ class Message(object): msgs_p = Message._get_replies(self._msg) if msgs_p is None: - return None + return EmptyMessagesResult(self) return Messages(msgs_p, self) @@ -424,10 +451,10 @@ class Message(object): raise NotmuchError(STATUS.NOT_INITIALIZED) #Returns NULL if any error occurs. - header = Message._get_header(self._msg, header) + header = Message._get_header(self._msg, _str(header)) if header == None: raise NotmuchError(STATUS.NULL_POINTER) - return header.decode('UTF-8', errors='ignore') + return header.decode('UTF-8', 'ignore') def get_filename(self): """Returns the file path of the message file @@ -438,7 +465,7 @@ class Message(object): """ if self._msg is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - return Message._get_filename(self._msg) + return Message._get_filename(self._msg).decode('utf-8', 'ignore') def get_filenames(self): """Get all filenames for the email corresponding to 'message' @@ -795,9 +822,6 @@ class Message(object): """Represent a Message() object by str()""" return self.__str__() - def __str__(self): - return unicode(self).encode('utf-8') - def __unicode__(self): format = "%s (%s) (%s)" return format % (self.get_header('from'), diff --git a/bindings/python/notmuch/tag.py b/bindings/python/notmuch/tag.py index 2fb7d328..ceb72441 100644 --- a/bindings/python/notmuch/tag.py +++ b/bindings/python/notmuch/tag.py @@ -17,10 +17,10 @@ along with notmuch. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' """ from ctypes import c_char_p -from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP +from notmuch.globals import nmlib, STATUS, NotmuchError, NotmuchTagsP, _str, Python3StringMixIn -class Tags(object): +class Tags(Python3StringMixIn): """Represents a list of notmuch tags This object provides an iterator over a list of notmuch tags (which @@ -89,7 +89,7 @@ class Tags(object): _move_to_next.argtypes = [NotmuchTagsP] _move_to_next.restype = None - def next(self): + def __next__(self): if self._tags is None: raise NotmuchError(STATUS.NOT_INITIALIZED) if not self._valid(self._tags): @@ -98,6 +98,7 @@ class Tags(object): tag = Tags._get(self._tags).decode('UTF-8') self._move_to_next(self._tags) return tag + next = __next__ # python2.x iterator protocol compatibility def __nonzero__(self): """Implement bool(Tags) check that can be repeatedly used @@ -110,9 +111,6 @@ class Tags(object): left.""" return self._valid(self._tags) > 0 - def __str__(self): - return unicode(self).encode('utf-8') - def __unicode__(self): """string representation of :class:`Tags`: a space separated list of tags diff --git a/bindings/python/notmuch/thread.py b/bindings/python/notmuch/thread.py index 5058846d..e81ff1bd 100644 --- a/bindings/python/notmuch/thread.py +++ b/bindings/python/notmuch/thread.py @@ -20,13 +20,13 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>' from ctypes import c_char_p, c_long, c_int from notmuch.globals import (nmlib, STATUS, NotmuchError, NotmuchThreadP, NotmuchThreadsP, NotmuchMessagesP, - NotmuchTagsP,) + NotmuchTagsP, Python3StringMixIn) from notmuch.message import Messages from notmuch.tag import Tags from datetime import date -class Threads(object): +class Threads(Python3StringMixIn): """Represents a list of notmuch threads This object provides an iterator over a list of notmuch threads @@ -116,7 +116,7 @@ class Threads(object): _move_to_next.argtypes = [NotmuchThreadsP] _move_to_next.restype = None - def next(self): + def __next__(self): if self._threads is None: raise NotmuchError(STATUS.NOT_INITIALIZED) @@ -127,6 +127,7 @@ class Threads(object): thread = Thread(Threads._get(self._threads), self) self._move_to_next(self._threads) return thread + next = __next__ # python2.x iterator protocol compatibility def __len__(self): """len(:class:`Threads`) returns the number of contained Threads @@ -245,7 +246,7 @@ class Thread(object): """ if self._thread is None: raise NotmuchError(STATUS.NOT_INITIALIZED) - return Thread._get_thread_id(self._thread) + return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore') _get_total_messages = nmlib.notmuch_thread_get_total_messages _get_total_messages.argtypes = [NotmuchThreadP] @@ -325,7 +326,7 @@ class Thread(object): authors = Thread._get_authors(self._thread) if authors is None: return None - return authors.decode('UTF-8', errors='ignore') + return authors.decode('UTF-8', 'ignore') def get_subject(self): """Returns the Subject of 'thread' @@ -338,7 +339,7 @@ class Thread(object): subject = Thread._get_subject(self._thread) if subject is None: return None - return subject.decode('UTF-8', errors='ignore') + return subject.decode('UTF-8', 'ignore') def get_newest_date(self): """Returns time_t of the newest message date @@ -391,9 +392,6 @@ class Thread(object): raise NotmuchError(STATUS.NULL_POINTER) return Tags(tags_p, self) - def __str__(self): - return unicode(self).encode('utf-8') - def __unicode__(self): frm = "thread:%s %12s [%d/%d] %s; %s (%s)" diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 286fd196..2e58dab1 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -7,7 +7,7 @@ from distutils.core import setup # get the notmuch version number without importing the notmuch module version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'notmuch', 'version.py') -execfile(version_file) +exec(compile(open(version_file).read(), version_file, 'exec')) assert __VERSION__, 'Failed to read the notmuch binding version number' setup(name='notmuch', |