我不时地使用Python的poplib模块来检查/操作POP3邮箱,通常是交互的。然而,我发现接口非常麻烦,所以我决定尝试制作一个更容易使用的包装器。我没有试图使它完成,而是它基本上只是做我需要它做的。
我对此很满意(尽管建设性的批评总是受欢迎的),但有一件事让我觉得有点奇怪。我没有多少写上下文经理的经验。我认为让Pop3类本身成为上下文管理器通常要简单得多,但是我创建了两个上下文管理器,而不是Pop3类,因为这允许我用最少的重复代码处理连接的代码。也许还有更好的方法?
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()您还可以在https://gitlab.com/roelschroeven-unixtools/pop3tool上找到代码
发布于 2018-02-15 16:23:24
干得好,这是一些非常干净的代码。有一些小的东西你可以改进,但在大多数情况下,这是可读的和重点。我将先讨论一些较小的问题,然后讨论你的主要问题。
str.format,它解决了这些问题。我认为让Pop3类本身成为上下文管理器通常要简单得多,但是我创建了两个上下文管理器,而不是Pop3类,因为这允许我用最少的重复代码处理连接的代码。也许还有更好的方法?
严格地说,不需要connect和connect_ssl,所以您应该取消它们,让Pop3实现上下文管理器协议。
另一方面,我对当前的实现没有什么奇怪的地方(尽管如果您真的想过火,您应该创建一个Pop3SSL类以避免违反单一责任原则),只要它适合您,就保持它的原样吧!
https://codereview.stackexchange.com/questions/187576
复制相似问题