Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
lavr committed Feb 23, 2015
1 parent 336807b commit 73b1b19
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 157 deletions.
4 changes: 3 additions & 1 deletion emails/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
"""

__title__ = 'emails'
__version__ = '0.1.12'
__version__ = '0.2'
__author__ = 'Sergey Lavrinenko'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2013-2015 Sergey Lavrinenko'

USER_AGENT = 'python-emails/%s' % __version__

from .message import Message, html
from .utils import MessageID

Expand Down
4 changes: 4 additions & 0 deletions emails/exc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# encoding: utf-8

class HTTPLoaderError(Exception):
pass
104 changes: 79 additions & 25 deletions emails/loader/__init__.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,99 @@
# encoding: utf-8
import os
import os.path
from emails.loader.helpers import guess_charset
from emails.compat import to_unicode
from emails.compat import urlparse
from emails import Message
from emails.utils import fetch_url
from emails.loader import local_store

def from_url(url, **kwargs):
from .loaders import URLLoader
loader = URLLoader(url, **kwargs)
loader.load()
return loader

def from_url(url, message_params=None, requests_params=None, **kwargs):

def _make_base_url(url):
# /a/b.html -> /a
p = list(urlparse.urlparse(url))[:5]
p[2] = os.path.split(p[2])[0]
return urlparse.urlunsplit(p)

# Load html page
r = fetch_url(url, requests_args=requests_params)
html = r.content
html = to_unicode(html, charset=guess_charset(r.headers, html))
html = html.replace('\r\n', '\n') # Remove \r

message_params = message_params or {}
message = Message(html=html, **message_params)
message.create_transformer(requests_params=requests_params,
base_url=_make_base_url(url))
message.transformer.load_and_transform(**kwargs)
message.transformer.save()
return message

load_url = from_url


def from_directory(directory, index_file=None, **kwargs):
from .loaders import DirectoryLoader
loader = DirectoryLoader(directory, index_file=index_file, **kwargs)
loader.load()
return loader
def from_directory(directory, index_file=None, message_params=None, **kwargs):

store = local_store.FileSystemLoader(searchpath=directory)
index_file_name = store.find_index_file(index_file)
dirname, _ = os.path.split(index_file_name)
if dirname:
store.base_path = dirname

message_params = message_params or {}
message = Message(html=store[index_file_name], **message_params)
message.create_transformer(local_loader=store, requests_params=kwargs.get('requests_params'))
message.transformer.load_and_transform(**kwargs)
message.transformer.save()
return message


def from_file(filename, **kwargs):
return from_directory(directory=os.path.dirname(filename), index_file=os.path.basename(filename), **kwargs)


def from_zip(zip_file, **kwargs):
from .loaders import ZipLoader
loader = ZipLoader(zip_file, **kwargs)
loader.load()
return loader
def from_zip(zip_file, message_params=None, **kwargs):
store = local_store.ZipLoader(file=zip_file)
index_file_name = store.find_index_file()
dirname, index_file_name = os.path.split(index_file_name)
if dirname:
store.base_path = dirname

message_params = message_params or {}
message = Message(html=store[index_file_name], **message_params)
message.create_transformer(local_loader=store, requests_params=kwargs.get('requests_params'))
message.transformer.load_and_transform(**kwargs)
message.transformer.save()
return message


def from_html(html, css_text=None, **kwargs):
from .loaders import StringLoader
loader = StringLoader(html, css_text=css_text, **kwargs)
loader.load()
return loader
def from_html(html, base_url=None, message_params=None, **kwargs):
message_params = message_params or {}
message = Message(html=html, **message_params)
message.create_transformer(requests_params=kwargs.get('requests_params'), base_url=base_url)
message.transformer.load_and_transform(**kwargs)
message.transformer.save()
return message

from_string = from_html


def from_rfc822(msg, **kwargs):
from .loaders import RFC822Loader
loader = RFC822Loader(msg, **kwargs)
loader.load()
return loader
def from_rfc822(msg, message_params=None, **kw):

store = local_store.MsgLoader(msg=msg)
text = store.get_source('__index.txt')
html = store.get_source('__index.html')

message_params = message_params or {}
message = Message(html=html, text=text, **message_params)
if html:
message.create_transformer(local_loader=store, **kw)
message.transformer.load_and_transform()
message.transformer.save()
else:
# TODO: add attachments for text-only message
pass

return message
4 changes: 1 addition & 3 deletions emails/loader/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@
from emails.compat import urlparse
from emails.transformer import Transformer
from emails.loader import local_store
from emails.exc import HTTPLoaderError
import requests
from .helpers import guess_charset


class HTTPLoaderError(Exception):
pass

class BaseLoader(object):

USER_AGENT = 'python-emails/1.1'
Expand Down
138 changes: 85 additions & 53 deletions emails/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,17 @@
from dateutil.parser import parse as dateutil_parse
from email.header import Header
from email.utils import formatdate, getaddresses

from emails.compat import string_types, to_unicode, is_callable, to_bytes

from .utils import SafeMIMEText, SafeMIMEMultipart, sanitize_address, parse_name_and_email
from .utils import (SafeMIMEText, SafeMIMEMultipart, sanitize_address, parse_name_and_email, load_email_charsets)
from .smtp import ObjectFactory, SMTPBackend
from .store import MemoryFileStore, BaseFile
from .signers import DKIMSigner

from .utils import load_email_charsets

load_email_charsets() # sic!




class BadHeaderError(ValueError):
pass

# Header names that contain structured address data (RFC #5322)
ADDRESS_HEADERS = set(['from', 'sender', 'reply-to', 'to', 'cc', 'bcc', 'resent-from', 'resent-sender', 'resent-to',
'resent-cc', 'resent-bcc'])


def renderable(f):
@wraps(f)
Expand Down Expand Up @@ -58,6 +47,11 @@ class BaseMessage(object):

ROOT_PREAMBLE = 'This is a multi-part message in MIME format.\n'

# Header names that contain structured address data (RFC #5322)
ADDRESS_HEADERS = set(['from', 'sender', 'reply-to', 'to', 'cc', 'bcc',
'resent-from', 'resent-sender', 'resent-to',
'resent-cc', 'resent-bcc'])

attachment_cls = BaseFile
filestore_cls = MemoryFileStore

Expand Down Expand Up @@ -119,12 +113,22 @@ def set_html(self, html, url=None):
self._html = html
self._html_url = url

def get_html(self):
return self._html

html = property(get_html, set_html)

def set_text(self, text, url=None):
if hasattr(text, 'read'):
text = text.read()
self._text = text
self._text_url = url

def get_text(self):
return self._text

text = property(get_text, set_text)

@classmethod
def from_loader(cls, loader, template_cls=None, **kwargs):
"""
Expand Down Expand Up @@ -219,7 +223,7 @@ def set_header(self, msg, key, value, encode=True):
if '\n' in value or '\r' in value:
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (value, key))

if key.lower() in ADDRESS_HEADERS:
if key.lower() in self.ADDRESS_HEADERS:
value = ', '.join(sanitize_address(addr, self.charset)
for addr in getaddresses((value,)))

Expand Down Expand Up @@ -293,53 +297,18 @@ def _build_message(self, message_cls=None):
return msg


class MessageSendMixin(object):


class Message(BaseMessage):
"""
Email class
"""

dkim_cls = DKIMSigner
smtp_pool_factory = ObjectFactory
smtp_cls = SMTPBackend

def __init__(self, **kwargs):
BaseMessage.__init__(self, **kwargs)
self._dkim_signer = None
self.after_build = None


def message(self, message_cls=None):
msg = self._build_message(message_cls=message_cls)
if self._dkim_signer:
msg_str = msg.as_string()
dkim_header = self._dkim_signer.get_sign_header(to_bytes(msg_str))
if dkim_header:
msg._headers.insert(0, dkim_header)
return msg

def as_string(self):
# self.as_string() is not equialent self.message().as_string()
# self.as_string() gets one less message-to-string conversions for dkim
msg = self._build_message()
r = msg.as_string()
if self._dkim_signer:
dkim_header = self._dkim_signer.get_sign(to_bytes(r))
if dkim_header:
r = dkim_header + r
return r

@property
def smtp_pool(self):
pool = getattr(self, '_smtp_pool', None)
if pool is None:
pool = self._smtp_pool = self.smtp_pool_factory(cls=self.smtp_cls)
return pool

def dkim(self, **kwargs):
self._dkim_signer = self.dkim_cls(**kwargs)

def send(self,
to=None,
set_mail_to=True,
Expand Down Expand Up @@ -402,6 +371,73 @@ def send(self,
return response[0]


class MessageTransformerMixin(object):

transformer_cls = None

def create_transformer(self, **kw):
cls = self.transformer_cls
if cls is None:
from emails.transformer import MessageTransformer
cls = MessageTransformer

self._transformer = cls(message=self, **kw)
return self._transformer

def destroy_transformer(self):
self._transformer = None

@property
def transformer(self):
t = getattr(self, '_transformer', None)
if t is None:
t = self.create_transformer()
return t


class Message(BaseMessage, MessageSendMixin, MessageTransformerMixin):
"""
Email message with:
- DKIM signer
- smtp send
- Message.transformer object
"""

dkim_cls = DKIMSigner

def __init__(self, **kwargs):
BaseMessage.__init__(self, **kwargs)
self._dkim_signer = None
self.after_build = None

def dkim(self, **kwargs):
self._dkim_signer = self.dkim_cls(**kwargs)

def set_html(self, **kw):
# When html set, remove old transformer
self.destroy_transformer()
super(Message, self).set_html(**kw)

def message(self, message_cls=None):
msg = self._build_message(message_cls=message_cls)
if self._dkim_signer:
msg_str = msg.as_string()
dkim_header = self._dkim_signer.get_sign_header(to_bytes(msg_str))
if dkim_header:
msg._headers.insert(0, dkim_header)
return msg

def as_string(self):
# self.as_string() is not equialent self.message().as_string()
# self.as_string() gets one less message-to-string conversions for dkim
msg = self._build_message()
r = msg.as_string()
if self._dkim_signer:
dkim_header = self._dkim_signer.get_sign(to_bytes(r))
if dkim_header:
r = dkim_header + r
return r


def html(**kwargs):
return Message(**kwargs)
Expand Down Expand Up @@ -435,7 +471,3 @@ def recipients(self):
def message(self):
self._message.render(**self._context)
return self._message.message()




Loading

0 comments on commit 73b1b19

Please sign in to comment.