fix: add is_enabled()
method for email handler, to check per type
also add some more descriptive errors when email template not found, body empty
This commit is contained in:
parent
089d9d7ec6
commit
902412322e
|
@ -191,6 +191,17 @@ Glossary
|
|||
|
||||
Default is :class:`~wuttjamaican.email.handler.EmailHandler`.
|
||||
|
||||
email key
|
||||
String idenfier for a certain :term:`email type`. Each email key
|
||||
must be unique across the app, so the correct template files and
|
||||
other settings are used when sending etc.
|
||||
|
||||
email type
|
||||
The :term:`app` is capable of sending many types of emails,
|
||||
e.g. daily reports, alerts of various kinds etc. Each "type" of
|
||||
email then will have its own template(s) and sender/recipient
|
||||
settings etc. See also :term:`email key`.
|
||||
|
||||
entry point
|
||||
This refers to a "setuptools-style" entry point specifically,
|
||||
which is a mechanism used to register "plugins" and the like.
|
||||
|
|
|
@ -277,6 +277,52 @@ class EmailHandler(GenericHandler):
|
|||
except TopLevelLookupException:
|
||||
pass
|
||||
|
||||
def is_enabled(self, key):
|
||||
"""
|
||||
Returns flag indicating whether the given email type is
|
||||
"enabled" - i.e. whether it should ever be sent out (enabled)
|
||||
or always suppressed (disabled).
|
||||
|
||||
All email types are enabled by default, unless config says
|
||||
otherwise; e.g. to disable ``foo`` emails:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[wutta.email]
|
||||
|
||||
# nb. this is fallback if specific type is not configured
|
||||
default.enabled = true
|
||||
|
||||
# this disables 'foo' but e.g 'bar' is still enabled per default above
|
||||
foo.enabled = false
|
||||
|
||||
In a development setup you may want a reverse example, where
|
||||
all emails are disabled by default but you can turn on just
|
||||
one type for testing:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[wutta.email]
|
||||
|
||||
# do not send any emails unless explicitly enabled
|
||||
default.enabled = false
|
||||
|
||||
# turn on 'bar' for testing
|
||||
bar.enabled = true
|
||||
|
||||
See also :meth:`sending_is_enabled()` which is more of a
|
||||
master shutoff switch.
|
||||
|
||||
:param key: Unique identifier for the email type.
|
||||
|
||||
:returns: True if this email type is enabled, otherwise false.
|
||||
"""
|
||||
for key in set([key, 'default']):
|
||||
enabled = self.config.get_bool(f'{self.config.appname}.email.{key}.enabled')
|
||||
if enabled is not None:
|
||||
return enabled
|
||||
return True
|
||||
|
||||
def deliver_message(self, message, sender=None, recips=None):
|
||||
"""
|
||||
Deliver a message via SMTP smarthost.
|
||||
|
@ -368,17 +414,25 @@ class EmailHandler(GenericHandler):
|
|||
"""
|
||||
Send an email message.
|
||||
|
||||
This method can send a ``message`` you provide, or it can
|
||||
construct one automatically from key/config/templates.
|
||||
This method can send a message you provide, or it can
|
||||
construct one automatically from key / config / templates.
|
||||
|
||||
:param key: Indicates which "type" of automatic email to send.
|
||||
The most common use case is assumed to be the latter, where
|
||||
caller does not provide the message proper, but specifies key
|
||||
and context so the message is auto-created. In that case this
|
||||
method will also check :meth:`is_enabled()` and skip the
|
||||
sending if that returns false.
|
||||
|
||||
:param key: When auto-creating a message, this is the
|
||||
:term:`email key` identifying the type of email to send.
|
||||
Used to lookup config settings and template files.
|
||||
|
||||
:param context: Context dict for rendering automatic email
|
||||
template(s).
|
||||
|
||||
:param message: Optional pre-built message instance, to send
|
||||
as-is.
|
||||
as-is. If specified, nothing about the message will be
|
||||
auto-assigned from config.
|
||||
|
||||
:param sender: Optional sender address for the
|
||||
message/delivery.
|
||||
|
@ -415,9 +469,27 @@ class EmailHandler(GenericHandler):
|
|||
:meth:`make_auto_message()`. So, not used if you provide
|
||||
the ``message``.
|
||||
"""
|
||||
if key and not self.is_enabled(key):
|
||||
log.debug("skipping disabled email: %s", key)
|
||||
return
|
||||
|
||||
if message is None:
|
||||
if not key:
|
||||
raise ValueError("must specify email key (and/or message object)")
|
||||
|
||||
# auto-create message from key + context
|
||||
if sender:
|
||||
kwargs['sender'] = sender
|
||||
message = self.make_auto_message(key, context, **kwargs)
|
||||
if not (message.txt_body or message.html_body):
|
||||
raise RuntimeError(f"message (type: {key}) has no body - "
|
||||
"perhaps template file not found?")
|
||||
|
||||
if not (message.txt_body or message.html_body):
|
||||
if key:
|
||||
msg = f"message (type: {key}) has no body content"
|
||||
else:
|
||||
msg = "message has no body content"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.deliver_message(message, recips=recips)
|
||||
|
|
|
@ -280,6 +280,22 @@ class TestEmailHandler(TestCase):
|
|||
body = handler.get_auto_html_body('test_foo')
|
||||
self.assertEqual(body, '<p>hello from foo html template</p>\n')
|
||||
|
||||
def test_is_enabled(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# enabled by default
|
||||
self.assertTrue(handler.is_enabled('default'))
|
||||
self.assertTrue(handler.is_enabled('foo'))
|
||||
|
||||
# specific type disabled
|
||||
self.config.setdefault('wutta.email.foo.enabled', 'false')
|
||||
self.assertFalse(handler.is_enabled('foo'))
|
||||
|
||||
# default is disabled
|
||||
self.assertTrue(handler.is_enabled('bar'))
|
||||
self.config.setdefault('wutta.email.default.enabled', 'false')
|
||||
self.assertFalse(handler.is_enabled('bar'))
|
||||
|
||||
def test_deliver_message(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
|
@ -387,24 +403,56 @@ class TestEmailHandler(TestCase):
|
|||
self.assertTrue(handler.sending_is_enabled())
|
||||
|
||||
def test_send_email(self):
|
||||
with patch.object(mod.EmailHandler, 'deliver_message') as deliver_message:
|
||||
handler = self.make_handler()
|
||||
handler = self.make_handler()
|
||||
with patch.object(handler, 'deliver_message') as deliver_message:
|
||||
|
||||
# deliver_message() is called
|
||||
handler.send_email('foo', sender='bob@example.com', to='sally@example.com',
|
||||
txt_body='hello world')
|
||||
deliver_message.assert_called_once()
|
||||
# specify message w/ no body
|
||||
msg = handler.make_message()
|
||||
self.assertRaises(ValueError, handler.send_email, message=msg)
|
||||
self.assertFalse(deliver_message.called)
|
||||
|
||||
# make_auto_message() called only if needed
|
||||
with patch.object(handler, 'make_auto_message') as make_auto_message:
|
||||
# again, but also specify key
|
||||
msg = handler.make_message()
|
||||
self.assertRaises(ValueError, handler.send_email, 'foo', message=msg)
|
||||
self.assertFalse(deliver_message.called)
|
||||
|
||||
msg = handler.make_message()
|
||||
handler.send_email(message=msg)
|
||||
make_auto_message.assert_not_called()
|
||||
# specify complete message
|
||||
deliver_message.reset_mock()
|
||||
msg = handler.make_message(txt_body="hello world")
|
||||
handler.send_email(message=msg)
|
||||
deliver_message.assert_called_once_with(msg, recips=None)
|
||||
|
||||
handler.send_email('foo', sender='bob@example.com', to='sally@example.com',
|
||||
txt_body='hello world')
|
||||
make_auto_message.assert_called_once_with('foo', {},
|
||||
sender='bob@example.com',
|
||||
to='sally@example.com',
|
||||
txt_body='hello world')
|
||||
# again, but also specify key
|
||||
deliver_message.reset_mock()
|
||||
msg = handler.make_message(txt_body="hello world")
|
||||
handler.send_email('foo', message=msg)
|
||||
deliver_message.assert_called_once_with(msg, recips=None)
|
||||
|
||||
# no key, no message
|
||||
deliver_message.reset_mock()
|
||||
self.assertRaises(ValueError, handler.send_email)
|
||||
|
||||
# auto-create message w/ no template
|
||||
deliver_message.reset_mock()
|
||||
self.assertRaises(RuntimeError, handler.send_email, 'foo', sender='foo@example.com')
|
||||
self.assertFalse(deliver_message.called)
|
||||
|
||||
# auto create w/ body
|
||||
deliver_message.reset_mock()
|
||||
handler.send_email('foo', sender='foo@example.com', txt_body="hello world")
|
||||
self.assertTrue(deliver_message.called)
|
||||
|
||||
# type is disabled
|
||||
deliver_message.reset_mock()
|
||||
self.config.setdefault('wutta.email.foo.enabled', False)
|
||||
handler.send_email('foo', sender='foo@example.com', txt_body="hello world")
|
||||
self.assertFalse(deliver_message.called)
|
||||
|
||||
# default is disabled
|
||||
deliver_message.reset_mock()
|
||||
handler.send_email('bar', sender='bar@example.com', txt_body="hello world")
|
||||
self.assertTrue(deliver_message.called)
|
||||
deliver_message.reset_mock()
|
||||
self.config.setdefault('wutta.email.default.enabled', False)
|
||||
handler.send_email('bar', sender='bar@example.com', txt_body="hello world")
|
||||
self.assertFalse(deliver_message.called)
|
||||
|
|
Loading…
Reference in a new issue