X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=bindings%2Fpython%2Fnotmuch%2Fmessage.py;h=950d632a5de6d9c4b764b2d9bc03da7f8bded153;hp=613cc4abc2be7f89c1501cbabaf008288e50204e;hb=b4049316cc3bc933453268ed8d3e89b1ee0098a1;hpb=3b558de7811a765c3295a58bd53e2156eca0e32e diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index 613cc4ab..950d632a 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -23,6 +23,7 @@ from ctypes import c_char_p, c_void_p, c_long, c_uint from datetime import date from notmuch.globals import nmlib, STATUS, NotmuchError, Enum from notmuch.tag import Tags +from notmuch.filename import Filenames import sys import email import types @@ -36,50 +37,59 @@ class Messages(object): This object provides an iterator over a list of notmuch messages (Technically, it provides a wrapper for the underlying - *notmuch_messages_t* structure). Do note that the underlying - library only provides a one-time iterator (it cannot reset the - iterator to the start). Thus iterating over the function will - "exhaust" the list of messages, and a subsequent iteration attempt - will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also - note, that any function that uses iteration will also - exhaust the messages. So both:: - - for msg in msgs: print msg - - as well as:: - - number_of_msgs = len(msgs) - - will "exhaust" the Messages. If you need to re-iterate over a list of - messages you will need to retrieve a new :class:`Messages` object. - - Things are not as bad as it seems though, you can store and reuse - the single Message objects as often as you want as long as you - keep the parent Messages object around. (Recall that due to - hierarchical memory allocation, all derived Message objects will - be invalid when we delete the parent Messages() object, even if it - was already "exhausted".) So this works:: + *notmuch_messages_t* structure). Do note that the underlying library + only provides a one-time iterator (it cannot reset the iterator to + the start). Thus iterating over the function will "exhaust" the list + of messages, and a subsequent iteration attempt will raise a + :exc:`NotmuchError` STATUS.NOT_INITIALIZED. If you need to + re-iterate over a list of messages you will need to retrieve a new + :class:`Messages` object or cache your :class:`Message`s in a list + via:: + + msglist = list(msgs) + + You can store and reuse the single :class:`Message` objects as often + as you want as long as you keep the parent :class:`Messages` object + around. (Due to hierarchical memory allocation, all derived + :class:`Message` objects will be invalid when we delete the parent + :class:`Messages` object, even if it was already exhausted.) So + this works:: db = Database() msgs = Query(db,'').search_messages() #get a Messages() object - msglist = [] - for m in msgs: - msglist.append(m) - - # msgs is "exhausted" now and even len(msgs) will raise an exception. - # However it will be kept around until all retrieved Message() objects are - # also deleted. If you did e.g. an explicit del(msgs) here, the - # following lines would fail. + msglist = list(msgs) + + # msgs is "exhausted" now and msgs.next() will raise an exception. + # However it will be kept alive until all retrieved Message() + # objects are also deleted. If you do e.g. an explicit del(msgs) + # here, the following lines would fail. # You can reiterate over *msglist* however as often as you want. - # It is simply a list with Message objects. + # It is simply a list with :class:`Message`s. print (msglist[0].get_filename()) print (msglist[1].get_filename()) print (msglist[0].get_message_id()) + + + As :class:`Message` implements both __hash__() and __cmp__(), it is + possible to make sets out of :class:`Messages` and use set + arithmetic (this happens in python and will of course be *much* + slower than redoing a proper query with the appropriate filters:: + + s1, s2 = set(msgs1), set(msgs2) + s.union(s2) + s1 -= s2 + ... + + Be careful when using set arithmetic between message sets derived + from different Databases (ie the same database reopened after + messages have changed). If messages have added or removed associated + files in the meantime, it is possible that the same message would be + considered as a different object (as it points to a different file). """ - #notmuch_tags_get + #notmuch_messages_get _get = nmlib.notmuch_messages_get _get.restype = c_void_p @@ -147,33 +157,12 @@ class Messages(object): nmlib.notmuch_messages_move_to_next(self._msgs) return msg - def __len__(self): - """len(:class:`Messages`) returns the number of contained messages - - .. note:: As this iterates over the messages, we will not be able to - iterate over them again! So this will fail:: - - #THIS FAILS - msgs = Database().create_query('').search_message() - if len(msgs) > 0: #this 'exhausts' msgs - # next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!! - for msg in msgs: print msg - - Most of the time, using the - :meth:`Query.count_messages` is therefore more - appropriate (and much faster). While not guaranteeing - that it will return the exact same number than len(), - in my tests it effectively always did so. + def __nonzero__(self): """ - if self._msgs is None: - raise NotmuchError(STATUS.NOT_INITIALIZED) - - i=0 - while nmlib.notmuch_messages_valid(self._msgs): - nmlib.notmuch_messages_move_to_next(self._msgs) - i += 1 - self._msgs = None - return i + :return: True if there is at least one more thread in the + Iterator, False if not.""" + return self._msgs is not None and \ + nmlib.notmuch_messages_valid(self._msgs) > 0 def __del__(self): """Close and free the notmuch Messages""" @@ -238,12 +227,21 @@ class Message(object): """Represents a single Email message Technically, this wraps the underlying *notmuch_message_t* structure. + + As this implements __cmp__() it is possible to compare 2 + :class:`Message`s with:: + + if msg1 == msg2: """ """notmuch_message_get_filename (notmuch_message_t *message)""" _get_filename = nmlib.notmuch_message_get_filename _get_filename.restype = c_char_p + """return all filenames for a message""" + _get_filenames = nmlib.notmuch_message_get_filenames + _get_filenames.restype = c_void_p + """notmuch_message_get_flag""" _get_flag = nmlib.notmuch_message_get_flag _get_flag.restype = c_uint @@ -400,6 +398,19 @@ class Message(object): raise NotmuchError(STATUS.NOT_INITIALIZED) return Message._get_filename(self._msg) + def get_filenames(self): + """Get all filenames for the email corresponding to 'message' + + Returns a Filenames() generator with all absolute filepaths for + messages recorded to have the same Message-ID. These files must + not necessarily have identical content.""" + if self._msg is None: + raise NotmuchError(STATUS.NOT_INITIALIZED) + + files_p = Message._get_filenames(self._msg) + + return Filenames(files_p, self).as_generator() + def get_flag(self, flag): """Checks whether a specific flag is set for this message @@ -748,7 +759,7 @@ class Message(object): output += "\n\fbody{" parts = format["body"] - parts.sort(key=lambda(p): p["id"]) + parts.sort(key=lambda x: x['id']) for p in parts: if not p.has_key("filename"): output += "\n\fpart{ " @@ -771,6 +782,27 @@ class Message(object): return output + def __hash__(self): + """Implement hash(), so we can use Message() sets""" + file = self.get_filename() + if file is None: + return None + return hash(file) + + def __cmp__(self, other): + """Implement cmp(), so we can compare Message()s + + 2 messages are considered equal if they point to the same + Message-Id and if they point to the same file names. If 2 + Messages derive from different queries where some files have + been added or removed, the same messages would not be considered + equal (as they do not point to the same set of files + any more).""" + res = cmp(self.get_message_id(), other.get_message_id()) + if res: + res = cmp(list(self.get_filenames()), list(other.get_filenames())) + return res + def __del__(self): """Close and free the notmuch Message""" if self._msg is not None: