From 6e9881bd8d19f93e92b20ef67ef0502cf04ad6cb Mon Sep 17 00:00:00 2001 From: BJ Dierkes Date: Wed, 10 Sep 2014 18:27:38 -0500 Subject: [PATCH] Working on Issue #182 (requires tests/doc) --- ChangeLog | 4 +- cement/core/foundation.py | 52 ++++-- cement/core/mail.py | 92 +++++----- cement/ext/ext_dummy.py | 222 ++++++++++++++++++++++++ cement/ext/ext_memcached.py | 6 + cement/ext/ext_nulloutput.py | 46 ----- cement/ext/ext_smtp.py | 238 ++++++++++++++++++++++++++ cement/utils/test.py | 3 +- doc/source/api/core/mail.rst | 9 + doc/source/api/ext/ext_dummy.rst | 8 + doc/source/api/ext/ext_nulloutput.rst | 8 - doc/source/api/ext/ext_smtp.rst | 8 + doc/source/api/index.rst | 4 +- doc/source/dev/logging.rst | 4 +- doc/source/dev/output.rst | 4 +- doc/source/dev/quickstart.rst | 4 +- tests/core/controller_tests.py | 41 +++++ tests/core/foundation_tests.py | 10 +- tests/core/handler_tests.py | 10 +- tests/ext/dummy_tests.py | 13 ++ tests/ext/smtp_tests.py | 36 ++++ 21 files changed, 695 insertions(+), 127 deletions(-) create mode 100644 cement/ext/ext_dummy.py delete mode 100644 cement/ext/ext_nulloutput.py create mode 100644 cement/ext/ext_smtp.py create mode 100644 doc/source/api/core/mail.rst create mode 100644 doc/source/api/ext/ext_dummy.rst delete mode 100644 doc/source/api/ext/ext_nulloutput.rst create mode 100644 doc/source/api/ext/ext_smtp.rst create mode 100644 tests/ext/dummy_tests.py create mode 100644 tests/ext/smtp_tests.py diff --git a/ChangeLog b/ChangeLog index 00471418..ac0826a9 100755 --- a/ChangeLog +++ b/ChangeLog @@ -35,7 +35,9 @@ Features: * :issue:`119` - Added cement.utils.shell.Prompt to quickly gather user input in several different ways. - * :issue:`182` - Added an mail interface with basic implementations. + * :issue:`182` - Added an mail interface with basic implementations of + 'dummy' (just prints message to console) and 'smtp' supporting sending + email via SMTP. * :issue:`229` - Ability to override handlers via command line options. Also related: :issue:`225`. * :issue:`248` - Added documentation for BASH Auto-Completion example. diff --git a/cement/core/foundation.py b/cement/core/foundation.py index 9c5321c9..2b6977c9 100644 --- a/cement/core/foundation.py +++ b/cement/core/foundation.py @@ -4,12 +4,8 @@ import re import os import sys import signal - - from ..core import backend, exc, handler, hook, log, config, plugin, interface from ..core import output, extension, arg, controller, meta, cache, mail -from ..ext import ext_configparser, ext_argparse, ext_logging -from ..ext import ext_nulloutput, ext_plugin from ..utils.misc import is_true, minimal_logger from ..utils import fs @@ -395,37 +391,37 @@ class CementApp(meta.MetaMixin): signal_handler = cement_signal_handler """A function that is called to handle any caught signals.""" - config_handler = ext_configparser.ConfigParserConfigHandler + config_handler = 'configparser' """ A handler class that implements the IConfig interface. """ - mail_handler = mail.DummyMailHandler + mail_handler = 'dummy' """ A handler class that implements the IMail interface. """ - extension_handler = extension.CementExtensionHandler + extension_handler = 'cement' """ A handler class that implements the IExtension interface. """ - log_handler = ext_logging.LoggingLogHandler + log_handler = 'logging' """ A handler class that implements the ILog interface. """ - plugin_handler = ext_plugin.CementPluginHandler + plugin_handler = 'cement' """ A handler class that implements the IPlugin interface. """ - argument_handler = ext_argparse.ArgParseArgumentHandler + argument_handler = 'argparse' """ A handler class that implements the IArgument interface. """ - output_handler = ext_nulloutput.NullOutputHandler + output_handler = 'dummy' """ A handler class that implements the IOutput interface. """ @@ -466,7 +462,7 @@ class CementApp(meta.MetaMixin): """ core_extensions = [ - 'cement.ext.ext_nulloutput', + 'cement.ext.ext_dummy', 'cement.ext.ext_plugin', 'cement.ext.ext_configparser', 'cement.ext.ext_logging', @@ -489,6 +485,8 @@ class CementApp(meta.MetaMixin): 'plugin_dir', 'ignore_deprecation_warnings', 'template_dir', + 'cache_handler', + 'mail_handler', ] """ List of meta options that can/will be overridden by config options @@ -552,6 +550,7 @@ class CementApp(meta.MetaMixin): ``template_dirs``. """ + def __init__(self, label=None, **kw): super(CementApp, self).__init__(**kw) @@ -571,6 +570,7 @@ class CementApp(meta.MetaMixin): self.output = None self.controller = None self.cache = None + self.mail = None # setup argv... this has to happen before lay_cement() if self._meta.argv is None: @@ -949,6 +949,7 @@ class CementApp(meta.MetaMixin): # override select Meta via config base_dict = self.config.get_section_dict(self._meta.config_section) + for key in base_dict: if key in self._meta.core_meta_override or \ key in self._meta.meta_override: @@ -958,6 +959,31 @@ class CementApp(meta.MetaMixin): else: setattr(self._meta, key, base_dict[key]) + # load extensions from configuraton file + if 'extensions' in self.config.keys(self._meta.label): + exts = self.config.get(self._meta.label, 'extensions') + + # convert a comma-separated string to a list + if type(exts) is str: + ext_list = exts.split(',') + + # clean up extra space if they had it inbetween commas + ext_list = [x.strip() for x in ext_list] + + # set the new extensions value in the config + self.config.set(self._meta.label, 'extensions', ext_list) + + # otherwise, if it's a list (ConfigObj?) + elif type(exts) is list: + ext_list = exts + + for ext in ext_list: + # load the extension + self.ext.load_extension(ext) + + # add to meta data + self._meta.extensions.append(ext) + def _setup_mail_handler(self): LOG.debug("setting up %s.mail handler" % self._meta.label) self.mail = self._resolve_handler('mail', @@ -1096,6 +1122,8 @@ class CementApp(meta.MetaMixin): label = 'myapp' def validate_config(self): + super(MyApp, self).validate_config() + # test that the log file directory exist, if not create it logdir = os.path.dirname(self.config.get('log', 'file')) diff --git a/cement/core/mail.py b/cement/core/mail.py index 14c4851e..92e4d885 100644 --- a/cement/core/mail.py +++ b/cement/core/mail.py @@ -14,7 +14,7 @@ def mail_validator(klass, obj): 'send', ] - ### FIX ME: Validate Configuration Defaults Here + ### FIX ME: Validate Meta/Configuration Defaults Here interface.validate(IMail, obj, members) @@ -27,7 +27,21 @@ class IMail(interface.Interface): Implementations do *not* subclass from interfaces. - Usage: + **Configuration** + + Implementations much support the following configuration settings: + + * **to** - Default ``to`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **from_addr** - Default ``from_addr`` address + * **cc** - Default ``cc`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **bcc** - Default ``bcc`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **subject** - Default ``subject`` + * **subject_prefix** - Additional string to prepend to the ``subject`` + + **Usage** .. code-block:: python @@ -67,20 +81,39 @@ class IMail(interface.Interface): def send(body, **kwargs): """ - Send a mail message. Keyword arguments override defaults defined - in the handler implementations configuration (see possible keyword - arguments below). + Send a mail message. Keyword arguments override configuration + defaults (cc, bcc, etc). :param body: The message body to send - :type body: Multiline string + :type body: multiline string :keyword to: List of recipients (generally email addresses) + :type to: list + :keyword from_addr: Address (generally email) of the sender + :type from_addr: string :keyword cc: List of CC Recipients + :type cc: list :keyword bcc: List of BCC Recipients - :keyword from: Address (generall email) of the sender + :type bcc: list :keyword subject: Message subject line + :type subject: string :returns: Boolean (``True`` if message is sent successfully, ``False`` otherwise) + **Usage** + + .. code-block:: python + + # Using all configuration defaults + app.send('This is my message body') + + # Overriding configuration defaults + app.send('My message body' + to=['john@example.com'], + from_addr='me@example.com', + cc=['jane@example.com', 'rita@example.com'], + subject='This is my subject', + ) + """ @@ -88,6 +121,10 @@ class CementMailHandler(handler.CementBaseHandler): """ Base class that all Mail Handlers should sub-class from. + **Configuration Options** + + This handler supports the following configuration options under a + """ class Meta: """ @@ -104,7 +141,7 @@ class CementMailHandler(handler.CementBaseHandler): #: Configuration default values config_defaults = { 'to' : [], - 'from' : 'noreply@example.com', + 'from_addr' : 'noreply@example.com', 'cc' : [], 'bcc' : [], 'subject' : 'Default Subject Line', @@ -132,47 +169,10 @@ class CementMailHandler(handler.CementBaseHandler): value_list = value.split(',') # clean up extra space if they had it inbetween commas - value_list = (x.strip() for x in value_list) + value_list = [x.strip() for x in value_list] # set the new extensions value in the config self.app.config.set(self._meta.config_section, item, value_list) -class DummyMailHandler(CementMailHandler): - class Meta: - label = 'dummy' - - def _get_params(self, **kw): - params = dict() - for item in ['to', 'from', 'cc', 'bcc', 'subject', 'subject_prefix']: - config_item = self.app.config.get(self._meta.config_section, item) - params[item] = getattr(kw, item, config_item) - - return params - - def send(self, body, **kw): - # shorted config values - params = self._get_params(**kw) - msg = "\n" + "=" * 77 + "\n" - msg += "DUMMY MAIL MESSAGE\n" - msg += "-" * 77 + "\n\n" - msg += "To: %s\n" % ', '.join(params['to']) - msg += "From: %s\n" % params['from'] - msg += "CC: %s\n" % ', '.join(params['cc']) - msg += "BCC: %s\n" % ', '.join(params['bcc']) - msg += "Subject: %s%s\n\n---\n\n" % (params['subject_prefix'], - params['subject']) - msg += body + "\n" - - msg += "\n" + "-" * 77 + "\n" - - print msg - -class SMTPMailHandler(CementMailHandler): - class Meta: - label = 'smtp' - - def send(self, body, **kw): - pass - diff --git a/cement/ext/ext_dummy.py b/cement/ext/ext_dummy.py new file mode 100644 index 00000000..5e1ae899 --- /dev/null +++ b/cement/ext/ext_dummy.py @@ -0,0 +1,222 @@ +"""Dummy Framework Extension""" + +from ..core import backend, output, handler, mail +from ..utils.misc import minimal_logger + +LOG = minimal_logger(__name__) + + +class DummyOutputHandler(output.CementOutputHandler): + + """ + This class is an internal implementation of the + :ref:`IOutput ` interface. It does not take any + parameters on initialization, and does not actually output anything. + + """ + class Meta: + + """Handler meta-data""" + + interface = output.IOutput + """The interface this class implements.""" + + label = 'dummy' + """The string identifier of this handler.""" + + display_override_option = False + + def render(self, data_dict, template=None): + """ + This implementation does not actually render anything to output, but + rather logs it to the debug facility. + + :param data_dict: The data dictionary to render. + :param template: The template parameter is not used by this + implementation at all. + :returns: None + + """ + LOG.debug("not rendering any output to console") + LOG.debug("DATA: %s" % data_dict) + return None + + +class DummyMailHandler(mail.CementMailHandler): + + """ + This class implements the :ref:`IMail ` + interface, but is intended for use in development as no email is actually + sent. + + **Usage** + + .. code-block:: python + + class MyApp(CementApp): + class Meta: + label = 'myapp' + mail_handler = 'dummy' + + # create, setup, and run the app + app = MyApp() + app.setup() + app.run() + + # fake sending an email message + app.mail.send('This is my fake message', + subject='This is my subject', + to=['john@example.com', 'rita@example.com'], + from_addr='me@example.com', + ) + + The above will print the following to console: + + .. code-block:: text + + ====================================================================== + DUMMY MAIL MESSAGE + ---------------------------------------------------------------------- + + To: john@example.com, rita@example.com + From: me@example.com + CC: + BCC: + Subject: This is my subject + + --- + + This is my fake message + + ---------------------------------------------------------------------- + + **Configuration** + + This handler supports the following configuration settings: + + * **to** - Default ``to`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **from_addr** - Default ``from_addr`` address + * **cc** - Default ``cc`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **bcc** - Default ``bcc`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **subject** - Default ``subject`` + * **subject_prefix** - Additional string to prepend to the ``subject`` + + + You can add these to any application configuration file under a + ``[mail.dummy]`` section, for example: + + **~/.myapp.conf** + + .. code-block:: text + + [myapp] + + # set the mail handler to use + mail_handler = dummy + + + [mail.dummy] + + # default to addresses (comma separated list) + to = me@example.com + + # default from address + from = someone_else@example.com + + # default cc addresses (comma separated list) + cc = jane@example.com, rita@example.com + + # default bcc addresses (comma separated list) + bcc = blackhole@example.com, someone_else@example.com + + # default subject + subject = This is The Default Subject + + # additional prefix to prepend to the subject + subject_prefix = MY PREFIX > + + """ + + class Meta: + #: Unique identifier for this handler + label = 'dummy' + + def _get_params(self, **kw): + params = dict() + for item in ['to', 'from_addr', 'cc', 'bcc', 'subject']: + config_item = self.app.config.get(self._meta.config_section, item) + params[item] = kw.get(item, config_item) + + # also grab the subject_prefix + params['subject_prefix'] = self.app.config.get( + self._meta.config_section, + 'subject_prefix' + ) + + return params + + def send(self, body, **kw): + """ + Mimic sending an email message, but really just print what would be + sent to console. Keyword arguments override configuration + defaults (cc, bcc, etc). + + :param body: The message body to send + :type body: multiline string + :keyword to: List of recipients (generally email addresses) + :type to: list + :keyword from_addr: Address (generally email) of the sender + :type from_addr: string + :keyword cc: List of CC Recipients + :type cc: list + :keyword bcc: List of BCC Recipients + :type bcc: list + :keyword subject: Message subject line + :type subject: string + :returns: Boolean (``True`` if message is sent successfully, ``False`` + otherwise) + + **Usage** + + .. code-block:: python + + # Using all configuration defaults + app.send('This is my message body') + + # Overriding configuration defaults + app.send('My message body' + to=['john@example.com'], + from_addr='me@example.com', + cc=['jane@example.com', 'rita@example.com'], + subject='This is my subject', + ) + + """ + # shorted config values + params = self._get_params(**kw) + msg = "\n" + "=" * 77 + "\n" + msg += "DUMMY MAIL MESSAGE\n" + msg += "-" * 77 + "\n\n" + msg += "To: %s\n" % ', '.join(params['to']) + msg += "From: %s\n" % params['from_addr'] + msg += "CC: %s\n" % ', '.join(params['cc']) + msg += "BCC: %s\n" % ', '.join(params['bcc']) + + if params['subject_prefix'] not in [None, '']: + msg += "Subject: %s %s\n\n---\n\n" % (params['subject_prefix'], + params['subject']) + else: + msg += "Subject: %s\n\n---\n\n" % params['subject'] + msg += body + "\n" + + msg += "\n" + "-" * 77 + "\n" + + print msg + + +def load(app): + handler.register(DummyOutputHandler) + handler.register(DummyMailHandler) diff --git a/cement/ext/ext_memcached.py b/cement/ext/ext_memcached.py index 7a4a8754..838c7789 100644 --- a/cement/ext/ext_memcached.py +++ b/cement/ext/ext_memcached.py @@ -53,6 +53,12 @@ class MemcachedCacheHandler(cache.CementCacheHandler): .. code-block:: text + [myapp] + + # set the cache handler to use + cache_handler = memcached + + [cache.memcached] # time in seconds that an item in the cache will expire diff --git a/cement/ext/ext_nulloutput.py b/cement/ext/ext_nulloutput.py deleted file mode 100644 index 0ce5b302..00000000 --- a/cement/ext/ext_nulloutput.py +++ /dev/null @@ -1,46 +0,0 @@ -"""NullOutput Framework Extension""" - -from ..core import backend, output, handler -from ..utils.misc import minimal_logger - -LOG = minimal_logger(__name__) - - -class NullOutputHandler(output.CementOutputHandler): - - """ - This class is an internal implementation of the - :ref:`IOutput ` interface. It does not take any - parameters on initialization. - - """ - class Meta: - - """Handler meta-data""" - - interface = output.IOutput - """The interface this class implements.""" - - label = 'null' - """The string identifier of this handler.""" - - display_override_option = False - - def render(self, data_dict, template=None): - """ - This implementation does not actually render anything to output, but - rather logs it to the debug facility. - - :param data_dict: The data dictionary to render. - :param template: The template parameter is not used by this - implementation at all. - :returns: None - - """ - LOG.debug("not rendering any output to console") - LOG.debug("DATA: %s" % data_dict) - return None - - -def load(app): - handler.register(NullOutputHandler) diff --git a/cement/ext/ext_smtp.py b/cement/ext/ext_smtp.py new file mode 100644 index 00000000..23f0e874 --- /dev/null +++ b/cement/ext/ext_smtp.py @@ -0,0 +1,238 @@ + +import smtplib +from ..core import handler, mail +from ..utils.misc import minimal_logger, is_true + +LOG = minimal_logger(__name__) + + +class SMTPMailHandler(mail.CementMailHandler): + + """ + This class implements the :ref:`IMail ` + interface, and is based on the `smtplib + `_ standard library. + + **Usage** + + .. code-block:: python + + class MyApp(CementApp): + class Meta: + label = 'myapp' + mail_handler = 'smtp' + + # create, setup, and run the app + app = MyApp() + app.setup() + app.run() + + # fake sending an email message + app.mail.send('This is my fake message', + subject='This is my subject', + to=['john@example.com', 'rita@example.com'], + from_addr='me@example.com', + ) + + **Configuration** + + This handler supports the following configuration settings: + + * **to** - Default ``to`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **from_addr** - Default ``from_addr`` address + * **cc** - Default ``cc`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **bcc** - Default ``bcc`` addresses (list, or comma separated depending + on the ConfigHandler in use) + * **subject** - Default ``subject`` + * **subject_prefix** - Additional string to prepend to the ``subject`` + * **host** - The SMTP host server + * **port** - The SMTP host server port + * **timeout** - The timeout in seconds before terminating a connection + * **ssl** - Whether to initiate SSL or not + * **tls** - Whether to use TLS or not (requires SSL) + * **auth** - Whether or not to initiate SMTP authentication + * **username** - SMTP authentication username + * **password** - SMTP authentication password + + You can add these to any application configuration file under a + ``[mail.smtp]`` section, for example: + + **~/.myapp.conf** + + .. code-block:: text + + [myapp] + + # set the mail handler to use + mail_handler = smtp + + + [mail.smtp] + + # default to addresses (comma separated list) + to = me@example.com + + # default from address + from = someone_else@example.com + + # default cc addresses (comma separated list) + cc = jane@example.com, rita@example.com + + # default bcc addresses (comma separated list) + bcc = blackhole@example.com, someone_else@example.com + + # default subject + subject = This is The Default Subject + + # additional prefix to prepend to the subject + subject_prefix = MY PREFIX > + + # smtp host server + host = localhost + + # smtp host port + port = 465 + + # timeout in seconds + timeout = 30 + + # whether or not to establish an ssl connection + ssl = 1 + + # whether or not to use start tls + tls = 1 + + # whether or not to initiate smtp auth + auth = 1 + + # smtp auth username + username = john.doe + + # smtp auth password + password = oober_secure_password + + """ + + class Meta: + #: Unique identifier for this handler + label = 'smtp' + + #: Configuration default values + config_defaults = { + 'to' : [], + 'from_addr' : 'noreply@localhost', + 'cc' : [], + 'bcc' : [], + 'subject' : None, + 'subject_prefix' : None, + 'host' : 'localhost', + 'port' : '25', + 'timeout' : 30, + 'ssl' : False, + 'tls' : False, + 'auth' : False, + 'username' : None, + 'password' : None, + } + + def _get_params(self, **kw): + params = dict() + + # some keyword args override configuration defaults + for item in ['to', 'from_addr', 'cc', 'bcc', 'subject']: + config_item = self.app.config.get(self._meta.config_section, item) + params[item] = kw.get(item, config_item) + + # others don't + other_params = ['ssl', 'tls', 'host', 'port', 'auth', 'username', + 'password', 'timeout'] + for item in other_params: + params[item] = self.app.config.get(self._meta.config_section, + item) + + # also grab the subject_prefix + params['subject_prefix'] = self.app.config.get( + self._meta.config_section, + 'subject_prefix' + ) + + return params + + def send(self, body, **kw): + """ + Send an email message via SMTP. Keyword arguments override + configuration defaults (cc, bcc, etc). + + :param body: The message body to send + :type body: multiline string + :keyword to: List of recipients (generally email addresses) + :type to: list + :keyword from_addr: Address (generally email) of the sender + :type from_addr: string + :keyword cc: List of CC Recipients + :type cc: list + :keyword bcc: List of BCC Recipients + :type bcc: list + :keyword subject: Message subject line + :type subject: string + :returns: Boolean (``True`` if message is sent successfully, ``False`` + otherwise) + + **Usage** + + .. code-block:: python + + # Using all configuration defaults + app.send('This is my message body') + + # Overriding configuration defaults + app.send('My message body' + from_addr='me@example.com', + to=['john@example.com'], + cc=['jane@example.com', 'rita@example.com'], + subject='This is my subject', + ) + + """ + params = self._get_params(**kw) + + if is_true(params['ssl']): + server = smtplib.SMTP_SSL(params['host'], params['port'], + params['timeout']) + LOG.debug("%s : initiating ssl" % self._meta.label) + if is_true(params['tls']): + LOG.debug("%s : initiating tls" % self._meta.label) + server.starttls() + + else: + server = smtplib.SMTP(params['host'], params['port'], + params['timeout']) + + + if is_true(params['auth']): + server.login(params['username'], params['password']) + + if self.app.debug is True: + server.set_debuglevel(9) + + msg = "" + msg += "From: %s\r\nTo: %s\r\n" % (params['from_addr'], + ', '.join(params['to'])) + msg += "Cc: %s\r\n" % ', '.join(params['cc']) + msg += "Bcc: %s\r\n" % ', '.join(params['bcc']) + if params['subject_prefix'] not in [None, '']: + msg += "Subject: %s %s\r\n\r\n" % (params['subject_prefix'], + params['subject']) + else: + msg += "Subject: %s\r\n\r\n" % params['subject'] + msg += body + "\n" + + server.sendmail(params['from_addr'], + params['to']+params['cc']+params['bcc'], + msg) + server.quit() + +def load(app): + handler.register(SMTPMailHandler) diff --git a/cement/utils/test.py b/cement/utils/test.py index f46dcba3..db72b3d5 100644 --- a/cement/utils/test.py +++ b/cement/utils/test.py @@ -3,6 +3,7 @@ import unittest from tempfile import mkstemp, mkdtemp from ..core import backend, foundation +from ..utils.misc import rando # shortcuts from nose import SkipTest @@ -19,7 +20,7 @@ class TestApp(foundation.CementApp): """ class Meta: - label = 'test' + label = "app-%s" % rando()[:12] config_files = [] argv = [] base_controller = None diff --git a/doc/source/api/core/mail.rst b/doc/source/api/core/mail.rst new file mode 100644 index 00000000..afa9875b --- /dev/null +++ b/doc/source/api/core/mail.rst @@ -0,0 +1,9 @@ +.. _cement.core.mail: + +:mod:`cement.core.mail` +-------------------------- + +.. automodule:: cement.core.mail + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/api/ext/ext_dummy.rst b/doc/source/api/ext/ext_dummy.rst new file mode 100644 index 00000000..7d7559f5 --- /dev/null +++ b/doc/source/api/ext/ext_dummy.rst @@ -0,0 +1,8 @@ +.. _cement.ext.ext_dummy: + +:mod:`cement.ext.ext_dummy` +------------------------------------ + +.. automodule:: cement.ext.ext_dummy + :members: + :show-inheritance: diff --git a/doc/source/api/ext/ext_nulloutput.rst b/doc/source/api/ext/ext_nulloutput.rst deleted file mode 100644 index 8d0f57b4..00000000 --- a/doc/source/api/ext/ext_nulloutput.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _cement.ext.ext_nulloutput: - -:mod:`cement.ext.ext_nulloutput` ------------------------------------- - -.. automodule:: cement.ext.ext_nulloutput - :members: - :show-inheritance: diff --git a/doc/source/api/ext/ext_smtp.rst b/doc/source/api/ext/ext_smtp.rst new file mode 100644 index 00000000..df348c0e --- /dev/null +++ b/doc/source/api/ext/ext_smtp.rst @@ -0,0 +1,8 @@ +.. _cement.ext.ext_smtp: + +:mod:`cement.ext.ext_smtp` +========================== + +.. automodule:: cement.ext.ext_smtp + :members: + :show-inheritance: diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 31fe68b5..4d610bb0 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -21,6 +21,7 @@ Cement Core Modules core/hook core/interface core/log + core/mail core/meta core/output core/plugin @@ -56,7 +57,8 @@ Cement Extension Modules ext/ext_logging ext/ext_memcached ext/ext_mustache - ext/ext_nulloutput + ext/ext_dummy ext/ext_plugin + ext/ext_smtp ext/ext_yaml ext/ext_yaml_configobj diff --git a/doc/source/dev/logging.rst b/doc/source/dev/logging.rst index a690e862..05b5468d 100644 --- a/doc/source/dev/logging.rst +++ b/doc/source/dev/logging.rst @@ -110,8 +110,8 @@ debugging issues: 2012-07-13 02:19:42,272 (DEBUG) cement.core.foundation : adding signal handler for signal 15 2012-07-13 02:19:42,273 (DEBUG) cement.core.foundation : adding signal handler for signal 2 2012-07-13 02:19:42,273 (DEBUG) cement.core.foundation : setting up myapp.extension handler - 2012-07-13 02:19:42,273 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_nulloutput' framework extension - 2012-07-13 02:19:42,273 (DEBUG) cement.core.handler : registering handler '' into handlers['output']['null'] + 2012-07-13 02:19:42,273 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_dummy' framework extension + 2012-07-13 02:19:42,273 (DEBUG) cement.core.handler : registering handler '' into handlers['output']['null'] 2012-07-13 02:19:42,273 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_plugin' framework extension 2012-07-13 02:19:42,273 (DEBUG) cement.core.handler : registering handler '' into handlers['plugin']['cement'] 2012-07-13 02:19:42,273 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_configparser' framework extension diff --git a/doc/source/dev/output.rst b/doc/source/dev/output.rst index e0ba31d7..120d01d7 100644 --- a/doc/source/dev/output.rst +++ b/doc/source/dev/output.rst @@ -4,7 +4,7 @@ Output Handling =============== Cement defines an output interface called :ref:`IOutput `, -as well as the default :ref:`NullOutputHandler ` +as well as the default :ref:`DummyOutputHandler ` that implements the interface. This handler is part of Cement, and actually does nothing to produce output. Therefore it can be said that by default a Cement application does not handle rendering output to the console, but @@ -16,7 +16,7 @@ interface and not the full capabilities of the implementation. The following output handlers are included and maintained with Cement: - * :ref:`NullOutputHandler ` + * :ref:`DummyOutputHandler ` * :ref:`JsonOutputHandler ` * :ref:`YamlOutputHandler ` * :ref:`GenshiOutputHandler ` diff --git a/doc/source/dev/quickstart.rst b/doc/source/dev/quickstart.rst index 9f754290..e76d938f 100644 --- a/doc/source/dev/quickstart.rst +++ b/doc/source/dev/quickstart.rst @@ -86,8 +86,8 @@ Oh nice, ok... ArgParse is already setup with a few options I see. What else? 2014-04-15 12:28:24,706 (DEBUG) cement.core.foundation : adding signal handler for signal 15 2014-04-15 12:28:24,712 (DEBUG) cement.core.foundation : adding signal handler for signal 2 2014-04-15 12:28:24,712 (DEBUG) cement.core.foundation : setting up helloworld.extension handler - 2014-04-15 12:28:24,712 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_nulloutput' framework extension - 2014-04-15 12:28:24,712 (DEBUG) cement.core.handler : registering handler '' into handlers['output']['null'] + 2014-04-15 12:28:24,712 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_dummy' framework extension + 2014-04-15 12:28:24,712 (DEBUG) cement.core.handler : registering handler '' into handlers['output']['null'] 2014-04-15 12:28:24,712 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_plugin' framework extension 2014-04-15 12:28:24,713 (DEBUG) cement.core.handler : registering handler '' into handlers['plugin']['cement'] 2014-04-15 12:28:24,713 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_configparser' framework extension diff --git a/tests/core/controller_tests.py b/tests/core/controller_tests.py index fef76d0d..ab4ca571 100644 --- a/tests/core/controller_tests.py +++ b/tests/core/controller_tests.py @@ -2,6 +2,10 @@ from cement.core import exc, controller, handler from cement.utils import test +from cement.utils.misc import rando, init_defaults + +APP = "app-%s" % rando()[:12] + class TestController(controller.CementBaseController): class Meta: @@ -225,3 +229,40 @@ class ControllerTestCase(test.CementCoreTestCase): app.setup() app.run() self.eq(app.get_last_rendered()[0]['foo'], 'mypositional') + + def test_load_extensions_from_config_list(self): + defaults = init_defaults(APP) + defaults[APP]['extensions'] = ['json', 'yaml'] + + app = self.make_app( + label=APP, + extensions=[], + config_defaults=defaults, + ) + app.setup() + app.run() + + res = 'cement.ext.ext_json' in app.ext._loaded_extensions + self.ok(res) + + res = 'cement.ext.ext_yaml' in app.ext._loaded_extensions + self.ok(res) + + def test_load_extensions_from_config_str(self): + defaults = init_defaults(APP) + defaults[APP]['extensions'] = 'json, yaml' + + app = self.make_app( + label=APP, + extensions=[], + config_defaults=defaults, + ) + app.setup() + app.run() + + res = 'cement.ext.ext_json' in app.ext._loaded_extensions + self.ok(res) + + res = 'cement.ext.ext_yaml' in app.ext._loaded_extensions + self.ok(res) + diff --git a/tests/core/foundation_tests.py b/tests/core/foundation_tests.py index 75eacd55..eb8de295 100644 --- a/tests/core/foundation_tests.py +++ b/tests/core/foundation_tests.py @@ -90,7 +90,7 @@ class FoundationTestCase(test.CementCoreTestCase): from cement.ext import ext_logging from cement.ext import ext_argparse from cement.ext import ext_plugin - from cement.ext import ext_nulloutput + from cement.ext import ext_dummy # forces CementApp._resolve_handler to register the handler from cement.ext import ext_json @@ -102,6 +102,7 @@ class FoundationTestCase(test.CementCoreTestCase): extension_handler=extension.CementExtensionHandler(), plugin_handler=ext_plugin.CementPluginHandler(), output_handler=ext_json.JsonOutputHandler(), + mail_handler=ext_dummy.DummyMailHandler(), argv=[__file__, '--debug'] ) @@ -338,3 +339,10 @@ class FoundationTestCase(test.CementCoreTestCase): app.setup() app._suppress_output() + def test_core_meta_override(self): + defaults = init_defaults(APP) + defaults[APP]['mail_handler'] = 'dummy' + app = self.make_app(APP, debug=True, config_defaults=defaults) + app.setup() + app.run() + diff --git a/tests/core/handler_tests.py b/tests/core/handler_tests.py index 88117a96..6034da33 100644 --- a/tests/core/handler_tests.py +++ b/tests/core/handler_tests.py @@ -26,7 +26,7 @@ class BogusHandler4(meta.MetaMixin): class DuplicateHandler(output.CementOutputHandler): class Meta: interface = output.IOutput - label = 'null' + label = 'dummy' def _setup(self, config_obj): pass @@ -72,8 +72,8 @@ class HandlerTestCase(test.CementCoreTestCase): @test.raises(exc.FrameworkError) def test_register_duplicate_handler(self): - from cement.ext import ext_nulloutput - handler.register(ext_nulloutput.NullOutputHandler) + from cement.ext import ext_dummy + handler.register(ext_dummy.DummyOutputHandler) try: handler.register(DuplicateHandler) except exc.FrameworkError: @@ -89,7 +89,7 @@ class HandlerTestCase(test.CementCoreTestCase): def test_verify_handler(self): self.app.setup() - self.ok(handler.registered('output', 'null')) + self.ok(handler.registered('output', 'dummy')) self.eq(handler.registered('output', 'bogus_handler'), False) self.eq(handler.registered('bogus_type', 'bogus_handler'), False) @@ -148,7 +148,7 @@ class HandlerTestCase(test.CementCoreTestCase): def test_handler_registered(self): self.app.setup() - self.eq(handler.registered('output', 'null'), True) + self.eq(handler.registered('output', 'dummy'), True) def test_handler_get_fallback(self): self.app.setup() diff --git a/tests/ext/dummy_tests.py b/tests/ext/dummy_tests.py new file mode 100644 index 00000000..890826c9 --- /dev/null +++ b/tests/ext/dummy_tests.py @@ -0,0 +1,13 @@ +"""Tests for cement.ext.ext_dummy.""" + +from cement.utils import test +from cement.utils.misc import rando + +APP = "app-%s" % rando()[:12] + + +class DummyOutputHandlerTestCase(test.CementTestCase): + pass + +class DummyMailHandlerTestCase(test.CementTestCase): + pass diff --git a/tests/ext/smtp_tests.py b/tests/ext/smtp_tests.py new file mode 100644 index 00000000..8c114a07 --- /dev/null +++ b/tests/ext/smtp_tests.py @@ -0,0 +1,36 @@ +"""Tests for cement.ext.ext_smtp.""" + +from cement.utils import test +from cement.utils.misc import rando, init_defaults + +APP = "app-%s" % rando()[:12] + + +class SMTPMailHandlerTestCase(test.CementTestCase): + def setUp(self): + super(SMTPMailHandlerTestCase, self).setUp() + self.app = self.make_app(APP, + extensions=['smtp'], + mail_handler='smtp', + argv=[], + ) + + def test_smtp_defaults(self): + defaults = init_defaults(APP, 'mail.smtp') + defaults['mail.smtp']['to'] = 'nobody@localhost' + defaults['mail.smtp']['from_addr'] = 'nobody@localhost' + defaults['mail.smtp']['cc'] = 'nobody@localhost' + defaults['mail.smtp']['bcc'] = 'nobody@localhost' + defaults['mail.smtp']['subject'] = 'Test Email To nobody@localhost' + defaults['mail.smtp']['subject_prefix'] = 'PREFIX >' + + self.app = self.make_app(APP, + config_defaults=defaults, + extensions=['smtp'], + mail_handler='smtp', + argv=[], + ) + self.app.setup() + self.app.run() + self.app.mail.send('TEST MESSAGE') +