首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python的poplib.POP3和poplib.POP3_SSL包装器

Python的poplib.POP3和poplib.POP3_SSL包装器
EN

Code Review用户
提问于 2018-02-14 16:26:32
回答 1查看 626关注 0票数 8

我不时地使用Python的poplib模块来检查/操作POP3邮箱,通常是交互的。然而,我发现接口非常麻烦,所以我决定尝试制作一个更容易使用的包装器。我没有试图使它完成,而是它基本上只是做我需要它做的。

我对此很满意(尽管建设性的批评总是受欢迎的),但有一件事让我觉得有点奇怪。我没有多少写上下文经理的经验。我认为让Pop3类本身成为上下文管理器通常要简单得多,但是我创建了两个上下文管理器,而不是Pop3类,因为这允许我用最少的重复代码处理连接的代码。也许还有更好的方法?

代码语言:javascript
复制
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上找到代码

EN

回答 1

Code Review用户

回答已采纳

发布于 2018-02-15 16:23:24

干得好,这是一些非常干净的代码。有一些小的东西你可以改进,但在大多数情况下,这是可读的和重点。我将先讨论一些较小的问题,然后讨论你的主要问题。

  • 模块级的文档字符串应该位于源代码文件的最顶端。这也清楚地表明,它属于整个模块,并不是特定代码的附加文档。
  • "%"-formatting已经过时了,可能是一种痛苦,而且(可以说)很难读懂。幸运的是,有str.format,它解决了这些问题。
  • 行不应超过79个字符,根据PEP8 8。某些场景允许您打破这条规则,比如使用多行字符串,或者在长行包装会损害可读性的情况下。非正式地说,限制通常是80个或100个字符。

我认为让Pop3类本身成为上下文管理器通常要简单得多,但是我创建了两个上下文管理器,而不是Pop3类,因为这允许我用最少的重复代码处理连接的代码。也许还有更好的方法?

严格地说,不需要connectconnect_ssl,所以您应该取消它们,让Pop3实现上下文管理器协议。

另一方面,我对当前的实现没有什么奇怪的地方(尽管如果您真的想过火,您应该创建一个Pop3SSL类以避免违反单一责任原则),只要它适合您,就保持它的原样吧!

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/187576

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档