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
					
				
					 3 changed files with 152 additions and 21 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue