mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 13:56:49 +00:00
Working on Issue #182 (requires tests/doc)
This commit is contained in:
parent
d03ea01976
commit
6e9881bd8d
@ -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.
|
||||
|
||||
@ -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'))
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
222
cement/ext/ext_dummy.py
Normal file
222
cement/ext/ext_dummy.py
Normal file
@ -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 <cement.core.output>` 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 <cement.core.mail>`
|
||||
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)
|
||||
@ -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
|
||||
|
||||
@ -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 <cement.core.output>` 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)
|
||||
238
cement/ext/ext_smtp.py
Normal file
238
cement/ext/ext_smtp.py
Normal file
@ -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 <cement.core.mail>`
|
||||
interface, and is based on the `smtplib
|
||||
<http://docs.python.org/dev/library/smtplib.html>`_ 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)
|
||||
@ -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
|
||||
|
||||
9
doc/source/api/core/mail.rst
Normal file
9
doc/source/api/core/mail.rst
Normal file
@ -0,0 +1,9 @@
|
||||
.. _cement.core.mail:
|
||||
|
||||
:mod:`cement.core.mail`
|
||||
--------------------------
|
||||
|
||||
.. automodule:: cement.core.mail
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
8
doc/source/api/ext/ext_dummy.rst
Normal file
8
doc/source/api/ext/ext_dummy.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. _cement.ext.ext_dummy:
|
||||
|
||||
:mod:`cement.ext.ext_dummy`
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: cement.ext.ext_dummy
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@ -1,8 +0,0 @@
|
||||
.. _cement.ext.ext_nulloutput:
|
||||
|
||||
:mod:`cement.ext.ext_nulloutput`
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: cement.ext.ext_nulloutput
|
||||
:members:
|
||||
:show-inheritance:
|
||||
8
doc/source/api/ext/ext_smtp.rst
Normal file
8
doc/source/api/ext/ext_smtp.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. _cement.ext.ext_smtp:
|
||||
|
||||
:mod:`cement.ext.ext_smtp`
|
||||
==========================
|
||||
|
||||
.. automodule:: cement.ext.ext_smtp
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@ -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
|
||||
|
||||
@ -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 '<class 'cement.ext.ext_nulloutput.NullOutputHandler'>' 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 '<class 'cement.ext.ext_dummy.DummyOutputHandler'>' 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 '<class 'cement.ext.ext_plugin.CementPluginHandler'>' into handlers['plugin']['cement']
|
||||
2012-07-13 02:19:42,273 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_configparser' framework extension
|
||||
|
||||
@ -4,7 +4,7 @@ Output Handling
|
||||
===============
|
||||
|
||||
Cement defines an output interface called :ref:`IOutput <cement.core.output>`,
|
||||
as well as the default :ref:`NullOutputHandler <cement.ext.ext_nulloutput>`
|
||||
as well as the default :ref:`DummyOutputHandler <cement.ext.ext_dummy>`
|
||||
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 <cement.ext.ext_nulloutput>`
|
||||
* :ref:`DummyOutputHandler <cement.ext.ext_dummy>`
|
||||
* :ref:`JsonOutputHandler <cement.ext.ext_json>`
|
||||
* :ref:`YamlOutputHandler <cement.ext.ext_yaml>`
|
||||
* :ref:`GenshiOutputHandler <cement.ext.ext_genshi>`
|
||||
|
||||
@ -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 '<class 'cement.ext.ext_nulloutput.NullOutputHandler'>' 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 '<class 'cement.ext.ext_dummy.DummyOutputHandler'>' 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 '<class 'cement.ext.ext_plugin.CementPluginHandler'>' into handlers['plugin']['cement']
|
||||
2014-04-15 12:28:24,713 (DEBUG) cement.core.extension : loading the 'cement.ext.ext_configparser' framework extension
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
13
tests/ext/dummy_tests.py
Normal file
13
tests/ext/dummy_tests.py
Normal file
@ -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
|
||||
36
tests/ext/smtp_tests.py
Normal file
36
tests/ext/smtp_tests.py
Normal file
@ -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')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user