mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 13:42:03 +00:00
parent
d04ddf0723
commit
a3b5317fb2
@ -35,6 +35,8 @@ Features:
|
||||
|
||||
* :issue:`119` - Added cement.utils.shell.Prompt to quickly gather user
|
||||
input in several different ways.
|
||||
* :issue:`229` - Ability to override handlers via command line options.
|
||||
Also related: :issue:`225`.
|
||||
* :issue:`248` - Added documentation for BASH Auto-Completion example.
|
||||
* :issue:`249` - Allow utils.shell.exec_cmd* to accept additional.
|
||||
parameters, passing all `args` and `kwargs` down to subprocess.Popen.
|
||||
@ -49,12 +51,15 @@ Features:
|
||||
* :issue:`269` - Allow app.close() to accept an exit code, and exit with
|
||||
that code.
|
||||
* :issue:`270` - Add support for multiple template_dirs
|
||||
* :issue:`274` - Add cement.core.interface.list function
|
||||
|
||||
Incompatible:
|
||||
|
||||
* :issue:`227` - Standardize handler config section naming conventions.
|
||||
All handler config sections are now labeled after ``interface.handler``
|
||||
(i.e. ``output.json``, or ``mail.sendgrid``, etc).
|
||||
* :issue:`229` - Handler override options deprecate the use of ``--json``,
|
||||
and ``--yaml`` output handler options.
|
||||
* :issue:`260` - Extensions/Plugins/Bootstrap modules must accept `app`
|
||||
argument in `load()` function. See Upgrading doc for more information.
|
||||
|
||||
|
||||
@ -82,6 +82,8 @@ class IArgument(interface.Interface):
|
||||
:keyword help: The help text for --help output (for that argument).
|
||||
:keyword action: Must support: ['store', 'store_true', 'store_false',
|
||||
'store_const']
|
||||
:keyword choices: A list of valid values that can be passed to an
|
||||
option whose action is ``store``.
|
||||
:keyword const: The value stored if action == 'store_const'.
|
||||
:keyword default: The default value.
|
||||
:returns: None
|
||||
|
||||
@ -5,7 +5,8 @@ import os
|
||||
import sys
|
||||
import signal
|
||||
|
||||
from ..core import backend, exc, handler, hook, log, config, plugin
|
||||
|
||||
from ..core import backend, exc, handler, hook, log, config, plugin, interface
|
||||
from ..core import output, extension, arg, controller, meta, cache
|
||||
from ..ext import ext_configparser, ext_argparse, ext_logging
|
||||
from ..ext import ext_nulloutput, ext_plugin
|
||||
@ -14,71 +15,90 @@ from ..utils import fs
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
from imp import reload # pragma: nocover
|
||||
from io import StringIO # pragma: nocover
|
||||
else:
|
||||
from StringIO import StringIO # pragma: nocover
|
||||
|
||||
LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
class NullOut(object):
|
||||
|
||||
def write(self, s):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def add_output_handler_override_option(app):
|
||||
|
||||
def add_handler_override_options(app):
|
||||
"""
|
||||
This is a ``post_setup`` hook that adds the ``--json`` argument to the
|
||||
argument object.
|
||||
This is a ``post_setup`` hook that adds the handler override options to
|
||||
the argument parser
|
||||
|
||||
:param app: The application object.
|
||||
|
||||
"""
|
||||
if len(handler.list('output')) > 1:
|
||||
output_handlers = []
|
||||
for h in handler.list('output'):
|
||||
output_handlers.append(h())
|
||||
if app._meta.handler_override_options is None:
|
||||
return
|
||||
|
||||
output_options = [x._meta.label
|
||||
for x in output_handlers
|
||||
if x._meta.display_override_option is True]
|
||||
for i in app._meta.handler_override_options:
|
||||
if i not in interface.list():
|
||||
LOG.debug("interface '%s'" % i +
|
||||
" is not defined, can not override handlers")
|
||||
continue
|
||||
|
||||
# don't display the option if not output handlers have
|
||||
# display_override_option enabled
|
||||
if len(output_options) > 0:
|
||||
if len(handler.list(i)) > 1:
|
||||
handlers = []
|
||||
for h in handler.list(i):
|
||||
handlers.append(h())
|
||||
|
||||
help_txt = "%s [%s]" % (
|
||||
app._meta.output_handler_override_help,
|
||||
', '.join(output_options)
|
||||
choices = [x._meta.label
|
||||
for x in handlers
|
||||
if x._meta.overridable is True]
|
||||
|
||||
# don't display the option if no handlers are overridable
|
||||
if not len(choices) > 0:
|
||||
LOG.debug("no handlers are overridable within the " +
|
||||
"%s interface" % i)
|
||||
continue
|
||||
|
||||
# override things that we need to control
|
||||
argument_kw = app._meta.handler_override_options[i][1]
|
||||
argument_kw['dest'] = '%s_handler_override' % i
|
||||
argument_kw['action'] = 'store'
|
||||
argument_kw['choices'] = choices
|
||||
|
||||
app.args.add_argument(
|
||||
*app._meta.handler_override_options[i][0],
|
||||
**app._meta.handler_override_options[i][1]
|
||||
)
|
||||
|
||||
if app._meta.output_handler_override is not None:
|
||||
app.args.add_argument(
|
||||
*app._meta.output_handler_override,
|
||||
help=help_txt,
|
||||
dest='output_handler_handler',
|
||||
action='store',
|
||||
metavar='STR'
|
||||
)
|
||||
|
||||
def output_handler_override(app):
|
||||
def handler_override(app):
|
||||
"""
|
||||
This is a ``post_argument_parsing`` hook that overrides the configured
|
||||
output handler if ``CementApp.Meta.output_handler_override`` is enabled
|
||||
and is passed at the command line.
|
||||
This is a ``post_argument_parsing`` hook that overrides a configured
|
||||
handler if defined in ``CementApp.Meta.handler_override_options`` and
|
||||
the option is passed at command line with a valid handler label.
|
||||
|
||||
:param app: The application object.
|
||||
|
||||
"""
|
||||
if app._meta.output_handler_override is None:
|
||||
if app._meta.handler_override_options is None:
|
||||
return
|
||||
|
||||
elif not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
for i in app._meta.handler_override_options.keys():
|
||||
if not hasattr(app.pargs, '%s_handler_override' % i):
|
||||
continue
|
||||
elif getattr(app.pargs, '%s_handler_override' % i) is None:
|
||||
continue
|
||||
else:
|
||||
# get the argument value from command line
|
||||
argument = getattr(app.pargs, '%s_handler_override' % i)
|
||||
setattr(app._meta, '%s_handler' % i, argument)
|
||||
|
||||
# and then re-setup the handler
|
||||
getattr(app, '_setup_%s_handler' % i)()
|
||||
|
||||
if app.pargs.output_handler_override is not None:
|
||||
app._meta.output_handler = app.pargs.output_handler_override
|
||||
app._setup_output_handler()
|
||||
|
||||
def cement_signal_handler(signum, frame):
|
||||
"""
|
||||
@ -317,6 +337,43 @@ class CementApp(meta.MetaMixin):
|
||||
affect whether ``arguments_override_config`` is ``True`` or ``False``.
|
||||
"""
|
||||
|
||||
core_handler_override_options = dict(
|
||||
output=(['-o'], dict(help='output handler')),
|
||||
)
|
||||
"""
|
||||
Similar to ``CementApp.Meta.handler_override_options`` but these are
|
||||
the core defaults required by Cement. This dictionary can be
|
||||
overridden by ``CementApp.Meta.handler_override_options`` (when they
|
||||
are merged together).
|
||||
"""
|
||||
|
||||
handler_override_options = {}
|
||||
"""
|
||||
Dictionary of handler override options that will be added to the
|
||||
argument parser, and allow the end-user to override handlers. Useful
|
||||
for interfaces that have multiple uses within the same application
|
||||
(for example: Output Handler (json, yaml, etc) or maybe a Cloud
|
||||
Provider Handler (rackspace, digitalocean, amazon, etc).
|
||||
|
||||
This dictionary will merge with
|
||||
``CementApp.Meta.core_handler_override_options`` but this one has
|
||||
precedence.
|
||||
|
||||
Dictionary Format:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
<interface_name> = (option_arguments, help_text)
|
||||
|
||||
|
||||
See ``CementApp.Meta.core_handler_override_options`` for an example
|
||||
of what this should look like.
|
||||
|
||||
Note, if set to ``None`` then no options will be defined, and the
|
||||
``CementApp.Meta.core_meta_override_options`` will be ignore (not
|
||||
recommended as some extensions rely on this feature).
|
||||
"""
|
||||
|
||||
config_section = None
|
||||
"""
|
||||
The base configuration section for the application.
|
||||
@ -380,19 +437,6 @@ class CementApp(meta.MetaMixin):
|
||||
class, or an instantiated class object.
|
||||
"""
|
||||
|
||||
output_handler_override = ['-o', '--output']
|
||||
"""
|
||||
Options added to ``argument_handler`` command line arguments allowing
|
||||
the user to override the ``output_handler`` (i.e. ``-o json``,
|
||||
``-o yaml``, etc.
|
||||
"""
|
||||
|
||||
output_handler_override_help = "output handler"
|
||||
"""
|
||||
Help text that is displayed for the
|
||||
``output_handler_override_option``.
|
||||
"""
|
||||
|
||||
cache_handler = None
|
||||
"""
|
||||
A handler class that implements the ICache interface. This can
|
||||
@ -517,12 +561,6 @@ class CementApp(meta.MetaMixin):
|
||||
``template_dirs``.
|
||||
"""
|
||||
|
||||
suppress_output = False
|
||||
"""
|
||||
Used internally to suppress all console output (for example, when
|
||||
``--quiet`` is passed at command line).
|
||||
"""
|
||||
|
||||
def __init__(self, label=None, **kw):
|
||||
super(CementApp, self).__init__(**kw)
|
||||
|
||||
@ -691,7 +729,7 @@ class CementApp(meta.MetaMixin):
|
||||
"Invalid exit status code (must be integer)"
|
||||
sys.exit(code)
|
||||
|
||||
def render(self, data, template=None):
|
||||
def render(self, data, template=None, out=sys.stdout):
|
||||
"""
|
||||
This is a simple wrapper around self.output.render() which simply
|
||||
returns an empty string if no self.output handler is defined.
|
||||
@ -699,6 +737,9 @@ class CementApp(meta.MetaMixin):
|
||||
:param data: The data dictionary to render.
|
||||
:param template: The template to render to. Default: None (some
|
||||
output handlers do not use templates).
|
||||
:param out: A file like object (sys.stdout, or actual file). Set to
|
||||
``None`` is no output is desired (just render and return).
|
||||
Default: sys.stdout
|
||||
|
||||
"""
|
||||
for res in hook.run('pre_render', self, data):
|
||||
@ -719,6 +760,13 @@ class CementApp(meta.MetaMixin):
|
||||
else:
|
||||
out_text = str(res)
|
||||
|
||||
if out is not None and not hasattr(out, 'write'):
|
||||
raise TypeError("Argument 'out' must be a 'file' like object")
|
||||
elif out is not None and out_text is None:
|
||||
LOG.debug('render() called but output text is None')
|
||||
elif out:
|
||||
out.write(out_text)
|
||||
|
||||
self._last_rendered = (data, out_text)
|
||||
return out_text
|
||||
|
||||
@ -764,6 +812,22 @@ class CementApp(meta.MetaMixin):
|
||||
"""A shortcut for self.args.add_argument."""
|
||||
self.args.add_argument(*args, **kw)
|
||||
|
||||
def _suppress_output(self):
|
||||
if self._meta.debug is True:
|
||||
LOG.debug('not suppressing console output because of debug mode')
|
||||
return
|
||||
|
||||
LOG.debug('suppressing all console output')
|
||||
backend.__saved_stdout__ = sys.stdout
|
||||
backend.__saved_stderr__ = sys.stderr
|
||||
sys.stdout = NullOut()
|
||||
sys.stderr = NullOut()
|
||||
|
||||
def _unsuppress_output(self):
|
||||
LOG.debug('unsuppressing all console output')
|
||||
sys.stdout = backend.__saved_stdout__
|
||||
sys.stderr = backend.__saved_stderr__
|
||||
|
||||
def _lay_cement(self):
|
||||
"""Initialize the framework."""
|
||||
LOG.debug("laying cement for the '%s' application" %
|
||||
@ -772,16 +836,7 @@ class CementApp(meta.MetaMixin):
|
||||
if '--debug' in self._meta.argv:
|
||||
self._meta.debug = True
|
||||
elif '--quiet' in self._meta.argv:
|
||||
# the following are hacks to suppress console output
|
||||
# for flag in ['--quiet', '--json', '--yaml']:
|
||||
self._meta.suppress_output = True
|
||||
|
||||
if self._meta.suppress_output:
|
||||
LOG.debug('suppressing all console output per runtime config')
|
||||
backend.__saved_stdout__ = sys.stdout
|
||||
backend.__saved_stderr__ = sys.stderr
|
||||
sys.stdout = NullOut()
|
||||
sys.stderr = NullOut()
|
||||
self._suppress_output()
|
||||
|
||||
# start clean
|
||||
backend.__hooks__ = {}
|
||||
@ -801,8 +856,8 @@ class CementApp(meta.MetaMixin):
|
||||
hook.define('post_render')
|
||||
|
||||
# register some built-in framework hooks
|
||||
hook.register('post_setup', add_output_handler_override_option)
|
||||
hook.register('post_argument_parsing', output_handler_override)
|
||||
hook.register('post_setup', add_handler_override_options, weight=-99)
|
||||
hook.register('post_argument_parsing', handler_override, weight=-99)
|
||||
|
||||
# define and register handlers
|
||||
handler.define(extension.IExtension)
|
||||
@ -998,6 +1053,16 @@ class CementApp(meta.MetaMixin):
|
||||
action='store_true',
|
||||
help='suppress all output')
|
||||
|
||||
# merge handler override meta data
|
||||
if self._meta.handler_override_options is not None:
|
||||
# fucking long names... fuck. anyway, merge the core handler
|
||||
# override options with developer defined options
|
||||
core = self._meta.core_handler_override_options.copy()
|
||||
dev = self._meta.handler_override_options.copy()
|
||||
core.update(dev)
|
||||
|
||||
self._meta.handler_override_options = core
|
||||
|
||||
def _setup_controllers(self):
|
||||
LOG.debug("setting up application controllers")
|
||||
|
||||
|
||||
@ -44,12 +44,12 @@ class CementBaseHandler(meta.MetaMixin):
|
||||
override any existing defaults under that section.
|
||||
"""
|
||||
|
||||
display_override_option = False
|
||||
overridable = False
|
||||
"""
|
||||
Whether or not to display this handlers label along with other
|
||||
override options (if handler override options are enabled in
|
||||
``CementApp``). Generally used for things like
|
||||
``CementApp.Meta.output_handler_override``
|
||||
Whether or not handler can be overridden by
|
||||
``CementApp.Meta.handler_override_options``. Will be listed as an
|
||||
available choice to override the specific handler (i.e.
|
||||
``CementApp.Meta.output_handler``, etc).
|
||||
"""
|
||||
|
||||
def __init__(self, **kw):
|
||||
|
||||
@ -3,11 +3,22 @@ Cement core interface module.
|
||||
|
||||
"""
|
||||
|
||||
from ..core import exc
|
||||
from ..core import exc, backend
|
||||
|
||||
DEFAULT_META = ['interface', 'label', 'config_defaults', 'config_section']
|
||||
|
||||
|
||||
def list():
|
||||
"""
|
||||
Return a list of defined interfaces (handler types).
|
||||
|
||||
:returns: List of defined interfaces
|
||||
:rtype: list
|
||||
|
||||
"""
|
||||
return backend.__handlers__.keys()
|
||||
|
||||
|
||||
class Interface(object):
|
||||
|
||||
"""
|
||||
|
||||
@ -9,31 +9,48 @@ from ..ext.ext_configparser import ConfigParserConfigHandler
|
||||
LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
# def add_json_option(app):
|
||||
# """
|
||||
# This is a ``post_setup`` hook that adds the ``--json`` argument to the
|
||||
# argument object.
|
||||
def suppress_output_before_run(app):
|
||||
"""
|
||||
This is a ``post_argument_parsing`` hook that suppresses console output if
|
||||
the ``JsonOutputHandler`` is triggered via command line.
|
||||
|
||||
# :param app: The application object.
|
||||
:param app: The application object.
|
||||
|
||||
# """
|
||||
# app.args.add_argument('--json', dest='output_handler',
|
||||
# action='store_const',
|
||||
# help='toggle json output handler',
|
||||
# const='json')
|
||||
"""
|
||||
if not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
elif app.pargs.output_handler_override == 'json':
|
||||
app._suppress_output()
|
||||
|
||||
|
||||
# def set_output_handler(app):
|
||||
# """
|
||||
# This is a ``pre_run`` hook that overrides the configured output handler
|
||||
# if ``--json`` is passed at the command line.
|
||||
def unsuppress_output_before_render(app, data):
|
||||
"""
|
||||
This is a ``pre_render`` that unsuppresses console output if
|
||||
the ``JsonOutputHandler`` is triggered via command line so that the JSON
|
||||
is the only thing in the output.
|
||||
|
||||
# :param app: The application object.
|
||||
:param app: The application object.
|
||||
|
||||
# """
|
||||
# if '--json' in app._meta.argv:
|
||||
# app._meta.output_handler = 'json'
|
||||
# app._setup_output_handler()
|
||||
"""
|
||||
if not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
elif app.pargs.output_handler_override == 'json':
|
||||
app._unsuppress_output()
|
||||
|
||||
|
||||
def suppress_output_after_render(app, out_text):
|
||||
"""
|
||||
This is a ``post_render`` hook that suppresses console output again after
|
||||
rendering, only if the ``JsonOutputHandler`` is triggered via command
|
||||
line.
|
||||
|
||||
:param app: The application object.
|
||||
|
||||
"""
|
||||
if not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
elif app.pargs.output_handler_override == 'json':
|
||||
app._suppress_output()
|
||||
|
||||
|
||||
class JsonOutputHandler(output.CementOutputHandler):
|
||||
@ -44,10 +61,15 @@ class JsonOutputHandler(output.CementOutputHandler):
|
||||
library. Please see the developer documentation on
|
||||
:ref:`Output Handling <dev_output_handling>`.
|
||||
|
||||
Note: The cement framework detects the '--json' option and suppresses
|
||||
output (same as if passing --quiet). Therefore, if debugging or
|
||||
troubleshooting issues you must pass the --debug option to see whats
|
||||
going on.
|
||||
Note: By default, Cement adds the ``-o`` command line option to allow the
|
||||
end user to override the output handler. For example: passing ``-o json``
|
||||
will override the default output handler and set it to
|
||||
``JsonOutputHandler``. See ``CementApp.Meta.handler_override_options``.
|
||||
|
||||
This extension forces Cement to suppress console output until
|
||||
``app.render`` is called (keeping the output pure JSON). If
|
||||
troubleshooting issues, you will need to pass the ``--debug`` option in
|
||||
order to unsuppress output and see what's happening.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
@ -60,7 +82,7 @@ class JsonOutputHandler(output.CementOutputHandler):
|
||||
label = 'json'
|
||||
"""The string identifier of this handler."""
|
||||
|
||||
display_override_option = True
|
||||
overridable = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(JsonOutputHandler, self).__init__(*args, **kw)
|
||||
@ -78,8 +100,6 @@ class JsonOutputHandler(output.CementOutputHandler):
|
||||
|
||||
"""
|
||||
LOG.debug("rendering output as Json via %s" % self.__module__)
|
||||
sys.stdout = backend.__saved_stdout__
|
||||
sys.stderr = backend.__saved_stderr__
|
||||
return json.dumps(data_dict)
|
||||
|
||||
|
||||
@ -114,7 +134,8 @@ class JsonConfigHandler(ConfigParserConfigHandler):
|
||||
|
||||
|
||||
def load(app):
|
||||
#hook.register('post_setup', add_json_option)
|
||||
#hook.register('pre_run', set_output_handler)
|
||||
hook.register('post_argument_parsing', suppress_output_before_run)
|
||||
hook.register('pre_render', unsuppress_output_before_render)
|
||||
hook.register('post_render', suppress_output_after_render)
|
||||
handler.register(JsonOutputHandler)
|
||||
handler.register(JsonConfigHandler)
|
||||
|
||||
@ -10,6 +10,50 @@ from ..ext.ext_configparser import ConfigParserConfigHandler
|
||||
LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
def suppress_output_before_run(app):
|
||||
"""
|
||||
This is a ``post_argument_parsing`` hook that suppresses console output if
|
||||
the ``YamlOutputHandler`` is triggered via command line.
|
||||
|
||||
:param app: The application object.
|
||||
|
||||
"""
|
||||
if not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
elif app.pargs.output_handler_override == 'yaml':
|
||||
app._suppress_output()
|
||||
|
||||
|
||||
def unsuppress_output_before_render(app, data):
|
||||
"""
|
||||
This is a ``pre_render`` that unsuppresses console output if
|
||||
the ``YamlOutputHandler`` is triggered via command line so that the YAML
|
||||
is the only thing in the output.
|
||||
|
||||
:param app: The application object.
|
||||
|
||||
"""
|
||||
if not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
elif app.pargs.output_handler_override == 'yaml':
|
||||
app._unsuppress_output()
|
||||
|
||||
|
||||
def suppress_output_after_render(app, out_text):
|
||||
"""
|
||||
This is a ``post_render`` hook that suppresses console output again after
|
||||
rendering, only if the ``YamlOutputHandler`` is triggered via command
|
||||
line.
|
||||
|
||||
:param app: The application object.
|
||||
|
||||
"""
|
||||
if not hasattr(app.pargs, 'output_handler_override'):
|
||||
return
|
||||
elif app.pargs.output_handler_override == 'yaml':
|
||||
app._suppress_output()
|
||||
|
||||
|
||||
class YamlOutputHandler(output.CementOutputHandler):
|
||||
"""
|
||||
This class implements the :ref:`IOutput <cement.core.output>`
|
||||
@ -18,20 +62,21 @@ class YamlOutputHandler(output.CementOutputHandler):
|
||||
STDOUT. Please see the developer documentation on
|
||||
:ref:`Output Handling <dev_output_handling>`.
|
||||
|
||||
**Note** The cement framework detects the '--yaml' option and suppresses
|
||||
output (same as if passing --quiet). Therefore, if debugging or
|
||||
troubleshooting issues you must pass the --debug option to see whats
|
||||
going on.
|
||||
Note: By default, Cement adds the ``-o`` command line option to allow the
|
||||
end user to override the output handler. For example: passing ``-o yaml``
|
||||
will override the default output handler and set it to
|
||||
``YamlOutputHandler``. See ``CementApp.Meta.handler_override_options``.
|
||||
|
||||
**Note** This extension has an external dependency on `pyYAML`. You must
|
||||
include `pyYAML` in your application's dependencies as Cement explicitly
|
||||
does *not* include external dependencies for optional extensions.
|
||||
This extension forces Cement to suppress console output until
|
||||
``app.render`` is called (keeping the output pure YAML). If
|
||||
troubleshooting issues, you will need to pass the ``--debug`` option in
|
||||
order to unsuppress output and see what's happening.
|
||||
|
||||
"""
|
||||
class Meta:
|
||||
interface = output.IOutput
|
||||
label = 'yaml'
|
||||
display_override_option = True
|
||||
overridable = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(YamlOutputHandler, self).__init__(*args, **kw)
|
||||
@ -58,34 +103,6 @@ class YamlOutputHandler(output.CementOutputHandler):
|
||||
return yaml.dump(data_dict)
|
||||
|
||||
|
||||
# def add_yaml_option(app):
|
||||
# """
|
||||
# This is a ``post_setup`` hook that adds the ``--yaml`` argument to the
|
||||
# command line.
|
||||
|
||||
# :param app: The application object.
|
||||
|
||||
# """
|
||||
# app.args.add_argument('--yaml',
|
||||
# dest='output_handler',
|
||||
# action='store_const',
|
||||
# help='toggle yaml output handler',
|
||||
# const='yaml')
|
||||
|
||||
|
||||
# def set_output_handler(app):
|
||||
# """
|
||||
# This is a ``pre_run`` hook that overrides the configured output handler
|
||||
# if ``--yaml`` is passed at the command line.
|
||||
|
||||
# :param app: The application object.
|
||||
|
||||
# """
|
||||
# if '--yaml' in app._meta.argv:
|
||||
# app._meta.output_handler = 'yaml'
|
||||
# app._setup_output_handler()
|
||||
|
||||
|
||||
class YamlConfigHandler(ConfigParserConfigHandler):
|
||||
"""
|
||||
This class implements the :ref:`IConfig <cement.core.config>`
|
||||
@ -123,7 +140,8 @@ class YamlConfigHandler(ConfigParserConfigHandler):
|
||||
|
||||
|
||||
def load(app):
|
||||
hook.register('post_argument_parsing', suppress_output_before_run)
|
||||
hook.register('pre_render', unsuppress_output_before_render)
|
||||
hook.register('post_render', suppress_output_after_render)
|
||||
handler.register(YamlOutputHandler)
|
||||
handler.register(YamlConfigHandler)
|
||||
#hook.register('post_setup', add_yaml_option)
|
||||
#hook.register('pre_run', set_output_handler)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""Cement testing utilities."""
|
||||
|
||||
import unittest
|
||||
from tempfile import mkstemp, mkdtemp
|
||||
from ..core import backend, foundation
|
||||
|
||||
# shortcuts
|
||||
@ -45,6 +46,8 @@ class CementTestCase(unittest.TestCase):
|
||||
|
||||
"""
|
||||
self.app = self.make_app()
|
||||
_, self.tmp_file = mkstemp()
|
||||
self.tmp_dir = mkdtemp()
|
||||
|
||||
def make_app(self, *args, **kw):
|
||||
"""
|
||||
|
||||
@ -254,9 +254,9 @@ Multiple Registered Handlers
|
||||
All handlers and interfaces are unique. In most cases, where the framework
|
||||
is concerned, only one handler is used. For example, whatever is configured
|
||||
for the ``log_handler`` will be used and setup as ``app.log``. However, take
|
||||
for example an Output handler. You might have a default ``output_handler`` of
|
||||
for example an Output Handler. You might have a default ``output_handler`` of
|
||||
``mustache``' (a text templating language) but may also want to override that
|
||||
handler with the ``json`` output handler when '--json' is passed at command
|
||||
handler with the ``json`` output handler when ``-o json`` is passed at command
|
||||
line. In order to allow this functionality, both the ``mustache`` and
|
||||
``json`` output handlers must be registered.
|
||||
|
||||
@ -337,3 +337,105 @@ In the real world this may look like ``[cache.memcached]``, or
|
||||
``[database.mysql]`` depending on what the interface label, and handler
|
||||
label's are. Additionally, individual handlers can override their config
|
||||
section by setting ``Meta.config_section``.
|
||||
|
||||
|
||||
Overriding Handlers Via Command Line
|
||||
------------------------------------
|
||||
|
||||
In some use cases, you will want the end user to have access to override the
|
||||
default handler of a particular interface. For example, Cement ships with
|
||||
multiple Output Handlers including ``json``, ``yaml``, and ``mustache``. A
|
||||
typical application might default to using ``mustache`` to render console
|
||||
output from text templates. That said, without changing any code in the
|
||||
application, the end user can simply pass the ``-o json`` command line
|
||||
option and output the same data that is rendered to template, out in pure
|
||||
JSON.
|
||||
|
||||
The only built-in handler override that Cement includes is for the above
|
||||
mentioned example, but you can add any that your application requires.
|
||||
|
||||
The following example shows this in action... note that the following is
|
||||
already setup by Cement, but we're putting it here for clarity:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement.core.foundation import CementApp
|
||||
|
||||
class MyApp(CementApp):
|
||||
class Meta:
|
||||
label = 'myapp'
|
||||
|
||||
# define what extensions we want to load
|
||||
extensions = ['mustache', 'json', 'yaml']
|
||||
|
||||
# define our default output handler
|
||||
output_handler = 'mustache'
|
||||
|
||||
# define our handler override options
|
||||
handler_override_options = dict(
|
||||
output = (['-o'], dict(help='output format')),
|
||||
)
|
||||
|
||||
|
||||
# create the app
|
||||
app = MyApp()
|
||||
|
||||
try:
|
||||
# setup the app
|
||||
app.setup()
|
||||
|
||||
# define some data for the output handler
|
||||
data = dict(foo='bar')
|
||||
|
||||
# run the app
|
||||
app.run()
|
||||
|
||||
# render something using our output handlers, using mustache by
|
||||
# default which use the default.m template
|
||||
app.render(data, 'default.m')
|
||||
|
||||
finally:
|
||||
# close the app
|
||||
app.close()
|
||||
|
||||
|
||||
Note what we see at command line:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python myapp.py --help
|
||||
usage: myapp.py [-h] [--debug] [--quiet] [-o {yaml,json}]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--debug toggle debug output
|
||||
--quiet suppress all output
|
||||
-o {yaml,json} output format
|
||||
|
||||
|
||||
Notice the ``-o`` command line option, that includes the choices: ``yaml``
|
||||
and ``json``. This feature will include all Output Handlers that have the
|
||||
``overridable`` meta-data option set to ``True``. The MustacheOutputHandler
|
||||
does not set this option, therefore it does not show up as a valid choice.
|
||||
|
||||
Now what happens when we run it?
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python myapp.py
|
||||
|
||||
This text is being rendered via Mustache.
|
||||
The value of the 'foo' variable is => 'bar'
|
||||
|
||||
The above is the default output, using ``mustache`` as our ``output_handler``,
|
||||
and rendering the output text from a template called ``default.m``. We can
|
||||
now override the output handler using the ``-o`` option and modify the output
|
||||
format:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python myapp.py -o json
|
||||
{"foo": "bar"}
|
||||
|
||||
|
||||
Again, any handler can be overridden in this fashion.
|
||||
|
||||
@ -160,11 +160,11 @@ And this looks like:
|
||||
The value of the 'foo' variable is => 'bar'
|
||||
|
||||
|
||||
Optionally, we can use the ``JsonOutputHandler`` via ``--json`` to trigger
|
||||
Optionally, we can use the ``JsonOutputHandler`` via ``-o json`` to trigger
|
||||
just Json output (supressing all other output) using our return dictionary:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ python myapp.py --json
|
||||
$ python myapp.py -o json
|
||||
{"foo": "bar"}
|
||||
|
||||
|
||||
@ -73,12 +73,13 @@ Which looks like:
|
||||
MyApp Does Amazing Things
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--debug toggle debug output
|
||||
--quiet suppress all output
|
||||
--json toggle json output handler
|
||||
--yaml toggle yaml output handler
|
||||
-h, --help show this help message and exit
|
||||
--debug toggle debug output
|
||||
--quiet suppress all output
|
||||
-o {json,yaml} output format
|
||||
|
||||
|
||||
Note the ``--json`` and ``--yaml`` configuration options that are provided
|
||||
by the loaded extensions.
|
||||
Note the ``-o`` command line option that are provided by Cement allowing the
|
||||
end user to override the output handler with the available/loaded extensions
|
||||
(that support this feature).
|
||||
|
||||
|
||||
@ -12,6 +12,40 @@ Upgrading from 2.2.x to 2.4.x
|
||||
Cement 2.4 introduced a few incompatible changes from the previous 2.2 stable
|
||||
release, as noted in the :ref:`Changelog <changelog>`.
|
||||
|
||||
|
||||
error: unrecognized arguments: --json/--yaml
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
After upgrading to Cement > 2.3.2 you might encounter the error:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
error: unrecognized arguments: --json
|
||||
|
||||
|
||||
Or similar errors like:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
error: unrecognized arguments: --yaml
|
||||
|
||||
|
||||
This is due to a design change, and a new feature allowing the end user to
|
||||
optionally override handlers via command line. Rather than having a unique
|
||||
option for every type of output handler, you now have one option that allows
|
||||
overriding the defined output handler by passing it the handler label.
|
||||
|
||||
Note that only handlers that have ``overridable = True`` in their meta-data
|
||||
will be valid options.
|
||||
|
||||
To resolve this issue, you simply need to pass ``-o json`` or ``-o yaml`` at
|
||||
command line to override the default output handler.
|
||||
|
||||
Related:
|
||||
|
||||
* :issue:`229`
|
||||
|
||||
|
||||
NoSectionError: No section: 'log'
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -33,6 +67,11 @@ The necessary change to resolve this issue is to change all references of
|
||||
``log`` in relation to the log configuration section, to ``log.logging``.
|
||||
|
||||
|
||||
Related:
|
||||
|
||||
* :issue:`227`
|
||||
|
||||
|
||||
TypeError: load() takes no arguments (1 given)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from cement.core import foundation, exc, backend, config, extension, plugin
|
||||
from cement.core import log, output, handler, hook, arg, controller
|
||||
from cement.utils import test
|
||||
@ -46,6 +47,7 @@ def my_hook_three(app):
|
||||
|
||||
class FoundationTestCase(test.CementCoreTestCase):
|
||||
def setUp(self):
|
||||
super(FoundationTestCase, self).setUp()
|
||||
self.app = self.make_app('my_app')
|
||||
|
||||
def test_argv_is_none(self):
|
||||
@ -139,6 +141,34 @@ class FoundationTestCase(test.CementCoreTestCase):
|
||||
app.output = None
|
||||
app.render(dict(foo='bar'))
|
||||
|
||||
def test_render_out_to_file(self):
|
||||
self.app = self.make_app(APP, extensions=['json'],
|
||||
output_handler='json')
|
||||
self.app.setup()
|
||||
self.app.run()
|
||||
|
||||
f = open(self.tmp_file, 'w')
|
||||
self.app.render(dict(foo='bar'), out=f)
|
||||
f.close()
|
||||
|
||||
f = open(self.tmp_file, 'r')
|
||||
data = json.load(f)
|
||||
f.close()
|
||||
|
||||
self.eq(data, dict(foo='bar'))
|
||||
|
||||
@test.raises(TypeError)
|
||||
def test_render_bad_out(self):
|
||||
self.app.setup()
|
||||
self.app.run()
|
||||
|
||||
try:
|
||||
self.app.render(dict(foo='bar'), out='bogus type')
|
||||
except TypeError as e:
|
||||
self.eq(e.args[0], "Argument 'out' must be a 'file' like object")
|
||||
raise
|
||||
|
||||
|
||||
@test.raises(exc.FrameworkError)
|
||||
def test_bad_label(self):
|
||||
try:
|
||||
@ -171,7 +201,6 @@ class FoundationTestCase(test.CementCoreTestCase):
|
||||
|
||||
def test_lay_cement(self):
|
||||
app = self.make_app('test', argv=['--quiet'])
|
||||
app = self.make_app('test', argv=['--json', '--yaml'])
|
||||
|
||||
def test_none_member(self):
|
||||
class Test(object):
|
||||
@ -270,3 +299,42 @@ class FoundationTestCase(test.CementCoreTestCase):
|
||||
except AssertionError as e:
|
||||
self.eq(e.args[0], "Invalid exit status code (must be integer)")
|
||||
raise
|
||||
|
||||
def test_handler_override_options(self):
|
||||
app = self.make_app(APP,
|
||||
argv=['-o', 'json'],
|
||||
extensions=['yaml', 'json'],
|
||||
)
|
||||
app.setup()
|
||||
app.run()
|
||||
self.eq(app._meta.output_handler, 'json')
|
||||
|
||||
def test_handler_override_options_is_none(self):
|
||||
app = self.make_app(APP,
|
||||
core_handler_override_options=None,
|
||||
handler_override_options=None
|
||||
)
|
||||
app.setup()
|
||||
app.run()
|
||||
|
||||
def test_handler_override_invalid_interface(self):
|
||||
app = self.make_app(APP,
|
||||
handler_override_options=dict(
|
||||
bogus_interface = (['-f'], ['--foo'], {}),
|
||||
)
|
||||
)
|
||||
app.setup()
|
||||
app.run()
|
||||
|
||||
def test_handler_override_options_not_passed(self):
|
||||
app = self.make_app(APP,
|
||||
extensions=['yaml', 'json'],
|
||||
)
|
||||
app.setup()
|
||||
app.run()
|
||||
|
||||
def test_suppress_output_while_debug(self):
|
||||
app = self.make_app(APP, debug=True)
|
||||
app.setup()
|
||||
app._suppress_output()
|
||||
|
||||
|
||||
@ -5,6 +5,10 @@ import sys
|
||||
from tempfile import mkstemp
|
||||
from cement.core import handler, backend, hook
|
||||
from cement.utils import test
|
||||
from cement.utils.misc import rando
|
||||
|
||||
APP = rando()[:12]
|
||||
|
||||
|
||||
class JsonExtTestCase(test.CementExtTestCase):
|
||||
CONFIG = '''{
|
||||
@ -38,7 +42,7 @@ class JsonExtTestCase(test.CementExtTestCase):
|
||||
output_handler='json',
|
||||
config_handler='json',
|
||||
config_files = [self.tmppath],
|
||||
argv=['--json']
|
||||
argv=['-o', 'json']
|
||||
)
|
||||
|
||||
def test_json(self):
|
||||
@ -67,3 +71,13 @@ class JsonExtTestCase(test.CementExtTestCase):
|
||||
|
||||
self.eq(self.app.config.get_section_dict('section'),
|
||||
self.CONFIG_PARSED['section'])
|
||||
|
||||
def test_handler_override_options_is_none(self):
|
||||
app = self.make_app(APP,
|
||||
extensions=['json'],
|
||||
core_handler_override_options={},
|
||||
handler_override_options={}
|
||||
)
|
||||
app.setup()
|
||||
app.run()
|
||||
app.render(dict(foo='bar'))
|
||||
|
||||
@ -6,6 +6,9 @@ import yaml
|
||||
from tempfile import mkstemp
|
||||
from cement.core import handler, hook
|
||||
from cement.utils import test
|
||||
from cement.utils.misc import rando
|
||||
|
||||
APP = rando()[:12]
|
||||
|
||||
|
||||
class YamlExtTestCase(test.CementTestCase):
|
||||
@ -41,7 +44,7 @@ class YamlExtTestCase(test.CementTestCase):
|
||||
config_handler='yaml',
|
||||
output_handler='yaml',
|
||||
config_files = [self.tmppath],
|
||||
argv=['--yaml']
|
||||
argv=['-o', 'yaml']
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
@ -74,3 +77,13 @@ class YamlExtTestCase(test.CementTestCase):
|
||||
|
||||
self.eq(self.app.config.get_section_dict('section'),
|
||||
self.CONFIG_PARSED['section'])
|
||||
|
||||
def test_handler_override_options_is_none(self):
|
||||
app = self.make_app(APP,
|
||||
extensions=['yaml'],
|
||||
core_handler_override_options=None,
|
||||
handler_override_options=None
|
||||
)
|
||||
app.setup()
|
||||
app.run()
|
||||
app.render(dict(foo='bar'))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user