from __future__ import unicode_literals
import logging
import string
import sys
ERRORS_HEADING = 'Errors'
class MissingRecipient(Exception):
[docs] def __init__(self, comment = None):
detail_msg = ''
if comment:
detail_msg = " {}".format(comment)
super(MissingRecipient, self).__init__(
"No recipients specified, but are required.{0}".format(detail_msg)
)
class BadRecipient(Exception):
[docs] """Base exception for issues with recipients. ``RecipientChecker``
implementations can either use this directly or subclass it to provide
more information.
"""
def __init__(self, recipient, msg_tpl):
"""Create a new instance.
Parameters
----------
recipient : str
The recipient which is bad in some manner. Will be made available
via the property of the same name.
msg_tpl : str
A format string for the message of the exception. Will have
the recioient value formatted into it, and the result passed
to the super constructor.
"""
super(BadRecipient, self).__init__(msg_tpl.format(recipient))
self._recipient = recipient
@property
def recipient(self):
return self._recipient
class PreviewFormatter(string.Formatter):
[docs] @staticmethod
def format_section(heading, content):
def list_or_none(l, template = '{}'):
[docs] if l:
return template.format(', '.join(sorted(l)))
else:
return None
"A template previewer"
def __init__(self, template_factory, recipient_checker, writer,
valid_placeholders = None):
"""
Parameters
----------
template_factory : callable(name)
Will be passed the name of a template, should return an
``EmailTemplate`` instance.
recipient_checker : RecipientChecker
Object to provide information about the recipients. See the
doc-comments on that class for details.
writer : file object
Used to output the preview of each item.
valid_placeholders : list, optional
A list of valid placeholders. If not passed then all placeholders
are considered valid.
"""
self._logger = logging.getLogger('libfritter.previewer')
self._template_factory = template_factory
self._recipient_checker = recipient_checker
self._writer = writer
self._valid_placeholders = set(valid_placeholders or [])
def preview_data(self, template_name):
[docs] """
Returns the gathered data, as a list of content tuples ready to be output.
Parameters
----------
template_name : str
The name of the template to get the preview data for, will be
passed to the factory callable the instance was created around.
"""
self._logger.debug("Getting preview data for '%s'.", template_name)
try:
et = self._template_factory(template_name)
recipients, recipient_errors = self._get_recipients(et.recipient)
body, used_placeholders, body_error = self._get_body(et)
if self._valid_placeholders:
placeholders = [
('Restricted to', self.list_or_none(self._valid_placeholders)),
('Used', used_placeholders),
]
else:
placeholders = used_placeholders
items = [
('To', recipients),
('Subject', et.subject),
('Body', body),
('Placeholders', placeholders),
]
errors = []
if recipient_errors:
errors += recipient_errors
if body_error:
errors.append(body_error)
if errors:
error_msg = "\n* ".join("{}".format(e) for e in errors)
items.append( (ERRORS_HEADING, '* ' + error_msg) )
return items
except Exception as e:
self._logger.exception("Getting preview data for '%s'.", template_name)
return [(ERRORS_HEADING, e)]
def preview(self, template_name, writer = None):
[docs] """
Writes a text preview of the template into the given writer.
Parameters
----------
template_name : str
The name of the template to get the preview data for, will be
passed to the factory callable the instance was created around.
writer : file object, optional
Used to output the preview of each item in preference to the
writer given to the instance when it was created.
Returns
-------
str or None
The content of the errors section, if any.
"""
if not writer:
writer = self._writer
errors_value = None
for name, value in self.preview_data(template_name):
content = value
if isinstance(value, list):
content = "".join(self.format_section(n, v) for n, v in value)
# Strip the final newline since one gets added below as well
if content:
assert content[-1] == "\n"
content = content[:-1]
content = self.format_section(name, content)
if sys.version_info[0] < 3:
# Python 2 writers can't deal with unicode characters
content = content.encode('utf-8')
writer.write(content)
if name == ERRORS_HEADING:
errors_value = "{}".format(value)
return errors_value
def _get_body(self, email_template):
formatter = PreviewFormatter(self._valid_placeholders)
try:
body = formatter.format(email_template.raw_body)
except Exception as e:
self._logger.exception("Getting body for '%s'.", email_template)
return None, None, e
required_keys = self.list_or_none(formatter.used_keys)
bad_keys = self.list_or_none(formatter.invalid_keys, "Invalid placeholder(s): {}.")
return body, required_keys, bad_keys
def _get_recipients(self, recipient_list):
if not recipient_list:
try:
self._recipient_checker.no_recipient()
return None, None
except MissingRecipient as mr:
return None, [mr]
descriptions = []
errors = []
for r in recipient_list:
try:
desc = self._recipient_checker.describe(r)
descriptions.append(desc)
except BadRecipient as e:
errors.append(e)
descriptions_str = None
if descriptions:
descriptions_str = ', '.join(descriptions)
return descriptions_str, errors