Create ImapMailbox.fetch_{mail,message}
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Thu, 14 Nov 2019 15:24:41 +0000 (16:24 +0100)
committerPlamen Dimitrov <pdimitrov@pevogam.com>
Tue, 19 Nov 2019 09:04:02 +0000 (11:04 +0200)
src/imap_mailbox.py

index 7b79f1f..a27f612 100644 (file)
@@ -33,6 +33,8 @@ Features provided in addition to :py:class:`imaplib.IMAP4`::
   * debug logging to console/file
   * memory of which folder is currently selected
   * function to copy/move an email from one folder to another
+  * deal with some of the variability of returned values from `fetch` command
+  * created email message objects from byte data
 
 Copyright: Intra2net AG
 
@@ -45,6 +47,8 @@ INTERFACE
 import imaplib
 import logging
 import time
+from email import policy
+from email import message_from_bytes
 
 log = logging.getLogger('pyi2ncommon.imap_mailbox')
 
@@ -70,7 +74,7 @@ def quote_imap_folder(folder):
 
     :param str folder: Name of a folder in an IMAP mailbox, possibly quoted
     :returns: same folder name, possibly with added quotes
-    :rtype: byte
+    :rtype: bytes
     """
     if not folder:
         return folder
@@ -189,7 +193,7 @@ class ImapMailbox(imaplib.IMAP4):
         :param str user: User name to use for login. Overrides the one given in
                          constructor. Must be given if not given to constructor
         :param str password: Password for login. Same restrictions as user
-        :returns: Whatever imap login returns.
+        :returns: first return item from imap login.
         """
         if user is not None:
             self.user = user
@@ -205,7 +209,7 @@ class ImapMailbox(imaplib.IMAP4):
         if typ != 'OK':
             raise ImapError('login', typ, data)
         self._select('INBOX')
-        return data
+        return data[0]
 
     def logout(self):
         """Log out of mailbox. Returns data returned by imap logout."""
@@ -277,3 +281,52 @@ class ImapMailbox(imaplib.IMAP4):
             typ, _ = self.expunge()
             if typ != 'OK':
                 raise ImapError('expunge', typ, data)
+
+    def fetch_mail(self, message_id, folder=None, part='RFC822'):
+        """
+        Fetch and return an email in RFC822 format.
+
+        .. seealso:: :py:meth:`fetch_message`
+
+        :param int message_id: Message id in given folder
+        :param folder: Folder that contains the message or None (default) if
+                       folder is already selected.
+        :type folder: str or None
+        :param str part: Message part to fetch. For simplicity of return args,
+                         only one part can be fetched at a time. Other possible
+                         values: ``UID``, ``BODY[TEXT]``, ...
+        :returns: requested message part
+        :rtype: bytes
+        """
+        self._select(folder)
+        self._clear_unsolicited_responses('SEARCH')
+        typ, data = super(ImapMailbox, self).fetch(str(message_id), part)
+        if typ != 'OK':
+            raise ImapError('fetch', typ, data)
+
+        # have to be flexible with returned data here...
+        # should be a list of len 1 of 2-tuples, but reality differs
+        if len(data) == 2 and data[1] == b')':
+            data = data[:1]
+        if len(data) != 1:
+            raise ImapError('fetch', typ,
+                            'Data has not len 1 but {}'.format(len(data)))
+        data = data[0]
+        if part.lower() == 'uid' and isinstance(data, bytes):
+            return data[0]
+        if len(data) != 2:
+            raise ImapError('fetch', typ,
+                            'Data[0] has len {} != 2'.format(len(data)))
+        return data[1]
+
+    def fetch_message(self, message_id, folder=None):
+        """
+        Fetch complete message and convert to message object.
+
+        All params are forwarded to :py:meth:`fetch_mail`. Afterwards, the
+        resulting data is parsed into a :py:class:`email.message.EmailMessage`
+        object (for python versions < 3.4: :py:class:`email.message.Message`).
+        """
+        return message_from_bytes(
+            self.fetch_mail(message_id, folder, part='RFC822'),
+            policy=policy.default + policy.strict)