Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created Message class to encapsulate the "messages" generator #129

Merged
merged 4 commits into from
Jul 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ nosetests.xml

example.*
example.py

# PyCharm
.idea/
89 changes: 62 additions & 27 deletions imbox/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from imbox.imap import ImapTransport
from imbox.parser import parse_email
from imbox.parser import parse_email, fetch_email_by_uid
from imbox.query import build_search_query

import logging

logger = logging.getLogger(__name__)

__version_info__ = (0, 9, 5)
Expand Down Expand Up @@ -36,29 +37,6 @@ def logout(self):
logger.info("Disconnected from IMAP Server {username}@{hostname}".format(
hostname=self.hostname, username=self.username))

def query_uids(self, **kwargs):
query = build_search_query(**kwargs)
message, data = self.connection.uid('search', None, query)
if data[0] is None:
return []
return data[0].split()

def fetch_by_uid(self, uid):
message, data = self.connection.uid('fetch', uid, '(BODY.PEEK[])')
logger.debug("Fetched message for UID {}".format(int(uid)))
raw_email = data[0][1]

email_object = parse_email(raw_email, policy=self.parser_policy)

return email_object

def fetch_list(self, **kwargs):
uid_list = self.query_uids(**kwargs)
logger.debug("Fetch all messages for UID in {}".format(uid_list))

for uid in uid_list:
yield (uid, self.fetch_by_uid(uid))

def mark_seen(self, uid):
logger.info("Mark UID {} with \\Seen FLAG".format(int(uid)))
self.connection.uid('STORE', uid, '+FLAGS', '(\\Seen)')
Expand All @@ -69,7 +47,6 @@ def mark_flag(self, uid):

def delete(self, uid):
logger.info("Mark UID {} with \\Deleted FLAG and expunge.".format(int(uid)))
mov, data = self.connection.uid('STORE', uid, '+FLAGS', '(\\Deleted)')
self.connection.expunge()

def copy(self, uid, destination_folder):
Expand All @@ -81,7 +58,7 @@ def move(self, uid, destination_folder):
if self.copy(uid, destination_folder):
self.delete(uid)

def messages(self, *args, **kwargs):
def messages(self, **kwargs):
folder = kwargs.get('folder', False)
msg = ""

Expand All @@ -90,7 +67,65 @@ def messages(self, *args, **kwargs):
msg = " from folder '{}'".format(folder)

logger.info("Fetch list of messages{}".format(msg))
return self.fetch_list(**kwargs)
return Messages(connection=self.connection,
parser_policy=self.parser_policy,
**kwargs)

def folders(self):
return self.connection.list()


class Messages:

def __init__(self,
connection,
parser_policy,
**kwargs):

self.connection = connection
self.parser_policy = parser_policy
self.kwargs = kwargs
self._uid_list = self._query_uids(**kwargs)

logger.debug("Fetch all messages for UID in {}".format(self._uid_list))

def _fetch_email(self, uid):
return fetch_email_by_uid(uid=uid,
connection=self.connection,
parser_policy=self.parser_policy)

def _query_uids(self, **kwargs):
query_ = build_search_query(**kwargs)
message, data = self.connection.uid('search', None, query_)
if data[0] is None:
return []
return data[0].split()

def _fetch_email_list(self):
for uid in self._uid_list:
yield uid, self._fetch_email(uid)

def __repr__(self):
if len(self.kwargs) > 0:
return 'Messages({})'.format('\n'.join('{}={}'.format(key, value)
for key, value in self.kwargs.items()))
return 'Messages(ALL)'

def __iter__(self):
return self._fetch_email_list()

def __next__(self):
return self

def __len__(self):
return len(self._uid_list)

def __getitem__(self, index):
uids = self._uid_list[index]

if not isinstance(uids, list):
uid = uids
return uid, self._fetch_email(uid)

return [(uid, self._fetch_email(uid))
for uid in uids]
14 changes: 4 additions & 10 deletions imbox/imap.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,16 @@ class ImapTransport:

def __init__(self, hostname, port=None, ssl=True, ssl_context=None, starttls=False):
self.hostname = hostname
self.port = port
kwargs = {}

if ssl:
self.transport = IMAP4_SSL
if not self.port:
self.port = 993
self.port = port or 993
if ssl_context is None:
ssl_context = pythonssllib.create_default_context()
kwargs["ssl_context"] = ssl_context
self.server = IMAP4_SSL(self.hostname, self.port, ssl_context=ssl_context)
else:
self.transport = IMAP4
if not self.port:
self.port = 143
self.port = port or 143
self.server = IMAP4(self.hostname, self.port)

self.server = self.transport(self.hostname, self.port, **kwargs)
if starttls:
self.server.starttls()
logger.debug("Created IMAP4 transport for {host}:{port}"
Expand Down
19 changes: 14 additions & 5 deletions imbox/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from imbox.utils import str_encode, str_decode

import logging

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -66,7 +67,7 @@ def decode_param(param):
values = v.split('\n')
value_results = []
for value in values:
match = re.search(r'=\?((?:\w|-)+)\?(Q|B)\?(.+)\?=', value)
match = re.search(r'=\?((?:\w|-)+)\?([QB])\?(.+)\?=', value)
if match:
encoding, type_, code = match.groups()
if type_ == 'Q':
Expand Down Expand Up @@ -127,6 +128,16 @@ def decode_content(message):
return content


def fetch_email_by_uid(uid, connection, parser_policy):
message, data = connection.uid('fetch', uid, '(BODY.PEEK[])')
logger.debug("Fetched message for UID {}".format(int(uid)))
raw_email = data[0][1]

email_object = parse_email(raw_email, policy=parser_policy)

return email_object


def parse_email(raw_email, policy=None):
if isinstance(raw_email, bytes):
raw_email = str_encode(raw_email, 'utf-8', errors='ignore')
Expand All @@ -140,9 +151,7 @@ def parse_email(raw_email, policy=None):
except UnicodeEncodeError:
email_message = email.message_from_string(raw_email.encode('utf-8'), **email_parse_kwargs)
maintype = email_message.get_content_maintype()
parsed_email = {}

parsed_email['raw_email'] = raw_email
parsed_email = {'raw_email': raw_email}

body = {
"plain": [],
Expand All @@ -162,7 +171,7 @@ def parse_email(raw_email, policy=None):
content = decode_content(part)

is_inline = content_disposition is None \
or content_disposition.startswith("inline")
or content_disposition.startswith("inline")
if content_type == "text/plain" and is_inline:
body['plain'].append(content)
elif content_type == "text/html" and is_inline:
Expand Down
3 changes: 1 addition & 2 deletions imbox/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
def format_date(date):
if isinstance(date, datetime.date):
return date.strftime('%d-%b-%Y')
else:
return date
return date


def build_search_query(**kwargs):
Expand Down
4 changes: 3 additions & 1 deletion imbox/utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import logging
logger = logging.getLogger(__name__)


def str_encode(value='', encoding=None, errors='strict'):
logger.debug("Encode str {} with and errors {}".format(value, encoding, errors))
return str(value, encoding, errors)


def str_decode(value='', encoding=None, errors='strict'):
if isinstance(value, str):
return bytes(value, encoding, errors).decode('utf-8')
elif isinstance(value, bytes):
return value.decode(encoding or 'utf-8', errors=errors)
else:
raise TypeError( "Cannot decode '{}' object".format(value.__class__) )
raise TypeError("Cannot decode '{}' object".format(value.__class__))