]> git.notmuchmail.org Git - notmuch/blobdiff - bindings/python/notmuch/message.py
python: Implement Message.__cmp__ and __hash__
[notmuch] / bindings / python / notmuch / message.py
index 613cc4abc2be7f89c1501cbabaf008288e50204e..d6201ba88b713153ed124b9a3e3d0c22d46afd48 100644 (file)
@@ -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 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
 import sys
 import email
 import types
@@ -36,35 +37,27 @@ class Messages(object):
 
     This object provides an iterator over a list of notmuch messages
     (Technically, it provides a wrapper for the underlying
 
     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. Also note, that any
+    function that uses iteration will also exhaust the messages.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 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::
 
       db   = Database()
       msgs = Query(db,'').search_messages() #get a Messages() object
 
       db   = Database()
       msgs = Query(db,'').search_messages() #get a Messages() object
-      msglist = []
-      for m in msgs:
-         msglist.append(m)
+      msglist = list(msgs)
 
       # msgs is "exhausted" now and even len(msgs) will raise an exception.
       # However it will be kept around until all retrieved Message() objects are
 
       # msgs is "exhausted" now and even len(msgs) will raise an exception.
       # However it will be kept around until all retrieved Message() objects are
@@ -77,9 +70,18 @@ class Messages(object):
       print (msglist[0].get_filename())
       print (msglist[1].get_filename())
       print (msglist[0].get_message_id())
       print (msglist[0].get_filename())
       print (msglist[1].get_filename())
       print (msglist[0].get_message_id())
+
+
+    As Message() implements both __hash__() and __cmp__(), it is
+    possible to make sets out of Messages() and use set arithmetic::
+
+        s1, s2 = set(msgs1), set(msgs2)
+        s.union(s2)
+        s1 -= s2
+        ...
     """
 
     """
 
-    #notmuch_tags_get
+    #notmuch_messages_get
     _get = nmlib.notmuch_messages_get
     _get.restype = c_void_p
 
     _get = nmlib.notmuch_messages_get
     _get.restype = c_void_p
 
@@ -147,33 +149,12 @@ class Messages(object):
         nmlib.notmuch_messages_move_to_next(self._msgs)
         return msg
 
         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"""
 
     def __del__(self):
         """Close and free the notmuch Messages"""
@@ -238,12 +219,21 @@ class Message(object):
     """Represents a single Email message
 
     Technically, this wraps the underlying *notmuch_message_t* structure.
     """Represents a single Email message
 
     Technically, this wraps the underlying *notmuch_message_t* structure.
+
+    As this implements both __hash__() and __cmp__(), it is possible to
+    compare 2 Message objects 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 
 
     """
 
     """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
     """notmuch_message_get_flag"""
     _get_flag = nmlib.notmuch_message_get_flag
     _get_flag.restype = c_uint
@@ -400,6 +390,19 @@ class Message(object):
             raise NotmuchError(STATUS.NOT_INITIALIZED)
         return Message._get_filename(self._msg)
 
             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
 
     def get_flag(self, flag):
         """Checks whether a specific flag is set for this message
 
@@ -748,7 +751,7 @@ class Message(object):
         output += "\n\fbody{"
 
         parts = format["body"]
         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{ "
         for p in parts:
             if not p.has_key("filename"):
                 output += "\n\fpart{ "
@@ -771,6 +774,23 @@ class Message(object):
 
         return output
 
 
         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."""
+        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:
     def __del__(self):
         """Close and free the notmuch Message"""
         if self._msg is not None: