Source code for gwrappy.gmail.utils

import os
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
import mimetypes
from email.utils import COMMASPACE
import base64

from gwrappy.utils import datetime_to_timestamp


[docs]def generate_q(**kwargs): """ Generate query for searching messages. [https://support.google.com/mail/answer/7190] :keyword kwargs: Key-Value pairs. Descriptive flags like *has* or *is* can take lists. Otherwise, list values would be interpreted as "OR". :return: String representation of search q """ parsed_q = [] for k, v in kwargs.items(): if k in ('has', 'is'): if isinstance(v, list): for element in v: parsed_q.append('%s:%s' % (k, element)) else: parsed_q.append('%s:%s' % (k, v)) elif k in ('before', 'after'): parsed_q.append('%s:%s' % (k, datetime_to_timestamp(v))) else: if isinstance(v, list): parsed_q.append( ' OR '.join(['%s:%s' % (k, element) for element in v]) ) else: parsed_q.append('%s:%s' % (k, v)) return None if len(parsed_q) == 0 else ' '.join(parsed_q)
def create_message(sender, to, subject, message_text, attachment_paths=None): def __generate_msg_part(part): assert isinstance(part, dict) assert 'text' in part if 'type' not in part: msg_type = 'plain' else: msg_type = part['type'] mime_part = MIMEText( part['text'].encode('utf-8'), _subtype=msg_type, _charset='utf-8' ) return mime_part if not isinstance(to, list): to = [to] if message_text is None or isinstance(message_text, (unicode, str)): msgs = [MIMEText(message_text.encode('utf-8'), _charset='utf-8')] elif isinstance(message_text, dict): msgs = [__generate_msg_part(message_text)] elif isinstance(message_text, list): msgs = [__generate_msg_part(message_part) for message_part in message_text] else: raise TypeError('Types accepted for message_text: string, dict, or list of dicts') message = MIMEMultipart() message['to'] = COMMASPACE.join(to) message['from'] = sender message['subject'] = subject # separate part for text etc message_alt = MIMEMultipart('alternative') message.attach(message_alt) # html portions have to be under a separate 'related' part under 'alternative' part # sequence matters, text > related (html > inline image) > attachments. ascending priority # if message text is a list, it's providing alternatives. # eg. if both plain and html are available, Gmail will choose HTML over plain # attach text first (lower priority) for msg in msgs: if msg.get_content_subtype() == 'plain': message_alt.attach(msg) # create 'related' part if html is required content_msgs = filter(lambda x: x.get_content_subtype() == 'html' or x.get_content_maintype() == 'image', msgs) if len(content_msgs) > 0: message_related = MIMEMultipart('related') message_alt.attach(message_related) for msg in content_msgs: message_related.attach(msg) # different treatment if contains attachments if attachment_paths is not None: if isinstance(attachment_paths, str): attachment_paths = [attachment_paths] elif not isinstance(attachment_paths, list): raise TypeError('Invalid input. Only acceptable types are str and list objects') for file_path in attachment_paths: content_type, encoding = mimetypes.guess_type(file_path) if content_type is None or encoding is not None: content_type = 'application/octet-stream' main_type, sub_type = content_type.split('/', 1) with open(file_path, 'rb') as fp: if main_type == 'text': msg = MIMEText(fp.read(), _subtype=sub_type) elif main_type == 'image': msg = MIMEImage(fp.read(), _subtype=sub_type) else: msg = MIMEBase(main_type, sub_type) msg.set_payload(fp.read()) msg.add_header('Content-Disposition', 'attachment', filename=os.path.basename(file_path)) message.attach(msg) return {'raw': base64.urlsafe_b64encode(message.as_string())}
[docs]def list_to_html(data, has_header=True, table_format=None): """ Convenience function to convert tables to html for attaching as message text. :param data: Table data :type data: list of lists :param has_header: Flag whether data contains a header in the first row. :type has_header: boolean :param table_format: Dictionary representation of formatting for table elements. Eg. {'table': "border: 2px solid black;"} :type table_format: dictionary :return: String representation of HTML. """ from tabulate import tabulate if has_header: header = data.pop(0) else: header = () table_html = tabulate(data, headers=header, tablefmt='html', numalign='left') if table_format is not None: if isinstance(table_format, str) and table_format.lower() == 'default': table_format = { 'table': "width: 100%; border-collapse: collapse; border: 2px solid black;", 'th': "border: 2px solid black;", 'td': "border: 1px solid black;" } if isinstance(table_format, dict): assert all([key in ('table', 'th', 'tr', 'td') for key in table_format.keys()]) for k, v in table_format.items(): table_html = table_html.replace('<%s>' % k, '<%s style="%s">' % (k, v)) return table_html