Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import email
- import email.utils
- import poplib
- from collections import OrderedDict
- from functools import lru_cache
- """
- Wrapper around poplib.POP3/poplib.POP3_SSL to make it easier to use. Doesn't (currently)
- offer all functionality of poplib, but it does what I need it to.
- Best used via the context manager connect() or connect_ssl():
- with pop3.connect_ssl(host, username, password) as p:
- for msg in p.messages.values(): # messages is a mapping: `num` -> `message`
- print(msg.summary)
- print(' to:', msg.headers['To'])
- """
- class Message:
- """Message represents a message on a POP3 server"""
- def __init__(self, pop3, num, size):
- """Initialize a new instance. Don't call this manually: this is only
- meant to be called by Pop3 instances."""
- self.pop3 = pop3
- self.num = num
- self.size = size
- self.marked_for_deletion = False
- def __repr__(self):
- return 'Message(num=%d, size=%d)' % (self.num, self.size)
- def __str__(self):
- return self.summary
- @property
- @lru_cache()
- def headers(self):
- """The message's headers as an email.message instance"""
- return self.pop3._top(self.num, 0)
- @property
- @lru_cache()
- def email(self):
- """The complete email as an email.message instance"""
- return self.pop3._retr(self.num)
- @property
- def summary(self):
- """Summary line: deletion flag, message number, from, date, subject"""
- return '%s%-3d %-20s %-25s %s' % (
- '*' if self.marked_for_deletion else ' ',
- self.num,
- self.headers['From'],
- email.utils.parsedate_to_datetime(self.headers['Date']).isoformat(sep=' '),
- self.headers['Subject'],
- )
- def mark_for_deletion(self):
- """Mark this message for deletion. The server will delete the message
- when the connection is closed."""
- self.pop3._mark_for_deletion(self.num)
- class Pop3:
- """Connection to a POP3 mailbox (simple wrapper around poplib.POP3 or poplib.POP3_SSL)
- Attribute `messages` holds the messages in the mailbox, as follows:
- Messages are represented by Message instances. Each message has an attribute num which
- is used to uniquely identify the message when communication with the server.
- `messages` is an OrderedDict indexed on `num`.
- Use e.g. `messages.values()` to get all messages.
- """
- def __init__(self, poplib_pop3, username, password):
- """Initialize a new instance. This is normally called from the connect or
- connect_ssl context managers.
- poplib_pop3: pre-made poplib.POP3 or poplib.POP3_SSL instance
- username: username
- password: password
- """
- self.poplib_pop3 = poplib_pop3
- self.poplib_pop3.user(username)
- self.poplib_pop3.pass_(password)
- self.messages = self._request_list()
- def _request_list(self):
- """Request the list of messages from the server"""
- response, msg_infos, size = self.poplib_pop3.list()
- messages = OrderedDict()
- for msg_info in msg_infos:
- msg_num_string, size_string = msg_info.split()
- msg_num = int(msg_num_string)
- size = int(size_string)
- messages[msg_num] = Message(self, msg_num, size)
- return messages
- @property
- def greeting(self):
- """Server greeting"""
- return self.poplib_pop3.getwelcome()
- def _mark_for_deletion(self, num):
- """Mark message <num> for deletion"""
- if num not in self.messages:
- raise KeyError('Invalid message number %d' % num)
- self.poplib_pop3.dele(num)
- self.messages[num].marked_for_deletion = True
- def _email_from_lines(self, lines):
- """Parse email as email.message from lines as we get them from the server"""
- # lines as we get them from the poplib module are bytestrings, but the
- # email module needs a string. Which codec to use? Depends on the
- # encoding specified in the headers, I would think, but we don't know
- # that yet.
- # Use UTF-8 for now ...
- message = ''.join(line.decode('UTF-8') + 'n' for line in lines)
- return email.message_from_string(message)
- def _top(self, num, nr_lines_extra):
- """Retrieve header + nr_lines_extra lines from server as an email.message instance"""
- if num not in self.messages:
- raise KeyError('Invalid message number %d' % num)
- response, lines, size = self.poplib_pop3.top(num, nr_lines_extra)
- return self._email_from_lines(lines)
- def _retr(self, num):
- """Retrieve message <num> from server as an email.message instance"""
- if num not in self.messages:
- raise KeyError('Invalid message number %d' % num)
- response, lines, size = self.poplib_pop3.retr(num)
- return self._email_from_lines(lines)
- def reset_deletion_marks(self):
- """Reset all deletion marks"""
- self.poplib_pop3.rset()
- def close(self):
- """Close the connection. Normally handed for you by the context manager."""
- self.poplib_pop3.quit()
- class connect:
- """Context manager for Pop3 without SSL"""
- def __init__(self, host, username, password, port=poplib.POP3_PORT, timeout=None):
- self.pop3 = Pop3(
- poplib.POP3(host, port, timeout),
- username,
- password
- )
- def __enter__(self):
- return self.pop3
- def __exit__(self, exc_type, exc_value, traceback):
- self.pop3.close()
- class connect_ssl:
- """Context manager for Pop3 with SSL"""
- def __init__(self, host, username, password, port=poplib.POP3_SSL_PORT, keyfile=None, certfile=None, timeout=None, context=None):
- self.pop3 = Pop3(
- poplib.POP3_SSL(host, port, keyfile, certfile, timeout, context),
- username,
- password
- )
- def __enter__(self):
- return self.pop3
- def __exit__(self, exc_type, exc_value, traceback):
- self.pop3.close()
Add Comment
Please, Sign In to add comment