From: Christian Herdtweck Date: Thu, 14 Nov 2019 15:24:41 +0000 (+0100) Subject: Create ImapMailbox.fetch_{mail,message} X-Git-Tag: v1.6.3~1 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=6535c3f0e4a6f69f77e59ed065d93ba5fe16b6f7;p=pyi2ncommon Create ImapMailbox.fetch_{mail,message} --- diff --git a/src/imap_mailbox.py b/src/imap_mailbox.py index 7b79f1f..a27f612 100644 --- a/src/imap_mailbox.py +++ b/src/imap_mailbox.py @@ -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)