3
0
Fork 0

fix: move email stuff from subpackage to module

This commit is contained in:
Lance Edgar 2024-12-19 18:34:03 -06:00
parent 902412322e
commit 6c8f1c973d
14 changed files with 244 additions and 331 deletions

View file

@ -1,6 +0,0 @@
``wuttjamaican.email.handler``
==============================
.. automodule:: wuttjamaican.email.handler
:members:

View file

@ -1,6 +0,0 @@
``wuttjamaican.email.message``
==============================
.. automodule:: wuttjamaican.email.message
:members:

View file

@ -189,7 +189,7 @@ Glossary
The :term:`handler` responsible for sending email on behalf of The :term:`handler` responsible for sending email on behalf of
the :term:`app`. the :term:`app`.
Default is :class:`~wuttjamaican.email.handler.EmailHandler`. Default is :class:`~wuttjamaican.email.EmailHandler`.
email key email key
String idenfier for a certain :term:`email type`. Each email key String idenfier for a certain :term:`email type`. Each email key

View file

@ -82,8 +82,6 @@ Contents
api/wuttjamaican.db.sess api/wuttjamaican.db.sess
api/wuttjamaican.db.util api/wuttjamaican.db.util
api/wuttjamaican.email api/wuttjamaican.email
api/wuttjamaican.email.handler
api/wuttjamaican.email.message
api/wuttjamaican.enum api/wuttjamaican.enum
api/wuttjamaican.exc api/wuttjamaican.exc
api/wuttjamaican.install api/wuttjamaican.install

View file

@ -50,9 +50,8 @@ for one or both of the ``text/plain`` and ``text/html`` content-types
(using ``txt`` and ``html`` as shorthand name, respectively). (using ``txt`` and ``html`` as shorthand name, respectively).
Template files must use the :doc:`Mako template language <mako:index>` Template files must use the :doc:`Mako template language <mako:index>`
and be named based on the and be named based on the :attr:`~wuttjamaican.email.Message.key` for
:attr:`~wuttjamaican.email.message.Message.key` for the email type, as the email type, as well as content-type.
well as content-type.
Therefore a new email of type ``poser_alert_foo`` would need one or Therefore a new email of type ``poser_alert_foo`` would need one or
both of these defined: both of these defined:

View file

@ -768,7 +768,7 @@ class AppHandler:
See also :meth:`send_email()`. See also :meth:`send_email()`.
:rtype: :class:`~wuttjamaican.email.handler.EmailHandler` :rtype: :class:`~wuttjamaican.email.EmailHandler`
""" """
if 'email' not in self.handlers: if 'email' not in self.handlers:
spec = self.config.get(f'{self.appname}.email.handler', spec = self.config.get(f'{self.appname}.email.handler',
@ -823,7 +823,7 @@ class AppHandler:
Send an email message. Send an email message.
This is a convenience wrapper around This is a convenience wrapper around
:meth:`~wuttjamaican.email.handler.EmailHandler.send_email()`. :meth:`~wuttjamaican.email.EmailHandler.send_email()`.
""" """
self.get_email_handler().send_email(*args, **kwargs) self.get_email_handler().send_email(*args, **kwargs)

View file

@ -26,15 +26,146 @@ Email Handler
import logging import logging
import smtplib import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from wuttjamaican.app import GenericHandler from wuttjamaican.app import GenericHandler
from wuttjamaican.util import resource_path from wuttjamaican.util import resource_path
from wuttjamaican.email.message import Message
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Message:
"""
Represents an email message to be sent.
:param to: Recipient(s) for the message. This may be either a
string, or list of strings. If a string, it will be converted
to a list since that is how the :attr:`to` attribute tracks it.
Similar logic is used for :attr:`cc` and :attr:`bcc`.
All attributes shown below may also be specified via constructor.
.. attribute:: key
Unique key indicating the "type" of message. An "ad-hoc"
message created arbitrarily may not have/need a key; however
one created via
:meth:`~wuttjamaican.email.EmailHandler.make_auto_message()`
will always have a key.
This key is not used for anything within the ``Message`` class
logic. It is used by
:meth:`~wuttjamaican.email.EmailHandler.make_auto_message()`
when constructing the message, and the key is set on the final
message only as a reference.
.. attribute:: sender
Sender (``From:``) address for the message.
.. attribute:: subject
Subject text for the message.
.. attribute:: to
List of ``To:`` recipients for the message.
.. attribute:: cc
List of ``Cc:`` recipients for the message.
.. attribute:: bcc
List of ``Bcc:`` recipients for the message.
.. attribute:: replyto
Optional reply-to (``Reply-To:``) address for the message.
.. attribute:: txt_body
String with the ``text/plain`` body content.
.. attribute:: html_body
String with the ``text/html`` body content.
"""
def __init__(
self,
key=None,
sender=None,
subject=None,
to=None,
cc=None,
bcc=None,
replyto=None,
txt_body=None,
html_body=None,
):
self.key = key
self.sender = sender
self.subject = subject
self.set_recips('to', to)
self.set_recips('cc', cc)
self.set_recips('bcc', bcc)
self.replyto = replyto
self.txt_body = txt_body
self.html_body = html_body
def set_recips(self, name, value):
""" """
if value:
if isinstance(value, str):
value = [value]
if not isinstance(value, (list, tuple)):
raise ValueError("must specify a string, tuple or list value")
else:
value = []
setattr(self, name, list(value))
def as_string(self):
"""
Returns the complete message as string. This is called from
within
:meth:`~wuttjamaican.email.EmailHandler.deliver_message()` to
obtain the SMTP payload.
"""
msg = None
if self.txt_body and self.html_body:
txt = MIMEText(self.txt_body, _charset='utf_8')
html = MIMEText(self.html_body, _subtype='html', _charset='utf_8')
msg = MIMEMultipart(_subtype='alternative', _subparts=[txt, html])
elif self.txt_body:
msg = MIMEText(self.txt_body, _charset='utf_8')
elif self.html_body:
msg = MIMEText(self.html_body, 'html', _charset='utf_8')
if not msg:
raise ValueError("message has no body parts")
msg['Subject'] = self.subject
msg['From'] = self.sender
for addr in self.to:
msg['To'] = addr
for addr in self.cc:
msg['Cc'] = addr
for addr in self.bcc:
msg['Bcc'] = addr
if self.replyto:
msg.add_header('Reply-To', self.replyto)
return msg.as_string()
class EmailHandler(GenericHandler): class EmailHandler(GenericHandler):
""" """
Base class and default implementation for the :term:`email Base class and default implementation for the :term:`email
@ -89,7 +220,7 @@ class EmailHandler(GenericHandler):
This is the "raw" factory which is simply a wrapper around the This is the "raw" factory which is simply a wrapper around the
class constructor. See also :meth:`make_auto_message()`. class constructor. See also :meth:`make_auto_message()`.
:returns: :class:`~wuttjamaican.email.message.Message` object. :returns: :class:`~wuttjamaican.email.Message` object.
""" """
return Message(**kwargs) return Message(**kwargs)
@ -112,7 +243,7 @@ class EmailHandler(GenericHandler):
:param \**kwargs: Any remaining kwargs are passed as-is to :param \**kwargs: Any remaining kwargs are passed as-is to
:meth:`make_message()`. More on this below. :meth:`make_message()`. More on this below.
:returns: :class:`~wuttjamaican.email.message.Message` object. :returns: :class:`~wuttjamaican.email.Message` object.
This method may invoke some others, to gather the message This method may invoke some others, to gather the message
attributes. Each will check config, or render a template, or attributes. Each will check config, or render a template, or
@ -147,8 +278,8 @@ class EmailHandler(GenericHandler):
def get_auto_sender(self, key): def get_auto_sender(self, key):
""" """
Returns automatic Returns automatic
:attr:`~wuttjamaican.email.message.Message.sender` address for :attr:`~wuttjamaican.email.Message.sender` address for a
a message, as determined by config. message, as determined by config.
""" """
# prefer configured sender specific to key # prefer configured sender specific to key
sender = self.config.get(f'{self.config.appname}.email.{key}.sender') sender = self.config.get(f'{self.config.appname}.email.{key}.sender')
@ -160,9 +291,8 @@ class EmailHandler(GenericHandler):
def get_auto_subject(self, key, context={}, rendered=True): def get_auto_subject(self, key, context={}, rendered=True):
""" """
Returns automatic Returns automatic :attr:`~wuttjamaican.email.Message.subject`
:attr:`~wuttjamaican.email.message.Message.subject` line for a line for a message, as determined by config.
message, as determined by config.
This calls :meth:`get_auto_subject_template()` and then This calls :meth:`get_auto_subject_template()` and then
renders the result using the given context. renders the result using the given context.
@ -200,25 +330,22 @@ class EmailHandler(GenericHandler):
def get_auto_to(self, key): def get_auto_to(self, key):
""" """
Returns automatic Returns automatic :attr:`~wuttjamaican.email.Message.to`
:attr:`~wuttjamaican.email.message.Message.to` recipient recipient address(es) for a message, as determined by config.
address(es) for a message, as determined by config.
""" """
return self.get_auto_recips(key, 'to') return self.get_auto_recips(key, 'to')
def get_auto_cc(self, key): def get_auto_cc(self, key):
""" """
Returns automatic Returns automatic :attr:`~wuttjamaican.email.Message.cc`
:attr:`~wuttjamaican.email.message.Message.cc` recipient recipient address(es) for a message, as determined by config.
address(es) for a message, as determined by config.
""" """
return self.get_auto_recips(key, 'cc') return self.get_auto_recips(key, 'cc')
def get_auto_bcc(self, key): def get_auto_bcc(self, key):
""" """
Returns automatic Returns automatic :attr:`~wuttjamaican.email.Message.bcc`
:attr:`~wuttjamaican.email.message.Message.bcc` recipient recipient address(es) for a message, as determined by config.
address(es) for a message, as determined by config.
""" """
return self.get_auto_recips(key, 'bcc') return self.get_auto_recips(key, 'bcc')
@ -239,10 +366,9 @@ class EmailHandler(GenericHandler):
def get_auto_txt_body(self, key, context={}): def get_auto_txt_body(self, key, context={}):
""" """
Returns automatic Returns automatic :attr:`~wuttjamaican.email.Message.txt_body`
:attr:`~wuttjamaican.email.message.Message.txt_body` content content for a message, as determined by config. This renders
for a message, as determined by config. This renders a a template with the given context.
template with the given context.
""" """
template = self.get_auto_body_template(key, 'txt') template = self.get_auto_body_template(key, 'txt')
if template: if template:
@ -251,9 +377,9 @@ class EmailHandler(GenericHandler):
def get_auto_html_body(self, key, context={}): def get_auto_html_body(self, key, context={}):
""" """
Returns automatic Returns automatic
:attr:`~wuttjamaican.email.message.Message.html_body` content :attr:`~wuttjamaican.email.Message.html_body` content for a
for a message, as determined by config. This renders a message, as determined by config. This renders a template
template with the given context. with the given context.
""" """
template = self.get_auto_body_template(key, 'html') template = self.get_auto_body_template(key, 'html')
if template: if template:
@ -327,10 +453,9 @@ class EmailHandler(GenericHandler):
""" """
Deliver a message via SMTP smarthost. Deliver a message via SMTP smarthost.
:param message: Either a :param message: Either a :class:`~wuttjamaican.email.Message`
:class:`~wuttjamaican.email.message.Message` object or object or similar, or a string representing the complete
similar, or a string representing the complete message to message to be sent as-is.
be sent as-is.
:param sender: Optional sender address to use for delivery. :param sender: Optional sender address to use for delivery.
If not specified, will be read from ``message``. If not specified, will be read from ``message``.
@ -339,10 +464,10 @@ class EmailHandler(GenericHandler):
If not specified, will be read from ``message``. If not specified, will be read from ``message``.
A general rule here is that you can either provide a proper A general rule here is that you can either provide a proper
:class:`~wuttjamaican.email.message.Message` object, **or** :class:`~wuttjamaican.email.Message` object, **or** you *must*
you *must* provide ``sender`` and ``recips``. The logic is provide ``sender`` and ``recips``. The logic is not smart
not smart enough (yet?) to parse sender/recips from a simple enough (yet?) to parse sender/recips from a simple string
string message. message.
Note also, this method does not (yet?) have robust error Note also, this method does not (yet?) have robust error
handling, so if an error occurs with the SMTP session, it will handling, so if an error occurs with the SMTP session, it will

View file

@ -1,33 +0,0 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023-2024 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Email Utilities
The following are available in this ``wuttjamaican.email`` namespace:
* :class:`~wuttjamaican.email.handler.EmailHandler`
* :class:`~wuttjamaican.email.message.Message`
"""
from .handler import EmailHandler
from .message import Message

View file

@ -1,158 +0,0 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttJamaican -- Base package for Wutta Framework
# Copyright © 2023-2024 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Email Message
"""
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
class Message:
"""
Represents an email message to be sent.
:param to: Recipient(s) for the message. This may be either a
string, or list of strings. If a string, it will be converted
to a list since that is how the :attr:`to` attribute tracks it.
Similar logic is used for :attr:`cc` and :attr:`bcc`.
All attributes shown below may also be specified via constructor.
.. attribute:: key
Unique key indicating the "type" of message. An "ad-hoc"
message created arbitrarily may not have/need a key; however
one created via
:meth:`~wuttjamaican.email.handler.EmailHandler.make_auto_message()`
will always have a key.
This key is not used for anything within the ``Message`` class
logic. It is used by
:meth:`~wuttjamaican.email.handler.EmailHandler.make_auto_message()`
when constructing the message, and the key is set on the final
message only as a reference.
.. attribute:: sender
Sender (``From:``) address for the message.
.. attribute:: subject
Subject text for the message.
.. attribute:: to
List of ``To:`` recipients for the message.
.. attribute:: cc
List of ``Cc:`` recipients for the message.
.. attribute:: bcc
List of ``Bcc:`` recipients for the message.
.. attribute:: replyto
Optional reply-to (``Reply-To:``) address for the message.
.. attribute:: txt_body
String with the ``text/plain`` body content.
.. attribute:: html_body
String with the ``text/html`` body content.
"""
def __init__(
self,
key=None,
sender=None,
subject=None,
to=None,
cc=None,
bcc=None,
replyto=None,
txt_body=None,
html_body=None,
):
self.key = key
self.sender = sender
self.subject = subject
self.set_recips('to', to)
self.set_recips('cc', cc)
self.set_recips('bcc', bcc)
self.replyto = replyto
self.txt_body = txt_body
self.html_body = html_body
def set_recips(self, name, value):
""" """
if value:
if isinstance(value, str):
value = [value]
if not isinstance(value, (list, tuple)):
raise ValueError("must specify a string, tuple or list value")
else:
value = []
setattr(self, name, list(value))
def as_string(self):
"""
Returns the complete message as string. This is called from
within
:meth:`~wuttjamaican.email.handler.EmailHandler.deliver_message()`
to obtain the SMTP payload.
"""
msg = None
if self.txt_body and self.html_body:
txt = MIMEText(self.txt_body, _charset='utf_8')
html = MIMEText(self.html_body, _subtype='html', _charset='utf_8')
msg = MIMEMultipart(_subtype='alternative', _subparts=[txt, html])
elif self.txt_body:
msg = MIMEText(self.txt_body, _charset='utf_8')
elif self.html_body:
msg = MIMEText(self.html_body, 'html', _charset='utf_8')
if not msg:
raise ValueError("message has no body parts")
msg['Subject'] = self.subject
msg['From'] = self.sender
for addr in self.to:
msg['To'] = addr
for addr in self.cc:
msg['Cc'] = addr
for addr in self.bcc:
msg['Bcc'] = addr
if self.replyto:
msg.add_header('Reply-To', self.replyto)
return msg.as_string()

View file

@ -1,76 +0,0 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from wuttjamaican.email import message as mod
class TestMessage(TestCase):
def make_message(self, **kwargs):
return mod.Message(**kwargs)
def test_set_recips(self):
msg = self.make_message()
self.assertEqual(msg.to, [])
# set as list
msg.set_recips('to', ['sally@example.com'])
self.assertEqual(msg.to, ['sally@example.com'])
# set as tuple
msg.set_recips('to', ('barney@example.com',))
self.assertEqual(msg.to, ['barney@example.com'])
# set as string
msg.set_recips('to', 'wilma@example.com')
self.assertEqual(msg.to, ['wilma@example.com'])
# set as null
msg.set_recips('to', None)
self.assertEqual(msg.to, [])
# otherwise error
self.assertRaises(ValueError, msg.set_recips, 'to', {'foo': 'foo@example.com'})
def test_as_string(self):
# error if no body
msg = self.make_message()
self.assertRaises(ValueError, msg.as_string)
# txt body
msg = self.make_message(sender='bob@example.com',
txt_body="hello world")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
# html body
msg = self.make_message(sender='bob@example.com',
html_body="<p>hello world</p>")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
# txt + html body
msg = self.make_message(sender='bob@example.com',
txt_body="hello world",
html_body="<p>hello world</p>")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
# everything
msg = self.make_message(sender='bob@example.com',
subject='meeting follow-up',
to='sally@example.com',
cc='marketing@example.com',
bcc='bob@example.com',
replyto='sales@example.com',
txt_body="hello world",
html_body="<p>hello world</p>")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
self.assertIn('Subject: meeting follow-up', complete)
self.assertIn('To: sally@example.com', complete)
self.assertIn('Cc: marketing@example.com', complete)
self.assertIn('Bcc: bob@example.com', complete)
self.assertIn('Reply-To: sales@example.com', complete)

View file

@ -5,8 +5,7 @@ from unittest.mock import patch, MagicMock
import pytest import pytest
from wuttjamaican.email import handler as mod from wuttjamaican import email as mod
from wuttjamaican.email import Message
from wuttjamaican.conf import WuttaConfig from wuttjamaican.conf import WuttaConfig
from wuttjamaican.util import resource_path from wuttjamaican.util import resource_path
from wuttjamaican.exc import ConfigurationError from wuttjamaican.exc import ConfigurationError
@ -36,28 +35,28 @@ class TestEmailHandler(TestCase):
# provider may specify paths as list # provider may specify paths as list
providers = { providers = {
'wuttatest': MagicMock(email_templates=['wuttjamaican.email:templates']), 'wuttatest': MagicMock(email_templates=['wuttjamaican:email-templates']),
} }
with patch.object(self.app, 'providers', new=providers): with patch.object(self.app, 'providers', new=providers):
handler = self.make_handler() handler = self.make_handler()
path = resource_path('wuttjamaican.email:templates') path = resource_path('wuttjamaican:email-templates')
self.assertEqual(handler.txt_templates.directories, [path]) self.assertEqual(handler.txt_templates.directories, [path])
self.assertEqual(handler.html_templates.directories, [path]) self.assertEqual(handler.html_templates.directories, [path])
# provider may specify paths as string # provider may specify paths as string
providers = { providers = {
'wuttatest': MagicMock(email_templates='wuttjamaican.email:templates'), 'wuttatest': MagicMock(email_templates='wuttjamaican:email-templates'),
} }
with patch.object(self.app, 'providers', new=providers): with patch.object(self.app, 'providers', new=providers):
handler = self.make_handler() handler = self.make_handler()
path = resource_path('wuttjamaican.email:templates') path = resource_path('wuttjamaican:email-templates')
self.assertEqual(handler.txt_templates.directories, [path]) self.assertEqual(handler.txt_templates.directories, [path])
self.assertEqual(handler.html_templates.directories, [path]) self.assertEqual(handler.html_templates.directories, [path])
def test_make_message(self): def test_make_message(self):
handler = self.make_handler() handler = self.make_handler()
msg = handler.make_message() msg = handler.make_message()
self.assertIsInstance(msg, Message) self.assertIsInstance(msg, mod.Message)
def test_make_auto_message(self): def test_make_auto_message(self):
handler = self.make_handler() handler = self.make_handler()
@ -70,7 +69,7 @@ class TestEmailHandler(TestCase):
# message is empty by default # message is empty by default
msg = handler.make_auto_message('foo') msg = handler.make_auto_message('foo')
self.assertIsInstance(msg, Message) self.assertIsInstance(msg, mod.Message)
self.assertEqual(msg.key, 'foo') self.assertEqual(msg.key, 'foo')
self.assertEqual(msg.sender, 'bob@example.com') self.assertEqual(msg.sender, 'bob@example.com')
self.assertEqual(msg.subject, "Automated message") self.assertEqual(msg.subject, "Automated message")
@ -85,7 +84,7 @@ class TestEmailHandler(TestCase):
# then we should get back a more complete message # then we should get back a more complete message
self.config.setdefault('wutta.email.test_foo.subject', "hello foo") self.config.setdefault('wutta.email.test_foo.subject', "hello foo")
self.config.setdefault('wutta.email.test_foo.to', 'sally@example.com') self.config.setdefault('wutta.email.test_foo.to', 'sally@example.com')
self.config.setdefault('wutta.email.templates', 'tests.email:templates') self.config.setdefault('wutta.email.templates', 'tests:email-templates')
handler = self.make_handler() handler = self.make_handler()
msg = handler.make_auto_message('test_foo') msg = handler.make_auto_message('test_foo')
self.assertEqual(msg.key, 'test_foo') self.assertEqual(msg.key, 'test_foo')
@ -240,7 +239,7 @@ class TestEmailHandler(TestCase):
# but returns a template if it exists # but returns a template if it exists
providers = { providers = {
'wuttatest': MagicMock(email_templates=['tests.email:templates']), 'wuttatest': MagicMock(email_templates=['tests:email-templates']),
} }
with patch.object(self.app, 'providers', new=providers): with patch.object(self.app, 'providers', new=providers):
handler = self.make_handler() handler = self.make_handler()
@ -257,7 +256,7 @@ class TestEmailHandler(TestCase):
# but returns body if template exists # but returns body if template exists
providers = { providers = {
'wuttatest': MagicMock(email_templates=['tests.email:templates']), 'wuttatest': MagicMock(email_templates=['tests:email-templates']),
} }
with patch.object(self.app, 'providers', new=providers): with patch.object(self.app, 'providers', new=providers):
handler = self.make_handler() handler = self.make_handler()
@ -273,7 +272,7 @@ class TestEmailHandler(TestCase):
# but returns body if template exists # but returns body if template exists
providers = { providers = {
'wuttatest': MagicMock(email_templates=['tests.email:templates']), 'wuttatest': MagicMock(email_templates=['tests:email-templates']),
} }
with patch.object(self.app, 'providers', new=providers): with patch.object(self.app, 'providers', new=providers):
handler = self.make_handler() handler = self.make_handler()
@ -456,3 +455,74 @@ class TestEmailHandler(TestCase):
self.config.setdefault('wutta.email.default.enabled', False) self.config.setdefault('wutta.email.default.enabled', False)
handler.send_email('bar', sender='bar@example.com', txt_body="hello world") handler.send_email('bar', sender='bar@example.com', txt_body="hello world")
self.assertFalse(deliver_message.called) self.assertFalse(deliver_message.called)
class TestMessage(TestCase):
def make_message(self, **kwargs):
return mod.Message(**kwargs)
def test_set_recips(self):
msg = self.make_message()
self.assertEqual(msg.to, [])
# set as list
msg.set_recips('to', ['sally@example.com'])
self.assertEqual(msg.to, ['sally@example.com'])
# set as tuple
msg.set_recips('to', ('barney@example.com',))
self.assertEqual(msg.to, ['barney@example.com'])
# set as string
msg.set_recips('to', 'wilma@example.com')
self.assertEqual(msg.to, ['wilma@example.com'])
# set as null
msg.set_recips('to', None)
self.assertEqual(msg.to, [])
# otherwise error
self.assertRaises(ValueError, msg.set_recips, 'to', {'foo': 'foo@example.com'})
def test_as_string(self):
# error if no body
msg = self.make_message()
self.assertRaises(ValueError, msg.as_string)
# txt body
msg = self.make_message(sender='bob@example.com',
txt_body="hello world")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
# html body
msg = self.make_message(sender='bob@example.com',
html_body="<p>hello world</p>")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
# txt + html body
msg = self.make_message(sender='bob@example.com',
txt_body="hello world",
html_body="<p>hello world</p>")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
# everything
msg = self.make_message(sender='bob@example.com',
subject='meeting follow-up',
to='sally@example.com',
cc='marketing@example.com',
bcc='bob@example.com',
replyto='sales@example.com',
txt_body="hello world",
html_body="<p>hello world</p>")
complete = msg.as_string()
self.assertIn('From: bob@example.com', complete)
self.assertIn('Subject: meeting follow-up', complete)
self.assertIn('To: sally@example.com', complete)
self.assertIn('Cc: marketing@example.com', complete)
self.assertIn('Bcc: bob@example.com', complete)
self.assertIn('Reply-To: sales@example.com', complete)