From 1fbb8511b97c4d230d0d1cf8b6a9b031053f204d Mon Sep 17 00:00:00 2001 From: zevav Date: Fri, 27 Jul 2018 14:31:21 -0400 Subject: [PATCH] added `authentication_error_message` as a class attribute to `Imbox`. If `Imbox.vendor` is not None, look for a custom authentication error string, and raise it if there's a problem logging in. The default error value is raised if there is no custom string. created `name_authentication_string_dict` from all subclasses of `Messages` in `vendors/__init__.py`, for the lookup mentioned above. Implement architecture discussed in #131, deciding whether to use `Messages` or a subclass of it in `Imbox.messages` based on the `Imbox.vendor` value. Use the `folder_lookup` dict, present on `Messages` but only filled in on its subclasses, to select the right folder based on a lowercased "standard" name given as the value of `folder`. created a blank `folder_lookup` on `Messages`. filled in the first vendor that subclasses `Messages`, `GmailMessages`. It contains class attributes `authentication_error_message`, `hostname`, `name`, used for purposes described above, as well as `folder_lookup` which is a dict containing aliases, sometimes several, for Gmail's unique folder naming scheme. These aliases are meant to be "human-friendly" for intuitive `folder` selection in calls to `Imbox.messages`. fixes #131. --- imbox/imbox.py | 32 +++++++++++++++++++++++--------- imbox/messages.py | 2 ++ imbox/vendors/__init__.py | 7 +++++-- imbox/vendors/gmail.py | 23 +++++++++++++++++++++++ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/imbox/imbox.py b/imbox/imbox.py index f8dfc14..89fa616 100644 --- a/imbox/imbox.py +++ b/imbox/imbox.py @@ -1,15 +1,19 @@ +import imaplib + from imbox.imap import ImapTransport from imbox.messages import Messages import logging -from imbox.vendors import GmailMessages, hostname_vendorname_dict +from imbox.vendors import GmailMessages, hostname_vendorname_dict, name_authentication_string_dict logger = logging.getLogger(__name__) class Imbox: + authentication_error_message = None + def __init__(self, hostname, username=None, password=None, ssl=True, port=None, ssl_context=None, policy=None, starttls=False, vendor=None): @@ -21,10 +25,20 @@ class Imbox: self.username = username self.password = password self.parser_policy = policy - self.connection = self.server.connect(username, password) + self.vendor = vendor or hostname_vendorname_dict.get(self.hostname) + + if self.vendor is not None: + self.authentication_error_message = name_authentication_string_dict.get(self.vendor) + + try: + self.connection = self.server.connect(username, password) + except imaplib.IMAP4.error as e: + if self.authentication_error_message is None: + raise + raise imaplib.IMAP4.error(self.authentication_error_message + '\n' + str(e)) + logger.info("Connected to IMAP Server with user {username} on {hostname}{ssl}".format( hostname=hostname, username=username, ssl=(" over SSL" if ssl or starttls else ""))) - self.vendor = vendor or hostname_vendorname_dict.get(self.hostname) def __enter__(self): return self @@ -62,19 +76,19 @@ class Imbox: def messages(self, **kwargs): folder = kwargs.get('folder', False) + messages_class = Messages + + if self.vendor == 'gmail': + messages_class = GmailMessages + if folder: - self.connection.select(folder) + self.connection.select(messages_class.folder_lookup.get((folder.lower())) or folder) msg = " from folder '{}'".format(folder) else: msg = " from inbox" logger.info("Fetch list of messages{}".format(msg)) - messages_class = Messages - - if self.vendor == 'gmail': - messages_class = GmailMessages - return messages_class(connection=self.connection, parser_policy=self.parser_policy, **kwargs) diff --git a/imbox/messages.py b/imbox/messages.py index 3d44f42..baefae7 100644 --- a/imbox/messages.py +++ b/imbox/messages.py @@ -8,6 +8,8 @@ logger = logging.getLogger(__name__) class Messages: + folder_lookup = {} + def __init__(self, connection, parser_policy, diff --git a/imbox/vendors/__init__.py b/imbox/vendors/__init__.py index f983626..da8a6b9 100644 --- a/imbox/vendors/__init__.py +++ b/imbox/vendors/__init__.py @@ -1,7 +1,10 @@ from imbox.vendors.gmail import GmailMessages +vendors = [GmailMessages] -hostname_vendorname_dict = {GmailMessages.hostname: GmailMessages.name} +hostname_vendorname_dict = {vendor.hostname: vendor.name for vendor in vendors} +name_authentication_string_dict = {vendor.name: vendor.authentication_error_message for vendor in vendors} __all__ = ['GmailMessages', - 'hostname_vendorname_dict'] \ No newline at end of file + 'hostname_vendorname_dict', + 'name_authentication_string_dict'] \ No newline at end of file diff --git a/imbox/vendors/gmail.py b/imbox/vendors/gmail.py index a9dfa4b..97c97d9 100644 --- a/imbox/vendors/gmail.py +++ b/imbox/vendors/gmail.py @@ -2,5 +2,28 @@ from imbox.messages import Messages class GmailMessages(Messages): + authentication_error_message = ('If you\'re not using an app-specific password, grab one here: ' + 'https://myaccount.google.com/apppasswords') hostname = 'imap.gmail.com' name = 'gmail' + folder_lookup = { + + 'all_mail': '"[Gmail]/All Mail"', + 'all': '"[Gmail]/All Mail"', + 'all mail': '"[Gmail]/All Mail"', + 'sent': '"[Gmail]/Sent Mail"', + 'sent mail': '"[Gmail]/Sent Mail"', + 'sent_mail': '"[Gmail]/Sent Mail"', + 'drafts': '"[Gmail]/Drafts"', + 'important': '"[Gmail]/Important"', + 'spam': '"[Gmail]/Spam"', + 'starred': '"[Gmail]/Starred"', + 'trash': '"[Gmail]/Trash"', + + } + + def __init__(self, + connection, + parser_policy, + **kwargs): + super().__init__(connection, parser_policy, **kwargs)