]> git.notmuchmail.org Git - notmuch/commitdiff
python: move Messages class into its own file
authorJustus Winter <4winter@informatik.uni-hamburg.de>
Wed, 22 Feb 2012 21:34:40 +0000 (22:34 +0100)
committerJustus Winter <4winter@informatik.uni-hamburg.de>
Wed, 22 Feb 2012 21:34:40 +0000 (22:34 +0100)
Signed-off-by: Justus Winter <4winter@informatik.uni-hamburg.de>
bindings/python/notmuch/__init__.py
bindings/python/notmuch/message.py
bindings/python/notmuch/messages.py [new file with mode: 0644]
bindings/python/notmuch/query.py
bindings/python/notmuch/thread.py

index 8de73d58864c5cfbc16904a7ffe83020898fe1d1..b8f36108dfde5d94f56e3db0d17169680224c772 100644 (file)
@@ -54,7 +54,8 @@ Copyright 2010-2011 Sebastian Spaeth <Sebastian@SSpaeth.de>
 from .database import Database
 from .directory import Directory
 from .filename import Filenames
 from .database import Database
 from .directory import Directory
 from .filename import Filenames
-from .message import Messages, Message
+from .message import Message
+from .messages import Messages
 from .query import Query
 from .tag import Tags
 from .thread import Threads, Thread
 from .query import Query
 from .tag import Tags
 from .thread import Threads, Thread
index 9ebb53d5bf3a92f4110b774687aceea8ce22adcb..20ba9cb918cbca07d05a4266d63a02898895334d 100644 (file)
@@ -21,7 +21,7 @@ Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
 
 from ctypes import c_char_p, c_long, c_uint, c_int
 from datetime import date
 
 from ctypes import c_char_p, c_long, c_uint, c_int
 from datetime import date
-from notmuch.globals import (
+from .globals import (
     nmlib,
     Enum,
     _str,
     nmlib,
     Enum,
     _str,
@@ -35,9 +35,9 @@ from notmuch.globals import (
     NotmuchMessagesP,
     NotmuchFilenamesP,
 )
     NotmuchMessagesP,
     NotmuchFilenamesP,
 )
-from notmuch.tag import Tags
-from notmuch.filename import Filenames
-import sys
+from .tag import Tags
+from .filename import Filenames
+
 import email
 try:
     import simplejson as json
 import email
 try:
     import simplejson as json
@@ -45,238 +45,6 @@ except ImportError:
     import json
 
 
     import json
 
 
-class Messages(object):
-    """Represents a list of notmuch messages
-
-    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:`NotInitializedError`. 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 = 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 :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_messages_get
-    _get = nmlib.notmuch_messages_get
-    _get.argtypes = [NotmuchMessagesP]
-    _get.restype = NotmuchMessageP
-
-    _collect_tags = nmlib.notmuch_messages_collect_tags
-    _collect_tags.argtypes = [NotmuchMessagesP]
-    _collect_tags.restype = NotmuchTagsP
-
-    def __init__(self, msgs_p, parent=None):
-        """
-        :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
-             structure. These are not publically exposed, so a user
-             will almost never instantiate a :class:`Messages` object
-             herself. They are usually handed back as a result,
-             e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
-             valid, we will raise an :exc:`NullPointerError` if it is
-             `None`.
-        :type msgs_p: :class:`ctypes.c_void_p`
-        :param parent: The parent object
-             (ie :class:`Query`) these tags are derived from. It saves
-             a reference to it, so we can automatically delete the db
-             object once all derived objects are dead.
-        :TODO: Make the iterator work more than once and cache the tags in
-               the Python object.(?)
-        """
-        if not msgs_p:
-            raise NullPointerError()
-
-        self._msgs = msgs_p
-        #store parent, so we keep them alive as long as self  is alive
-        self._parent = parent
-
-    def collect_tags(self):
-        """Return the unique :class:`Tags` in the contained messages
-
-        :returns: :class:`Tags`
-        :exceptions: :exc:`NotInitializedError` if not init'ed
-
-        .. note::
-
-            :meth:`collect_tags` will iterate over the messages and therefore
-            will not allow further iterations.
-        """
-        if not self._msgs:
-            raise NotInitializedError()
-
-        # collect all tags (returns NULL on error)
-        tags_p = Messages._collect_tags(self._msgs)
-        #reset _msgs as we iterated over it and can do so only once
-        self._msgs = None
-
-        if tags_p == None:
-            raise NullPointerError()
-        return Tags(tags_p, self)
-
-    def __iter__(self):
-        """ Make Messages an iterator """
-        return self
-
-    _valid = nmlib.notmuch_messages_valid
-    _valid.argtypes = [NotmuchMessagesP]
-    _valid.restype = bool
-
-    _move_to_next = nmlib.notmuch_messages_move_to_next
-    _move_to_next.argtypes = [NotmuchMessagesP]
-    _move_to_next.restype = None
-
-    def __next__(self):
-        if not self._msgs:
-            raise NotInitializedError()
-
-        if not self._valid(self._msgs):
-            self._msgs = None
-            raise StopIteration
-
-        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):
-        """
-        :return: True if there is at least one more thread in the
-            Iterator, False if not."""
-        return self._msgs is not None and \
-            self._valid(self._msgs) > 0
-
-    _destroy = nmlib.notmuch_messages_destroy
-    _destroy.argtypes = [NotmuchMessagesP]
-    _destroy.restype = None
-
-    def __del__(self):
-        """Close and free the notmuch Messages"""
-        if self._msgs is not None:
-            self._destroy(self._msgs)
-
-    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 = ""
-            set_sep = ""
-        elif format.lower() == "json":
-            set_start = "["
-            set_end = "]"
-            set_sep = ", "
-        else:
-            raise TypeError("format must be either 'text' or 'json'")
-
-        first_set = True
-
-        result.append(set_start)
-
-        # iterate through all toplevel messages in this thread
-        for msg in self:
-            # if not msg:
-            #     break
-            if not first_set:
-                result.append(set_sep)
-            first_set = False
-
-            result.append(set_start)
-            match = msg.is_match()
-            next_indent = indent
-
-            if (match or entire_thread):
-                if format.lower() == "text":
-                    result.append(msg.format_message_as_text(indent))
-                else:
-                    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().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
-
-    def __next__(self):
-        raise StopIteration()
-    next = __next__
-
-
 class Message(Python3StringMixIn):
     """Represents a single Email message
 
 class Message(Python3StringMixIn):
     """Represents a single Email message
 
@@ -418,6 +186,8 @@ class Message(Python3StringMixIn):
 
         msgs_p = Message._get_replies(self._msg)
 
 
         msgs_p = Message._get_replies(self._msg)
 
+        from .messages import Messages, EmptyMessagesResult
+
         if not msgs_p:
             return EmptyMessagesResult(self)
 
         if not msgs_p:
             return EmptyMessagesResult(self)
 
diff --git a/bindings/python/notmuch/messages.py b/bindings/python/notmuch/messages.py
new file mode 100644 (file)
index 0000000..20d3632
--- /dev/null
@@ -0,0 +1,262 @@
+"""
+This file is part of notmuch.
+
+Notmuch is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation, either version 3 of the License, or (at your
+option) any later version.
+
+Notmuch is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with notmuch.  If not, see <http://www.gnu.org/licenses/>.
+
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
+               Jesse Rosenthal <jrosenthal@jhu.edu>
+"""
+
+from .globals import (
+    nmlib,
+    NullPointerError,
+    NotInitializedError,
+    NotmuchTagsP,
+    NotmuchMessageP,
+    NotmuchMessagesP,
+)
+from .tag import Tags
+from .message import Message
+
+import sys
+
+class Messages(object):
+    """Represents a list of notmuch messages
+
+    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:`NotInitializedError`. 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 = 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 :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_messages_get
+    _get = nmlib.notmuch_messages_get
+    _get.argtypes = [NotmuchMessagesP]
+    _get.restype = NotmuchMessageP
+
+    _collect_tags = nmlib.notmuch_messages_collect_tags
+    _collect_tags.argtypes = [NotmuchMessagesP]
+    _collect_tags.restype = NotmuchTagsP
+
+    def __init__(self, msgs_p, parent=None):
+        """
+        :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
+             structure. These are not publically exposed, so a user
+             will almost never instantiate a :class:`Messages` object
+             herself. They are usually handed back as a result,
+             e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
+             valid, we will raise an :exc:`NullPointerError` if it is
+             `None`.
+        :type msgs_p: :class:`ctypes.c_void_p`
+        :param parent: The parent object
+             (ie :class:`Query`) these tags are derived from. It saves
+             a reference to it, so we can automatically delete the db
+             object once all derived objects are dead.
+        :TODO: Make the iterator work more than once and cache the tags in
+               the Python object.(?)
+        """
+        if not msgs_p:
+            raise NullPointerError()
+
+        self._msgs = msgs_p
+        #store parent, so we keep them alive as long as self  is alive
+        self._parent = parent
+
+    def collect_tags(self):
+        """Return the unique :class:`Tags` in the contained messages
+
+        :returns: :class:`Tags`
+        :exceptions: :exc:`NotInitializedError` if not init'ed
+
+        .. note::
+
+            :meth:`collect_tags` will iterate over the messages and therefore
+            will not allow further iterations.
+        """
+        if not self._msgs:
+            raise NotInitializedError()
+
+        # collect all tags (returns NULL on error)
+        tags_p = Messages._collect_tags(self._msgs)
+        #reset _msgs as we iterated over it and can do so only once
+        self._msgs = None
+
+        if tags_p == None:
+            raise NullPointerError()
+        return Tags(tags_p, self)
+
+    def __iter__(self):
+        """ Make Messages an iterator """
+        return self
+
+    _valid = nmlib.notmuch_messages_valid
+    _valid.argtypes = [NotmuchMessagesP]
+    _valid.restype = bool
+
+    _move_to_next = nmlib.notmuch_messages_move_to_next
+    _move_to_next.argtypes = [NotmuchMessagesP]
+    _move_to_next.restype = None
+
+    def __next__(self):
+        if not self._msgs:
+            raise NotInitializedError()
+
+        if not self._valid(self._msgs):
+            self._msgs = None
+            raise StopIteration
+
+        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):
+        """
+        :return: True if there is at least one more thread in the
+            Iterator, False if not."""
+        return self._msgs is not None and \
+            self._valid(self._msgs) > 0
+
+    _destroy = nmlib.notmuch_messages_destroy
+    _destroy.argtypes = [NotmuchMessagesP]
+    _destroy.restype = None
+
+    def __del__(self):
+        """Close and free the notmuch Messages"""
+        if self._msgs is not None:
+            self._destroy(self._msgs)
+
+    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 = ""
+            set_sep = ""
+        elif format.lower() == "json":
+            set_start = "["
+            set_end = "]"
+            set_sep = ", "
+        else:
+            raise TypeError("format must be either 'text' or 'json'")
+
+        first_set = True
+
+        result.append(set_start)
+
+        # iterate through all toplevel messages in this thread
+        for msg in self:
+            # if not msg:
+            #     break
+            if not first_set:
+                result.append(set_sep)
+            first_set = False
+
+            result.append(set_start)
+            match = msg.is_match()
+            next_indent = indent
+
+            if (match or entire_thread):
+                if format.lower() == "text":
+                    result.append(msg.format_message_as_text(indent))
+                else:
+                    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().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
+
+    def __next__(self):
+        raise StopIteration()
+    next = __next__
index 15ed153e94e49cc4c8fa6d9f7d8ead0dc5052a72..b669a3ef09141cb9f6f04c5e2080647a3b45a548 100644 (file)
@@ -30,7 +30,7 @@ from notmuch.globals import (
     NotInitializedError,
 )
 from notmuch.thread import Threads
     NotInitializedError,
 )
 from notmuch.thread import Threads
-from notmuch.message import Messages
+from .messages import Messages
 
 
 class Query(object):
 
 
 class Query(object):
index d1ba3e55be4d771ccb83614264deb01425cfc4f6..c599bcbf647d4f625a10c71f0aeaa8b7ff2dc60b 100644 (file)
@@ -28,7 +28,7 @@ from notmuch.globals import (
     NotmuchMessagesP,
     NotmuchTagsP,
 )
     NotmuchMessagesP,
     NotmuchTagsP,
 )
-from notmuch.message import Messages
+from .messages import Messages
 from notmuch.tag import Tags
 from datetime import date
 
 from notmuch.tag import Tags
 from datetime import date