Resolved Issue 172 - PEP8 Compliance

This commit is contained in:
BJ Dierkes 2012-09-07 15:30:54 -05:00
parent dccd46e67e
commit 9f8b6dc0c6
31 changed files with 1235 additions and 1137 deletions

View File

@ -36,6 +36,10 @@ Features:
* :issue:`165` - Allow utils.fs.backup() to support a suffix kwarg
* :issue:`166` - Ability to set the 'app' for CementTestCase.make_app()
Misc:
* :issue:`172` - 100% PEP8 Compliant
Incompatible Changes:
* :issue:`167` - Listed above, in order to fix this issue as well as

View File

@ -9,7 +9,7 @@ introduce a standard, and feature-full platform for both simple and complex
command line applications as well as support rapid development needs without
sacrificing quality.
[![Continuous Integration Status](https://secure.travis-ci.org/cement/cement.png)](http://travis-ci.org/cement/cement)
[![Continuous Integration Status](http://travis-ci.org/cement/cement.png)](http://travis-ci.org/cement/cement)
Cement core features include (but are not limited to):
@ -25,7 +25,8 @@ Cement core features include (but are not limited to):
* Cache handler interface adds caching support for improved performance
* Controller handler supports sub-commands, and nested controllers
* Zero external dependencies* (ext's with dependencies ship separately)
* 100% test coverage
* 100% test coverage using Nose
* 100% PEP8 compliant using `pep8` and `autopep8` tools
* Extensive Sphinx documentation
* Tested on Python 2.6, 2.7, 3.1, and 3.2

View File

@ -1 +1 @@
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover

View File

@ -7,106 +7,109 @@ from ..core import backend, interface, handler
LOG = backend.minimal_logger(__name__)
def argument_validator(klass, obj):
"""Validates a handler implementation against the IArgument interface."""
members = [
'_setup',
'parse',
'add_argument',
]
]
interface.validate(IArgument, obj, members)
class IArgument(interface.Interface):
"""
This class defines the Argument Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Argument Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below. Implementations do *not* subclass from interfaces.
Example:
.. code-block:: python
from cement.core import interface, arg
class MyArgumentHandler(arg.CementArgumentHandler):
class Meta:
interface = arg.IArgument
label = 'my_argument_handler'
"""
class IMeta:
"""Interface meta-data options."""
label = 'argument'
"""The string identifier of the interface."""
validator = argument_validator
"""Interface validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler Meta-data')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object
:returns: None
"""
def add_argument(*args, **kw):
"""
Add arguments for parsing. This should be -o/--option or positional.
Add arguments for parsing. This should be -o/--option or positional.
Note that the interface defines the following parameters so that at
the very least, external extensions can guarantee that they can
the very least, external extensions can guarantee that they can
properly add command line arguments when necessary. The
implementation itself should, and will provide and support many more
options than those listed here. That said, the implementation must
support the following:
:arg args: List of option arguments. Generally something like
:arg args: List of option arguments. Generally something like
['-h', '--help'].
:keyword dest: The destination name (var). Default: arg[0]'s string.
:keyword help: The help text for --help output (for that argument).
:keyword action: Must support: ['store', 'store_true', 'store_false',
:keyword action: Must support: ['store', 'store_true', 'store_false',
'store_const']
:keyword const: The value stored if action == 'store_const'.
:keyword default: The default value.
:returns: None
"""
def parse(arg_list):
"""
Parse the argument list (i.e. sys.argv). Can return any object as
long as it's members contain those of the added arguments. For
long as it's members contain those of the added arguments. For
example, if adding a '-v/--version' option that stores to the dest of
'version', then the member must be callable as 'Object().version'.
:param arg_list: A list of command line arguments.
:returns: Callable object
"""
class CementArgumentHandler(handler.CementBaseHandler):
"""
Base class that all Argument Handlers should sub-class from.
"""
"""Base class that all Argument Handlers should sub-class from."""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
label = None
"""The string identifier of the handler implementation."""
interface = IArgument
"""The interface that this class implements."""
def __init__(self, *args, **kw):
super(CementArgumentHandler, self).__init__(*args, **kw)

View File

@ -6,71 +6,74 @@ import logging
from ..core import exc
def defaults(*sections):
"""
Returns a standard dictionary object to use for application defaults.
If sections are given, it will create a nested dict for each section name.
:arg sections: Section keys to create nested dictionaries for.
:returns: Dictionary of nested dictionaries (sections)
:rtype: dict
.. code-block:: python
from cement.core import foundation, backend
defaults = backend.defaults('myapp', 'section2', 'section3')
defaults['myapp']['debug'] = False
defaults['section2']['foo'] = 'bar
defaults['section3']['foo2'] = 'bar2'
app = foundation.CementApp('myapp', config_defaults=defaults)
"""
"""
defaults = dict()
for section in sections:
defaults[section] = dict()
return defaults
def minimal_logger(name, debug=False):
"""
Setup just enough for cement to be able to do debug logging. This is the
logger used by the Cement framework, which is setup and accessed before
the application is functional (and more importantly before the
the application is functional (and more importantly before the
applications log handler is usable).
:param name: The logging namespace. This is generally '__name__' or
:param name: The logging namespace. This is generally '__name__' or
anything you want.
:param debug: Toggle debug output. Default: False
:type debug: boolean
:type debug: boolean
:returns: Logger object
.. code-block:: python
from cement.core import backend
LOG = backend.minimal_logger('cement')
LOG.debug('This is a debug message')
"""
log = logging.getLogger(name)
formatter = logging.Formatter(
"%(asctime)s (%(levelname)s) %(name)s : %(message)s")
"%(asctime)s (%(levelname)s) %(name)s : %(message)s")
console = logging.StreamHandler()
console.setFormatter(formatter)
console.setLevel(logging.INFO)
console.setLevel(logging.INFO)
log.setLevel(logging.INFO)
# FIX ME: really don't want to hard check sys.argv like this but can't
# figure any better way get logging started (only for debug) before the
# app logging is setup.
if '--debug' in sys.argv or debug:
console.setLevel(logging.DEBUG)
console.setLevel(logging.DEBUG)
log.setLevel(logging.DEBUG)
log.addHandler(console)
return log
# global handlers dict
handlers = {}
@ -78,7 +81,7 @@ handlers = {}
hooks = {}
# Save original stdout/stderr for supressing output. This is actually reset
# in foundation.CementApp.lay_cement() before nullifying output, but we set
# in foundation.CementApp.lay_cement() before nullifying output, but we set
# it here just for a default.
SAVED_STDOUT = sys.stdout
SAVED_STDERR = sys.stderr

View File

@ -4,105 +4,108 @@ from ..core import backend, exc, interface, handler
LOG = backend.minimal_logger(__name__)
def cache_validator(klass, obj):
"""Validates a handler implementation against the ICache interface."""
members = [
'_setup',
'get',
'set',
'delete',
'purge',
]
interface.validate(ICache, obj, members)
]
interface.validate(ICache, obj, members)
class ICache(interface.Interface):
"""
This class defines the Cache Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Cache Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import cache
class MyCacheHandler(object):
class Meta:
interface = cache.ICache
label = 'my_cache_handler'
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'cache'
"""The label (or type identifier) of the interface."""
validator = cache_validator
"""Interface validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler meta-data')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:returns: None
:param app_obj: The application object.
:returns: None
"""
def get(key, fallback=None):
"""
Get the value for a key in the cache. If the key does not exist
or the key/value in cache is expired, this functions must return
or the key/value in cache is expired, this functions must return
'fallback' (which in turn must default to None).
:param key: The key of the value stored in cache
:param fallback: Optional value that is returned if the cache is
:param fallback: Optional value that is returned if the cache is
expired or the key does not exist. Default: None
:returns: Unknown (whatever the value is in cache, or the `fallback`)
"""
"""
def set(key, value, time=None):
"""
Set the key/value in the cache for a set amount of `time`.
Set the key/value in the cache for a set amount of `time`.
:param key: The key of the value to store in cache.
:param value: The value of that key to store in cache.
:param time: A one-off expire time. If no time is given, then a
default value is used (determined by the implementation).
:param time: A one-off expire time. If no time is given, then a
default value is used (determined by the implementation).
:type time: integer (seconds) or None
:returns: None
"""
def delete(key):
"""
Deletes a key/value from the cache.
:param key: The key in the cache to delete.
:returns: True if the key is successfully deleted, False otherwise.
:rtype: boolean
"""
def purge():
"""
Clears all data from the cache.
"""
class CementCacheHandler(handler.CementBaseHandler):
"""
Base class that all Cache Handlers should sub-class from.
@ -110,15 +113,15 @@ class CementCacheHandler(handler.CementBaseHandler):
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
label = None
"""String identifier of this handler implementation."""
interface = ICache
"""The interface that this handler class implements."""
def __init__(self, *args, **kw):
super(CementCacheHandler, self).__init__(*args, **kw)

View File

@ -2,144 +2,146 @@
from ..core import exc, backend, interface, handler
def config_validator(klass, obj):
"""Validates a handler implementation against the IConfig interface."""
members = [
'_setup',
'keys',
'keys',
'has_key',
'get_sections',
'get_sections',
'get_section_dict',
'get',
'set',
'parse_file',
'get',
'set',
'parse_file',
'merge',
'add_section',
'has_section',
]
]
interface.validate(IConfig, obj, members)
class IConfig(interface.Interface):
"""
This class defines the Config Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Config Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
All implementations must provide sane 'default' functionality when
instantiated with no arguments. Meaning, it can and should accept
All implementations must provide sane 'default' functionality when
instantiated with no arguments. Meaning, it can and should accept
optional parameters that alter how it functions, but can not require
any parameters. When the framework first initializes handlers it does
not pass anything too them, though a handler can be instantiated first
(with or without parameters) and then passed to 'CementApp()' already
instantiated.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import config
class MyConfigHandler(config.CementConfigHandler):
class Meta:
interface = config.IConfig
label = 'my_config_handler'
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'config'
"""The string identifier of the interface."""
validator = config_validator
"""The validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler Meta-data')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:returns: None
:param app_obj: The application object.
:returns: None
"""
def parse_file(file_path):
"""
Parse config file settings from file_path. Returns True if the file
existed, and was parsed successfully. Returns False otherwise.
:param file_path: The path to the config file to parse.
:returns: True if the file was parsed, False otherwise.
:returns: True if the file was parsed, False otherwise.
:rtype: boolean
"""
def keys(section):
"""
Return a list of configuration keys from `section`.
:param section: The config [section] to pull keys from.
:returns: A list of keys in `section`.
:rtype: list
"""
def get_sections():
"""
Return a list of configuration sections. These are designated by a
[block] label in a config file.
:returns: A list of config sections.
:rtype: list
"""
def get_section_dict(section):
"""
Return a dict of configuration parameters for [section].
:param section: The config [section] to generate a dict from (using
:param section: The config [section] to generate a dict from (using
that section keys).
:returns: A dictionary of the config section.
:rtype: dict
"""
def add_section(section):
"""
Add a new section if it doesn't exist.
:param section: The [section] label to create.
:returns: None
"""
"""
def get(section, key):
"""
Return a configuration value based on [section][key]. The return
value type is unknown.
:param section: The [section] of the configuration to pull key value
:param section: The [section] of the configuration to pull key value
from.
:param key: The configuration key to get the value from.
:returns: The value of the `key` in `section`.
:returns: The value of the `key` in `section`.
:rtype: Unknown
"""
def set(section, key, value):
"""
Set a configuration value based at [section][key].
:param section: The [section] of the configuration to pull key value
:param section: The [section] of the configuration to pull key value
from.
:param key: The configuration key to set the value at.
:param value: The value to set.
@ -150,39 +152,39 @@ class IConfig(interface.Interface):
def merge(dict_obj, override=True):
"""
Merges a dict object into the configuration.
:param dict_obj: The dictionary to merge into the config
:param override: Boolean. Whether to override existing values.
:param override: Boolean. Whether to override existing values.
Default: True
:returns: None
"""
def has_section(section):
"""
Returns whether or not the section exists.
:param section: The section to test for.
:returns: boolean
"""
class CementConfigHandler(handler.CementBaseHandler):
"""
Base class that all Config Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
label = None
"""The string identifier of the implementation."""
interface = IConfig
"""The interface that this handler implements."""
def __init__(self, *args, **kw):
super(CementConfigHandler, self).__init__(*args, **kw)
super(CementConfigHandler, self).__init__(*args, **kw)

View File

@ -7,15 +7,16 @@ from ..core import backend, exc, interface, handler
LOG = backend.minimal_logger(__name__)
def controller_validator(klass, obj):
"""
Validates a handler implementation against the IController interface.
"""
members = [
'_setup',
'_dispatch',
]
]
meta = [
'label',
'interface',
@ -27,53 +28,54 @@ def controller_validator(klass, obj):
'epilog',
'stacked_on',
'hide',
]
]
interface.validate(IController, obj, members, meta=meta)
# also check _meta.arguments values
errmsg = "Controller arguments must be a list of tuples. I.e. " + \
"[ (['-f', '--foo'], dict(action='store')), ]"
try:
for _args,_kwargs in obj._meta.arguments:
for _args, _kwargs in obj._meta.arguments:
if not type(_args) is list:
raise exc.InterfaceError(errmsg)
if not type(_kwargs) is dict:
raise exc.InterfaceError(errmsg)
except ValueError:
raise exc.InterfaceError(errmsg)
class IController(interface.Interface):
"""
This class defines the Controller Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Controller Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import controller
class MyBaseController(controller.CementBaseController):
class Meta:
interface = controller.IController
...
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'controller'
"""The string identifier of the interface."""
validator = controller_validator
"""The interface validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler meta-data')
def _setup(app_obj):
"""
The _setup function is after application initialization and after it
@ -82,62 +84,63 @@ class IController(interface.Interface):
right before it's _dispatch() function is called to execute a command.
Must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:returns: None
"""
def _dispatch(self):
"""
Reads the application object's data to dispatch a command from this
controller. For example, reading self.app.pargs to determine what
command was passed, and then executing that command function.
Note that Cement does *not* parse arguments when calling _dispatch()
on a controller, as it expects the controller to handle parsing
on a controller, as it expects the controller to handle parsing
arguments (I.e. self.app.args.parse()).
:returns: None
:returns: None
"""
class expose(object):
"""
Used to expose controller functions to be listed as commands, and to
Used to expose controller functions to be listed as commands, and to
decorate the function with Meta data for the argument parser.
:param hide: Whether the command should be visible.
:type hide: boolean
:param help: Help text to display for that command.
:type help: str
:param aliases: Aliases to this command.
:type aliases: list
Usage:
.. code-block:: python
from cement.core.controller import CementBaseController, expose
class MyAppBaseController(CementBaseController):
class Meta:
label = 'base'
@expose(hide=True, aliases=['run'])
def default(self):
print("In MyAppBaseController.default()")
@expose()
def my_command(self):
print("In MyAppBaseController.my_command()")
"""
def __init__(self, hide=False, help='', aliases=[]):
self.hide = hide
self.help = help
self.aliases = aliases
def __call__(self, func):
self.func = func
self.func.label = self.func.__name__
@ -147,23 +150,24 @@ class expose(object):
self.func.aliases = self.aliases
return self.func
class CementBaseController(handler.CementBaseHandler):
"""
This is an implementation of the
`IControllerHandler <#cement.core.controller.IController>`_ interface, but
as a base class that application controllers `should` subclass from.
This is an implementation of the
`IControllerHandler <#cement.core.controller.IController>`_ interface, but
as a base class that application controllers `should` subclass from.
Registering it directly as a handler is useless.
NOTE: This handler **requires** that the applications 'arg_handler' be
argparse. If using an alternative argument handler you will need to
argparse. If using an alternative argument handler you will need to
write your own controller base class.
Usage:
.. code-block:: python
from cement.core import controller
class MyAppBaseController(controller.CementBaseController):
class Meta:
label = 'base'
@ -172,97 +176,97 @@ class CementBaseController(handler.CementBaseHandler):
arguments = []
epilog = "This is the text at the bottom of --help."
# ...
class MyStackedController(controller.CementBaseController):
class Meta:
label = 'second_controller'
aliases = ['sec', 'secondary']
stacked_on = 'base'
# ...
"""
class Meta:
"""
Controller meta-data (can be passed as keyword arguments to the parent
Controller meta-data (can be passed as keyword arguments to the parent
class).
"""
interface = IController
"""The interface this class implements."""
label = 'base'
"""The string identifier for the controller."""
aliases = []
"""
A list of aliases for the controller. Will be treated like
command/function aliases for non-stacked controllers. For example:
'myapp <controller_label> --help' is the same as
'myapp <controller_label> --help' is the same as
'myapp <controller_alias> --help'.
"""
description = None
"""The description shown at the top of '--help'. Default: None"""
config_section = None
"""
A config [section] to merge config_defaults into. Cement will default
A config [section] to merge config_defaults into. Cement will default
to controller.<label> if None is set.
"""
config_defaults = {}
config_defaults = {}
"""
Configuration defaults (type: dict) that are merged into the
Configuration defaults (type: dict) that are merged into the
applications config object for the config_section mentioned above.
"""
arguments = []
"""
Arguments to pass to the argument_handler. The format is a list
of tuples whos items are a ( list, dict ). Meaning:
``[ ( ['-f', '--foo'], dict(dest='foo', help='foo option') ), ]``
This is equivelant to manually adding each argument to the argument
parser as in the following example:
``parser.add_argument(['-f', '--foo'], help='foo option', dest='foo')``
"""
stacked_on = None
"""
A label of another controller to 'stack' commands/arguments on top of.
"""
hide = False
"""Whether or not to hide the controller entirely."""
epilog = None
"""
The text that is displayed at the bottom when '--help' is passed.
"""
usage = None
"""
The text that is displayed at the top when '--help' is passed.
The text that is displayed at the top when '--help' is passed.
Although the default is `None`, Cement will set this to a generic
usage based on the `prog`, `controller` name, etc if nothing else is
passed.
"""
argument_formatter = argparse.RawDescriptionHelpFormatter
"""
The argument formatter class to use to display --help output.
"""
### FIX ME: What is this used for???
ignored = ['visible', 'hidden', 'exposed']
def __init__(self, *args, **kw):
super(CementBaseController, self).__init__(*args, **kw)
self.app = None
self.command = 'default'
self.config = None
@ -272,7 +276,7 @@ class CementBaseController(handler.CementBaseHandler):
self.hidden = {}
self.exposed = {}
self._arguments = []
def _setup(self, app_obj):
"""
See `IController._setup() <#cement.core.cache.IController._setup>`_.
@ -282,22 +286,22 @@ class CementBaseController(handler.CementBaseHandler):
if self._meta.description is None:
self._meta.description = "%s Controller" % \
self._meta.label.capitalize()
# shortcuts
self.config = self.app.config
self.log = self.app.log
self.pargs = self.app.pargs
self.render = self.app.render
self._collect()
def _parse_args(self):
"""
Parses command line arguments and determine a command to dispatch.
"""
# chop off a command argument if it matches an exposed command
if len(self.app.argv) > 0 and not self.app.argv[0].startswith('-'):
# translate dashes back to underscores
cmd = re.sub('-', '_', self.app.argv[0])
if cmd in self.exposed:
@ -306,33 +310,33 @@ class CementBaseController(handler.CementBaseHandler):
else:
# FIX ME: This seems inefficient. Would be better to have an
# alias map... with key: controller_label.func_name
for label,func in self.exposed.items():
for label, func in self.exposed.items():
if self.app.argv[0] in func['aliases']:
self.command = func['label']
self.app.argv.pop(0)
break
self.app.args.description = self._help_text
self.app.args.usage = self._usage_text
self.app.args.formatter_class=self._meta.argument_formatter
self.app.args.formatter_class = self._meta.argument_formatter
self.app._parse_args()
self.pargs = self.app.pargs
def _dispatch(self):
"""
Takes the remaining arguments from self.app.argv and parses for a
command to dispatch, and if so... dispatches it.
"""
self._add_arguments_to_parser()
self._parse_args()
if not self.command:
LOG.debug("no command to dispatch")
else:
func = self.exposed[self.command]
LOG.debug("dispatching command: %s.%s" % \
else:
func = self.exposed[self.command]
LOG.debug("dispatching command: %s.%s" %
(func['controller'], func['label']))
if func['controller'] == self._meta.label:
@ -347,44 +351,44 @@ class CementBaseController(handler.CementBaseHandler):
"""
This is the default action if no arguments (sub-commands) are passed
at command line.
:raises: NotImplementedError
"""
raise NotImplementedError
def _add_arguments_to_parser(self):
"""
Run after _collect(). Add the collected arguments to the apps
argument parser.
"""
for _args,_kwargs in self._arguments:
for _args, _kwargs in self._arguments:
self.app.args.add_argument(*_args, **_kwargs)
def _collect_from_self(self):
"""
Collect arguments from this controller.
:raises: cement.core.exc.FrameworkError
"""
# collect our Meta arguments
for _args,_kwargs in self._meta.arguments:
for _args, _kwargs in self._meta.arguments:
if (_args, _kwargs) not in self._arguments:
self._arguments.append((_args, _kwargs))
# epilog only good for non-stacked controllers
if hasattr(self._meta, 'epilog'):
if not hasattr(self._meta, 'stacked_on') or \
not self._meta.stacked_on:
if not hasattr(self._meta, 'stacked_on') or \
not self._meta.stacked_on:
self.app.args.epilog = self._meta.epilog
# collect exposed commands from ourself
for member in dir(self):
if member in self.ignored or member.startswith('_'):
continue
func = getattr(self, member)
if hasattr(func, 'exposed'):
func_dict = dict(
@ -393,83 +397,83 @@ class CementBaseController(handler.CementBaseHandler):
help=func.help,
aliases=func.aliases,
hide=func.hide,
)
)
if func_dict['label'] == self._meta.label:
raise exc.FrameworkError(
"Controller command '%s' " % func_dict['label'] + \
"Controller command '%s' " % func_dict['label'] +
"matches controller label. Use 'default' instead."
)
)
self.exposed[func.label] = func_dict
if func.hide:
self.hidden[func.label] = func_dict
else:
if not getattr(self._meta, 'hide', None):
self.visible[func.label] = func_dict
def _collect_from_non_stacked_controller(self, controller):
"""
Collect arguments from non-stacked controllers.
:param controller: The controller to collect arguments from.
:type controller: Uninstantiated controller class
"""
contr = controller()
LOG.debug('exposing %s controller' % contr._meta.label)
func_dict = dict(
controller=contr._meta.label,
label=contr._meta.label,
help=contr._meta.description,
aliases=contr._meta.aliases, # for display only
aliases=contr._meta.aliases, # for display only
hide=False,
)
)
# expose the controller label as a sub command
self.exposed[contr._meta.label] = func_dict
if not getattr(contr._meta, 'hide', None):
self.visible[contr._meta.label] = func_dict
def _collect_from_stacked_controller(self, controller):
def _collect_from_stacked_controller(self, controller):
"""
Collect arguments from stacked controllers.
:param controller: The controller to collect arguments from.
:type controller: Uninstantiated controller class
:raises: cement.core.exc.FrameworkError
"""
"""
contr = controller()
contr._setup(self.app)
contr._collect()
# add stacked arguments into ours
for _args,_kwargs in contr._arguments:
for _args, _kwargs in contr._arguments:
if (_args, _kwargs) not in self._arguments:
self._arguments.append((_args, _kwargs))
# add stacked commands into ours
# add stacked commands into ours
# determine hidden vs. visible commands
func_dicts = contr.exposed
for label in func_dicts:
if label in self.exposed:
if label in self.exposed:
if label == 'default':
LOG.debug(
"ignoring duplicate command '%s' " % label + \
"found in '%s' " % contr._meta.label + \
"ignoring duplicate command '%s' " % label +
"found in '%s' " % contr._meta.label +
"controller."
)
)
continue
else:
raise exc.FrameworkError(
"Duplicate command '%s' " % label + \
"found in '%s' " % contr._meta.label + \
"Duplicate command '%s' " % label +
"found in '%s' " % contr._meta.label +
"controller."
)
)
if func_dicts[label]['hide']:
self.hidden[label] = func_dicts[label]
elif not getattr(contr._meta, 'hide', False):
@ -478,18 +482,18 @@ class CementBaseController(handler.CementBaseHandler):
def _collect_from_controllers(self):
"""Collect arguments from all controllers."""
for controller in handler.list('controller'):
contr = controller()
if contr._meta.label == self._meta.label:
continue
# expose other controllers as commands also
if not hasattr(contr._meta, 'stacked_on') \
or contr._meta.stacked_on is None:
or contr._meta.stacked_on is None:
# only show non-stacked controllers under base
if self._meta.label == 'base':
self._collect_from_non_stacked_controller(controller)
self._collect_from_non_stacked_controller(controller)
elif contr._meta.stacked_on == self._meta.label:
self._collect_from_stacked_controller(controller)
@ -497,23 +501,23 @@ class CementBaseController(handler.CementBaseHandler):
"""
Collects all commands and arguments from this controller, and other
availble controllers.
:raises: cement.core.exc.FrameworkError
"""
LOG.debug("collecting arguments and commands from '%s' controller" % \
LOG.debug("collecting arguments and commands from '%s' controller" %
self)
self.visible = {}
self.hidden = {}
self.exposed = {}
self._arguments = []
self._collect_from_self()
self._collect_from_controllers()
self._check_for_duplicates_on_aliases()
def _check_for_duplicates_on_aliases(self):
known_aliases = {}
for label in self.exposed:
@ -521,28 +525,29 @@ class CementBaseController(handler.CementBaseHandler):
for alias in func['aliases']:
if alias in known_aliases:
raise exc.FrameworkError(
"Alias '%s' " % alias + \
"from the '%s' controller " % func['controller'] + \
"collides with an alias of the same name " + \
"in the '%s' controller." % known_aliases[alias]['controller']
)
"Alias '%s' " % alias +
"from the '%s' controller " % func['controller'] +
"collides with an alias of the same name " +
"in the '%s' controller." % known_aliases[
alias]['controller']
)
elif alias in self.exposed.keys():
raise exc.FrameworkError(
"Alias '%s' " % alias + \
"from the '%s' controller " % func['controller'] + \
"collides with a command of the same name in the " + \
"'%s' " % self.exposed[alias]['controller'] + \
"Alias '%s' " % alias +
"from the '%s' controller " % func['controller'] +
"collides with a command of the same name in the " +
"'%s' " % self.exposed[alias]['controller'] +
"controller."
)
)
known_aliases[alias] = func
@property
def _usage_text(self):
"""Returns the usage text displayed when '--help' is passed."""
if self._meta.usage is not None:
return self._meta.usage
if self == self.app._meta.base_controller:
txt = "%s <CMD> -opt1 --opt2=VAL [arg1] [arg2] ..." % \
self.app.args.prog
@ -550,38 +555,38 @@ class CementBaseController(handler.CementBaseHandler):
txt = "%s %s <CMD> -opt1 --opt2=VAL [arg1] [arg2] ..." % \
(self.app.args.prog, self._meta.label)
return txt
@property
def _help_text(self):
"""Returns the help text displayed when '--help' is passed."""
cmd_txt = ''
# hack it up to keep commands in alphabetical order
sorted_labels = []
for label in list(self.visible.keys()):
old_label = label
label = re.sub('_', '-', label)
sorted_labels.append(label)
if label != old_label:
self.visible[label] = self.visible[old_label]
del self.visible[old_label]
sorted_labels.sort()
for label in sorted_labels:
func = self.visible[label]
if len(func['aliases']) > 0:
cmd_txt = cmd_txt + " %s (aliases: %s)\n" % \
(label, ', '.join(func['aliases']))
(label, ', '.join(func['aliases']))
else:
cmd_txt = cmd_txt + " %s\n" % label
if func['help']:
cmd_txt = cmd_txt + " %s\n\n" % func['help']
else:
cmd_txt = cmd_txt + "\n"
if len(cmd_txt) > 0:
txt = '''%s
@ -589,9 +594,9 @@ commands:
%s
''' % (self._meta.description, cmd_txt)
else:
txt = self._meta.description
return textwrap.dedent(txt)
return textwrap.dedent(txt)

View File

@ -1,34 +1,38 @@
"""Cement core exceptions module."""
class FrameworkError(Exception):
"""
General framework (non-application) related errors.
:param msg: The error message.
"""
def __init__(self, msg):
Exception.__init__(self)
self.msg = msg
def __str__(self):
return self.msg
class InterfaceError(FrameworkError):
"""Interface related errors."""
pass
class CaughtSignal(FrameworkError):
"""
Raised when a defined signal is caught. For more information regarding
signals, reference the `signal <http://docs.python.org/library/signal.html>`_ library.
Raised when a defined signal is caught. For more information regarding
signals, reference the
`signal <http://docs.python.org/library/signal.html>`_ library.
:param signum: The signal number.
:param frame: The signal frame.
"""
def __init__(self, signum, frame):
msg = 'Caught signal %s' % signum
super(CaughtSignal, self).__init__(msg)
self.signum = signum
self.frame = frame
self.frame = frame

View File

@ -4,158 +4,161 @@ import sys
from ..core import backend, exc, interface, handler
if sys.version_info[0] >= 3:
from imp import reload # pragma: no cover
from imp import reload # pragma: no cover
LOG = backend.minimal_logger(__name__)
def extension_validator(klass, obj):
"""
Validates an handler implementation against the IExtension interface.
"""
members = [
'_setup',
'load_extension',
'load_extensions',
'get_loaded_extensions',
]
]
interface.validate(IExtension, obj, members)
class IExtension(interface.Interface):
"""
This class defines the Extension Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Extension Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import extension
class MyExtensionHandler(object):
class Meta:
interface = extension.IExtension
label = 'my_extension_handler'
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'extension'
"""The string identifier of the interface."""
validator = extension_validator
"""The interface validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler Meta-data class')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:param app_obj: The application object.
:returns: None
"""
def load_extension(self, ext_module):
"""
Load an extension whose module is 'ext_module'. For example,
Load an extension whose module is 'ext_module'. For example,
'cement.ext.ext_configobj'.
:param ext_module: The name of the extension to load.
:type ext_module: str
"""
def load_extensions(self, ext_list):
"""
Load all extensions from ext_list.
:param ext_list: A list of extension modules to load. For example:
:param ext_list: A list of extension modules to load. For example:
``['cement.ext.ext_configobj', 'cement.ext.ext_logging']``
:type ext_list: list
"""
class CementExtensionHandler(handler.CementBaseHandler):
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
interface = IExtension
"""The interface that this class implements."""
label = 'cement'
"""The string identifier of the handler."""
def __init__(self, **kw):
"""
This is an implementation of the IExtentionHandler interface. It
This is an implementation of the IExtentionHandler interface. It
handles loading framework extensions.
"""
super(CementExtensionHandler, self).__init__(**kw)
self.app = None
self._loaded_extensions = []
def get_loaded_extensions(self):
"""Returns list of loaded extensions."""
return self._loaded_extensions
def load_extension(self, ext_module):
"""
Given an extension module name, load or in other-words 'import' the
Given an extension module name, load or in other-words 'import' the
extension.
:param ext_module: The extension module name. For example:
:param ext_module: The extension module name. For example:
'cement.ext.ext_logging'.
:type ext_module: str
:raises: cement.core.exc.FrameworkError
"""
# If its not a full module path then preppend our default path
if ext_module.find('.') == -1:
ext_module = 'cement.ext.ext_%s' % ext_module
if ext_module in self._loaded_extensions:
LOG.debug("framework extension '%s' already loaded" % ext_module)
return
return
LOG.debug("loading the '%s' framework extension" % ext_module)
try:
if ext_module not in sys.modules:
__import__(ext_module, globals(), locals(), [], 0)
if hasattr(sys.modules[ext_module], 'load'):
sys.modules[ext_module].load()
if ext_module not in self._loaded_extensions:
if ext_module not in self._loaded_extensions:
self._loaded_extensions.append(ext_module)
except ImportError as e:
raise exc.FrameworkError(e.args[0])
def load_extensions(self, ext_list):
"""
Given a list of extension modules, iterate over the list and pass
individually to self.load_extension().
:param ext_list: A list of extension modules.
:type ext_list: list
"""
for ext in ext_list:
self.load_extension(ext)

View File

@ -10,46 +10,50 @@ 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
from ..utils.misc import is_true
from ..utils import fs
if sys.version_info[0] >= 3:
if sys.version_info[0] >= 3:
from imp import reload # pragma: nocover
LOG = backend.minimal_logger(__name__)
LOG = backend.minimal_logger(__name__)
class NullOut(object):
def write(self, s):
pass
def flush(self):
pass
def cement_signal_handler(signum, frame):
"""
Catch a signal, run the 'signal' hook, and then raise an exception
Catch a signal, run the 'signal' hook, and then raise an exception
allowing the app to handle logic elsewhere.
:param signum: The signal number
:param frame: The signal frame.
:raises: cement.core.exc.CaughtSignal
"""
LOG.debug('Caught signal %s' % signum)
"""
LOG.debug('Caught signal %s' % signum)
for res in hook.run('signal', signum, frame):
pass
raise exc.CaughtSignal(signum, frame)
class CementApp(meta.MetaMixin):
"""
The primary class to build applications from.
Usage:
The following is the simplest CementApp:
.. code-block:: python
from cement.core import foundation
app = foundation.CementApp('helloworld')
try:
@ -57,13 +61,13 @@ class CementApp(meta.MetaMixin):
app.run()
finally:
app.close()
A more advanced example looks like:
.. code-block:: python
from cement.core import foundation, controller
class MyController(controller.CementBaseController):
class Meta:
label = 'base'
@ -74,11 +78,11 @@ class CementApp(meta.MetaMixin):
debug=False,
some_config_param='some_value',
)
@controller.expose(help='This is the default command', hide=True)
def default(self):
print('Hello World')
class MyApp(foundation.CementApp):
class Meta:
label = 'helloworld'
@ -91,251 +95,251 @@ class CementApp(meta.MetaMixin):
app.run()
finally:
app.close()
"""
class Meta:
"""
Application meta-data (can also be passed as keyword arguments to the
Application meta-data (can also be passed as keyword arguments to the
parent class).
"""
label = None
"""
The name of the application. This should be the common name as you
would see and use at the command line. For example 'helloworld', or
The name of the application. This should be the common name as you
would see and use at the command line. For example 'helloworld', or
'my-awesome-app'.
"""
debug = False
"""
Used internally, and should not be used by developers. This is set
to `True` if `--debug` is passed at command line."""
config_files = None
"""
List of config files to parse.
List of config files to parse.
Note: Though Meta.config_section defaults to None, Cement will
set this to a default list based on Meta.label (or in other words,
set this to a default list based on Meta.label (or in other words,
the name of the application). This will equate to:
.. code-block:: python
['/etc/<app_label>/<app_label>.conf',
'~/.<app_label>.conf',
['/etc/<app_label>/<app_label>.conf',
'~/.<app_label>.conf',
'~/.<app_label>/config']
"""
plugins = []
"""
A list of plugins to load. This is generally considered bad
A list of plugins to load. This is generally considered bad
practice since plugins should be dynamically enabled/disabled
via a plugin config file.
via a plugin config file.
"""
plugin_config_dir = None
"""
A directory path where plugin config files can be found. Files
must end in '.conf'. By default, this setting is also overridden
by the '[base] -> plugin_config_dir' config setting parsed in any
of the application configuration files.
Note: Though the meta default is None, Cement will set this to
``/etc/<app_label>/plugins.d/`` if not set during app.setup().
"""
plugin_bootstrap = None
"""
A python package (dotted import path) where plugin code can be
loaded from. This is generally something like 'myapp.plugins'
where a plugin file would live at ``myapp/plugins/myplugin.py``.
This provides a facility for applications that use 'namespace'
This provides a facility for applications that use 'namespace'
packages allowing plugins to share the applications python
namespace.
"""
plugin_dir = None
"""
A directory path where plugin code (modules) can be loaded from.
By default, this setting is also overridden by the
'[base] -> plugin_dir' config setting parsed in any of the
application configuration files (where [base] is the
By default, this setting is also overridden by the
'[base] -> plugin_dir' config setting parsed in any of the
application configuration files (where [base] is the
base configuration section of the application which is determined
by Meta.config_section but defaults to Meta.label).
Note: Though the meta default is None, Cement will set this to
``/usr/lib/<app_label>/plugins/`` if not set during app.setup()
"""
argv = None
"""
A list of arguments to use for parsing command line arguments
and options.
Note: Though Meta.argv defaults to None, Cement will set this to
``list(sys.argv[1:])`` if no argv is set in Meta during setup().
"""
arguments_override_config = False
"""
A boolean to toggle whether command line arguments should
A boolean to toggle whether command line arguments should
override configuration values if the argument name matches the
config key. I.e. --foo=bar would override config['myapp']['foo'].
"""
config_section = None
"""
The base configuration section for the application.
Note: Though Meta.config_section defaults to None, Cement will
set this to the value of Meta.label (or in other words, the name
of the application).
"""
config_defaults = None
"""Default configuration dictionary. Must be of type 'dict'."""
catch_signals = [signal.SIGTERM, signal.SIGINT]
"""
List of signals to catch, and raise exc.CaughtSignal for.
Can be set to None to disable signal handling.
"""
signal_handler = cement_signal_handler
"""A function that is called to handle any caught signals."""
config_handler = ext_configparser.ConfigParserConfigHandler
"""
A handler class that implements the IConfig interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
extension_handler = extension.CementExtensionHandler
"""
A handler class that implements the IExtension interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
log_handler = ext_logging.LoggingLogHandler
"""
A handler class that implements the ILog interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
plugin_handler = ext_plugin.CementPluginHandler
"""
A handler class that implements the IPlugin interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
argument_handler = ext_argparse.ArgParseArgumentHandler
"""
A handler class that implements the IArgument interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
output_handler = ext_nulloutput.NullOutputHandler
"""
A handler class that implements the IOutput interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
cache_handler = None
"""
A handler class that implements the ICache interface. This can
be a string (label of a registered handler), an uninstantiated
class, or an instantiated class object.
"""
base_controller = None
"""
This is the base application controller. If a controller is set,
runtime operations are passed to the controller for command
runtime operations are passed to the controller for command
dispatch and argument parsing when CementApp.run() is called.
Note that cement will automatically set the `base_controller` to a
registered controller whose label is 'base' (only if `base_controller`
is not currently set).
"""
extensions = []
extensions = []
"""List of additional framework extensions to load."""
bootstrap = None
bootstrap = None
"""
A bootstrapping module to load after app creation, and before
app.setup() is called. This is useful for larger applications
that need to offload their bootstrapping code such as registering
hooks/handlers/etc to another file.
This must be a dotted python module path.
hooks/handlers/etc to another file.
This must be a dotted python module path.
I.e. 'myapp.bootstrap' (myapp/bootstrap.py). Cement will then
import the module, and if the module has a 'load()' function, that
will also be called. Essentially, this is the same as an
will also be called. Essentially, this is the same as an
extension or plugin, but as a facility for the application itself
to bootstrap 'hardcoded' application code. It is also called
before plugins are loaded.
"""
core_extensions = [
core_extensions = [
'cement.ext.ext_nulloutput',
'cement.ext.ext_plugin',
'cement.ext.ext_configparser',
'cement.ext.ext_logging',
'cement.ext.ext_configparser',
'cement.ext.ext_logging',
'cement.ext.ext_argparse',
]
]
"""
List of Cement core extensions. These are generally required by
Cement and should only be modified if you know what you're
doing. Use 'extensions' to add to this list, rather than
Cement and should only be modified if you know what you're
doing. Use 'extensions' to add to this list, rather than
overriding core extensions. That said if you want to prune down
your application, you can remove core extensions if they are
not necessary (for example if using your own log handler
extension you likely don't want/need LoggingLogHandler to be
not necessary (for example if using your own log handler
extension you likely don't want/need LoggingLogHandler to be
registered).
"""
core_meta_override = [
'debug',
'plugin_config_dir',
'plugin_config_dir',
'plugin_dir'
]
]
"""
List of meta options that can/will be overridden by config options
of the '[base]' config section (where [base] is the base
configuration section of the application which is determined by
Meta.config_section but defaults to Meta.label). These overrides
are required by the framework to function properly and should not
be used by end user (developers) unless you really know what
you're doing. To add your own extended meta overrides please use
of the '[base]' config section (where [base] is the base
configuration section of the application which is determined by
Meta.config_section but defaults to Meta.label). These overrides
are required by the framework to function properly and should not
be used by end user (developers) unless you really know what
you're doing. To add your own extended meta overrides please use
'meta_override'.
"""
meta_override = []
"""
List of meta options that can/will be overridden by config options
of the '[base]' config section (where [base] is the
of the '[base]' config section (where [base] is the
base configuration section of the application which is determined
by Meta.config_section but defaults to Meta.label).
"""
def __init__(self, label=None, **kw):
"""
def __init__(self, label=None, **kw):
super(CementApp, self).__init__(**kw)
# for convenience we translate this to _meta
if label:
self._meta.label = label
self._validate_label()
self._loaded_bootstrap = None
self._parsed_args = None
self.ext = None
self.config = None
self.log = None
@ -344,78 +348,79 @@ class CementApp(meta.MetaMixin):
self.output = None
self.controller = None
self.cache = None
# setup argv... this has to happen before lay_cement()
if self._meta.argv is None:
self._meta.argv = list(sys.argv[1:])
# hack for command line --debug
if '--debug' in self.argv:
self._meta.debug = True
# setup the cement framework
self._lay_cement()
@property
def argv(self):
"""The arguments list that will be used when self.run() is called."""
return self._meta.argv
def extend(self, member_name, member_object):
"""
Extend the CementApp() object with additional functions/classes such
as 'app.my_custom_function()', etc. It provides an interface for
extensions to provide functionality that travel along with the
extensions to provide functionality that travel along with the
application object.
:param member_name: The name to attach the object to.
:type member_name: str
:param member_object: The function or class object to attach to
:param member_object: The function or class object to attach to
CementApp().
:raises: cement.core.exc.FrameworkError
"""
if hasattr(self, member_name):
raise exc.FrameworkError("App member '%s' already exists!" % \
member_name)
LOG.debug("extending appication with '.%s' (%s)" % \
raise exc.FrameworkError("App member '%s' already exists!" %
member_name)
LOG.debug("extending appication with '.%s' (%s)" %
(member_name, member_object))
setattr(self, member_name, member_object)
def _validate_label(self):
if not self._meta.label:
raise exc.FrameworkError("Application name missing.")
# validate the name is ok
ok = ['_', '-']
for char in self._meta.label:
if char in ok:
continue
if not char.isalnum():
raise exc.FrameworkError(
"App label can only contain alpha-numeric, dashes, or underscores."
)
"App label can only contain alpha-numeric, dashes, " +
"or underscores."
)
def setup(self):
"""
This function wraps all '_setup' actons in one call. It is called
before self.run(), allowing the application to be _setup but not
executed (possibly letting the developer perform other actions
before full execution.).
All handlers should be instantiated and callable after setup is
complete.
"""
LOG.debug("now setting up the '%s' application" % self._meta.label)
if self._meta.bootstrap is not None:
LOG.debug("importing bootstrap code from %s" % \
LOG.debug("importing bootstrap code from %s" %
self._meta.bootstrap)
if self._meta.bootstrap not in sys.modules \
or self._loaded_bootstrap is None:
or self._loaded_bootstrap is None:
__import__(self._meta.bootstrap, globals(), locals(), [], 0)
if hasattr(sys.modules[self._meta.bootstrap], 'load'):
sys.modules[self._meta.bootstrap].load()
@ -423,10 +428,10 @@ class CementApp(meta.MetaMixin):
self._loaded_bootstrap = sys.modules[self._meta.bootstrap]
else:
reload(self._loaded_bootstrap)
for res in hook.run('pre_setup', self):
pass
self._setup_signals()
self._setup_extension_handler()
self._setup_config_handler()
@ -440,16 +445,16 @@ class CementApp(meta.MetaMixin):
for res in hook.run('post_setup', self):
pass
def run(self):
"""
This function wraps everything together (after self._setup() is
This function wraps everything together (after self._setup() is
called) to run the application.
"""
for res in hook.run('pre_run', self):
pass
# If controller exists, then pass controll to it
if self.controller:
self.controller._dispatch()
@ -461,72 +466,72 @@ class CementApp(meta.MetaMixin):
def close(self):
"""
Close the application. This runs the pre_close and post_close hooks
allowing plugins/extensions/etc to 'cleanup' at the end of program
Close the application. This runs the pre_close and post_close hooks
allowing plugins/extensions/etc to 'cleanup' at the end of program
execution.
"""
for res in hook.run('pre_close', self):
pass
LOG.debug("closing the application")
for res in hook.run('post_close', self):
pass
def render(self, data, template=None):
"""
This is a simple wrapper around self.output.render() which simply
returns an empty string if no self.output handler is defined.
:param data: The data dictionary to render.
:param template: The template to render to. Default: None (some
:param template: The template to render to. Default: None (some
output handlers do not use templates).
"""
for res in hook.run('pre_render', self, data):
if not type(res) is dict:
LOG.debug("pre_render hook did not return a dict().")
else:
data = res
if not self.output:
LOG.debug('render() called, but no output handler defined.')
out_text = ''
else:
out_text = self.output.render(data, template)
for res in hook.run('post_render', self, out_text):
if not type(res) is str:
LOG.debug('post_render hook did not return a str()')
else:
out_text = str(res)
return out_text
@property
def pargs(self):
"""
Returns the `parsed_args` object as returned by self.args.parse().
"""
return self._parsed_args
def add_arg(self, *args, **kw):
"""A shortcut for self.args.add_argument."""
"""A shortcut for self.args.add_argument."""
self.args.add_argument(*args, **kw)
def _lay_cement(self):
"""Initialize the framework."""
LOG.debug("laying cement for the '%s' application" % \
LOG.debug("laying cement for the '%s' application" %
self._meta.label)
### overrides via command line
suppress_output = False
if '--debug' in self._meta.argv:
self._meta.debug = True
else:
# the following are hacks to suppress console output
# the following are hacks to suppress console output
for flag in ['--quiet', '--json', '--yaml']:
if flag in self._meta.argv:
suppress_output = True
@ -538,7 +543,7 @@ class CementApp(meta.MetaMixin):
backend.SAVED_STDERR = sys.stderr
sys.stdout = NullOut()
sys.stderr = NullOut()
# start clean
backend.hooks = {}
backend.handlers = {}
@ -553,8 +558,8 @@ class CementApp(meta.MetaMixin):
hook.define('signal')
hook.define('pre_render')
hook.define('post_render')
# define and register handlers
# define and register handlers
handler.define(extension.IExtension)
handler.define(log.ILog)
handler.define(config.IConfig)
@ -563,44 +568,44 @@ class CementApp(meta.MetaMixin):
handler.define(arg.IArgument)
handler.define(controller.IController)
handler.define(cache.ICache)
# extension handler is the only thing that can't be loaded... as,
# extension handler is the only thing that can't be loaded... as,
# well, an extension. ;)
handler.register(extension.CementExtensionHandler)
def _parse_args(self):
self._parsed_args = self.args.parse(self.argv)
if self._meta.arguments_override_config is True:
for member in dir(self._parsed_args):
if member and member.startswith('_'):
continue
# don't override config values for options that weren't passed
# or in otherwords are None
elif getattr(self._parsed_args, member) is None:
continue
for section in self.config.get_sections():
if member in self.config.keys(section):
self.config.set(section, member,
self.config.set(section, member,
getattr(self._parsed_args, member))
def _setup_signals(self):
if not self._meta.catch_signals:
LOG.debug("catch_signals=None... not handling any signals")
return
for signum in self._meta.catch_signals:
LOG.debug("adding signal handler for signal %s" % signum)
signal.signal(signum, self._meta.signal_handler)
def _resolve_handler(self, handler_type, handler_def, raise_error=True):
"""
Resolves the actual handler as it can be either a string identifying
the handler to load from backend.handlers, or it can be an
the handler to load from backend.handlers, or it can be an
instantiated or non-instantiated handler class.
:param handler_type: The type of handler (aka the interface label)
:param hander_def: The handler as defined in CementApp.Meta.
:type handler_def: str, uninstantiated object, or instantiated object
@ -608,7 +613,7 @@ class CementApp(meta.MetaMixin):
to resolve the handler.
:type raise_error: boolean
:returns: The instantiated handler object.
"""
han = None
if type(handler_def) == str:
@ -621,7 +626,7 @@ class CementApp(meta.MetaMixin):
han = handler_def()
if not handler.registered(handler_type, han._meta.label):
handler.register(handler_def)
msg = "Unable to resolve handler '%s' of type '%s'" % \
(handler_def, handler_type)
if han is not None:
@ -631,117 +636,120 @@ class CementApp(meta.MetaMixin):
raise exc.FrameworkError(msg)
elif han is None:
LOG.debug(msg)
def _setup_extension_handler(self):
LOG.debug("setting up %s.extension handler" % self._meta.label)
self.ext = self._resolve_handler('extension',
LOG.debug("setting up %s.extension handler" % self._meta.label)
self.ext = self._resolve_handler('extension',
self._meta.extension_handler)
self.ext.load_extensions(self._meta.core_extensions)
self.ext.load_extensions(self._meta.extensions)
def _setup_config_handler(self):
LOG.debug("setting up %s.config handler" % self._meta.label)
self.config = self._resolve_handler('config',
self.config = self._resolve_handler('config',
self._meta.config_handler)
if self._meta.config_section is None:
self._meta.config_section = self._meta.label
self.config.add_section(self._meta.config_section)
if not self._meta.config_defaults is None:
self.config.merge(self._meta.config_defaults)
if self._meta.config_files is None:
label = self._meta.label
user_home = os.path.abspath(os.path.expanduser(os.environ['HOME']))
user_home = fs.abspath(os.environ['HOME'])
self._meta.config_files = [
os.path.join('/', 'etc', label, '%s.conf' % label),
os.path.join(user_home, '.%s.conf' % label),
os.path.join(user_home, '.%s' % label, 'config'),
]
]
for _file in self._meta.config_files:
self.config.parse_file(_file)
# 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:
key in self._meta.meta_override:
setattr(self._meta, key, base_dict[key])
def _setup_log_handler(self):
LOG.debug("setting up %s.log handler" % self._meta.label)
self.log = self._resolve_handler('log', self._meta.log_handler)
def _setup_plugin_handler(self):
LOG.debug("setting up %s.plugin handler" % self._meta.label)
LOG.debug("setting up %s.plugin handler" % self._meta.label)
# modify app defaults if not set
if not self._meta.plugin_config_dir:
self._meta.plugin_config_dir = '/etc/%s/plugins.d/' % self._meta.label
self._meta.plugin_config_dir = '/etc/%s/plugins.d/' % \
self._meta.label
if not self._meta.plugin_dir:
self._meta.plugin_dir = '/usr/lib/%s/plugins' % self._meta.label
self.plugin = self._resolve_handler('plugin',
self.plugin = self._resolve_handler('plugin',
self._meta.plugin_handler)
self.plugin.load_plugins(self._meta.plugins)
self.plugin.load_plugins(self.plugin.get_enabled_plugins())
def _setup_output_handler(self):
if self._meta.output_handler is None:
LOG.debug("no output handler defined, skipping.")
return
LOG.debug("setting up %s.output handler" % self._meta.label)
self.output = self._resolve_handler('output',
LOG.debug("setting up %s.output handler" % self._meta.label)
self.output = self._resolve_handler('output',
self._meta.output_handler,
raise_error=False)
def _setup_cache_handler(self):
if self._meta.cache_handler is None:
LOG.debug("no cache handler defined, skipping.")
return
LOG.debug("setting up %s.cache handler" % self._meta.label)
self.cache = self._resolve_handler('cache',
self._meta.cache_handler,
raise_error=False)
LOG.debug("setting up %s.cache handler" % self._meta.label)
self.cache = self._resolve_handler('cache',
self._meta.cache_handler,
raise_error=False)
def _setup_arg_handler(self):
LOG.debug("setting up %s.arg handler" % self._meta.label)
self.args = self._resolve_handler('argument',
LOG.debug("setting up %s.arg handler" % self._meta.label)
self.args = self._resolve_handler('argument',
self._meta.argument_handler)
self.args.add_argument('--debug', dest='debug',
action='store_true', help='toggle debug output')
self.args.add_argument('--quiet', dest='suppress_output',
action='store_true', help='suppress all output')
self.args.add_argument('--debug', dest='debug',
action='store_true',
help='toggle debug output')
self.args.add_argument('--quiet', dest='suppress_output',
action='store_true',
help='suppress all output')
def _setup_controllers(self):
LOG.debug("setting up application controllers")
LOG.debug("setting up application controllers")
if self._meta.base_controller is not None:
self.controller = self._resolve_handler('controller',
self._meta.base_controller)
cntr = self._resolve_handler('controller',
self._meta.base_controller)
self.controller = cntr
self._meta.base_controller = self.controller
elif self._meta.base_controller is None:
if handler.registered('controller', 'base'):
self.controller = self._resolve_handler('controller', 'base')
self.controller = self._resolve_handler('controller', 'base')
self._meta.base_controller = self.controller
# This is necessary for some backend usage
if self._meta.base_controller is not None:
if self._meta.base_controller._meta.label != 'base':
raise exc.FrameworkError("Base controllers must have " +
"a label of 'base'.")
raise exc.FrameworkError("Base controllers must have " +
"a label of 'base'.")
# Trump all with whats passed at the command line, and pop off the arg
if len(self.argv) > 0:
controller = None
# translate dashes to underscore
label = re.sub('-', '_', self.argv[0])
h = handler.get('controller', label, None)
if h:
controller = h()
@ -752,7 +760,7 @@ class CementApp(meta.MetaMixin):
if label in contr._meta.aliases:
controller = contr
break
if controller:
self.controller = controller
self.controller._setup(self)
@ -761,28 +769,28 @@ class CementApp(meta.MetaMixin):
# if no handler can be found, that's ok
if not self.controller:
LOG.debug("no controller could be found.")
def validate_config(self):
"""
Validate application config settings.
Usage:
.. code-block:: python
import os
from cement.core import foundation
class MyApp(foundation.CementApp):
class Meta:
label = 'myapp'
def validate_config(self):
# test that the log file directory exist, if not create it
logdir = os.path.dirname(self.config.get('log', 'file'))
if not os.path.exists(logdir):
os.makedirs(logdir)
"""
pass

View File

@ -8,246 +8,251 @@ from ..core import exc, backend, meta
LOG = backend.minimal_logger(__name__)
class CementBaseHandler(meta.MetaMixin):
"""Base handler class that all Cement Handlers should subclass from."""
class Meta:
"""
Handler meta-data (can also be passed as keyword arguments to the
parent class).
"""
label = None
"""The string identifier of this handler."""
interface = None
"""The interface that this class implements."""
config_section = None
"""
A config [section] to merge config_defaults with.
A config [section] to merge config_defaults with.
Note: Though Meta.config_section defaults to None, Cement will
set this to the value of ``<interface_label>.<handler_label>`` if
no section is set by the user/develop.
"""
config_defaults = None
"""
A config dictionary that is merged into the applications config
in the [<config_section>] block. These are defaults and do not
override any existing defaults under that section.
"""
def __init__(self, **kw):
super(CementBaseHandler, self).__init__(**kw)
self.app = None
def _setup(self, app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:returns: None
:param app_obj: The application object.
:returns: None
"""
self.app = app_obj
if self._meta.config_section is None:
self._meta.config_section = "%s.%s" % \
(self._meta.interface.IMeta.label, self._meta.label)
if self._meta.config_defaults is not None:
LOG.debug("merging config defaults from '%s'" % self)
dict_obj = dict()
dict_obj[self._meta.config_section] = self._meta.config_defaults
self.app.config.merge(dict_obj, override=False)
def get(handler_type, handler_label, *args):
"""
Get a handler object.
Required Arguments:
:param handler_type: The type of handler (i.e. 'output')
:type handler_type: str
:param handler_label: The label of the handler (i.e. 'json')
:type handler_label: str
:param fallback: A fallback value to return if handler_label doesn't
:param fallback: A fallback value to return if handler_label doesn't
exist.
:returns: An uninstantiated handler object
:raises: cement.core.exc.FrameworkError
Usage:
from cement.core import handler
output = handler.get('output', 'json')
output.render(dict(foo='bar'))
"""
if handler_type not in backend.handlers:
raise exc.FrameworkError("handler type '%s' does not exist!" % \
handler_type)
raise exc.FrameworkError("handler type '%s' does not exist!" %
handler_type)
if handler_label in backend.handlers[handler_type]:
return backend.handlers[handler_type][handler_label]
elif len(args) > 0:
return args[0]
else:
raise exc.FrameworkError("handlers['%s']['%s'] does not exist!" % \
(handler_type, handler_label))
raise exc.FrameworkError("handlers['%s']['%s'] does not exist!" %
(handler_type, handler_label))
def list(handler_type):
"""
Return a list of handlers for a given type.
:param handler_type: The type of handler (i.e. 'output')
:returns: List of handlers that match `type`.
:rtype: list
:raises: cement.core.exc.FrameworkError
"""
if handler_type not in backend.handlers:
raise exc.FrameworkError("handler type '%s' does not exist!" % \
handler_type)
raise exc.FrameworkError("handler type '%s' does not exist!" %
handler_type)
res = []
for label in backend.handlers[handler_type]:
if label == '__interface__':
continue
res.append(backend.handlers[handler_type][label])
return res
def define(interface):
"""
Define a handler based on the provided interface. Defines a handler type
based on <interface>.IMeta.label.
:param interface: The interface class that defines the interface to be
:param interface: The interface class that defines the interface to be
implemented by handlers.
:raises: cement.core.exc.InterfaceError
:raises: cement.core.exc.FrameworkError
Usage:
.. code-block:: python
from cement.core import handler
handler.define(IDatabaseHandler)
"""
if not hasattr(interface, 'IMeta'):
raise exc.InterfaceError("Invalid %s, " % interface + \
"missing 'IMeta' class.")
raise exc.InterfaceError("Invalid %s, " % interface +
"missing 'IMeta' class.")
if not hasattr(interface.IMeta, 'label'):
raise exc.InterfaceError("Invalid %s, " % interface + \
"missing 'IMeta.label' class.")
LOG.debug("defining handler type '%s' (%s)" % \
(interface.IMeta.label, interface.__name__))
raise exc.InterfaceError("Invalid %s, " % interface +
"missing 'IMeta.label' class.")
LOG.debug("defining handler type '%s' (%s)" %
(interface.IMeta.label, interface.__name__))
if interface.IMeta.label in backend.handlers:
raise exc.FrameworkError("Handler type '%s' already defined!" % \
interface.IMeta.label)
backend.handlers[interface.IMeta.label] = {'__interface__' : interface}
raise exc.FrameworkError("Handler type '%s' already defined!" %
interface.IMeta.label)
backend.handlers[interface.IMeta.label] = {'__interface__': interface}
def defined(handler_type):
"""
Test whether a handler type is defined.
:param handler_type: The name or 'type' of the handler (I.e. 'logging').
:returns: True if the handler type is defined, False otherwise.
:rtype: boolean
"""
if handler_type in backend.handlers:
return True
else:
return False
def register(handler_obj):
"""
Register a handler object to a handler. If the same object is already
registered then no exception is raised, however if a different object
attempts to be registered to the same name a FrameworkError is
attempts to be registered to the same name a FrameworkError is
raised.
:param handler_obj: The uninstantiated handler object to register.
:raises: cement.core.exc.InterfaceError
:raises: cement.core.exc.FrameworkError
Usage:
.. code-block:: python
from cement.core import handler
class MyDatabaseHandler(object):
class Meta:
interface = IDatabase
label = 'mysql'
def connect(self):
...
handler.register(MyDatabaseHandler)
"""
orig_obj = handler_obj
# for checks
obj = orig_obj()
if not hasattr(obj._meta, 'label') or not obj._meta.label:
raise exc.InterfaceError("Invalid handler %s, " % orig_obj + \
"missing '_meta.label'.")
raise exc.InterfaceError("Invalid handler %s, " % orig_obj +
"missing '_meta.label'.")
if not hasattr(obj._meta, 'interface') or not obj._meta.interface:
raise exc.InterfaceError("Invalid handler %s, " % orig_obj + \
"missing '_meta.interface'.")
raise exc.InterfaceError("Invalid handler %s, " % orig_obj +
"missing '_meta.interface'.")
# translate dashes to underscores
orig_obj.Meta.label = re.sub('-', '_', obj._meta.label)
obj._meta.label = re.sub('-', '_', obj._meta.label)
handler_type = obj._meta.interface.IMeta.label
LOG.debug("registering handler '%s' into handlers['%s']['%s']" % \
LOG.debug("registering handler '%s' into handlers['%s']['%s']" %
(orig_obj, handler_type, obj._meta.label))
if handler_type not in backend.handlers:
raise exc.FrameworkError("Handler type '%s' doesn't exist." % \
handler_type)
raise exc.FrameworkError("Handler type '%s' doesn't exist." %
handler_type)
if obj._meta.label in backend.handlers[handler_type] and \
backend.handlers[handler_type][obj._meta.label] != obj:
raise exc.FrameworkError("handlers['%s']['%s'] already exists" % \
backend.handlers[handler_type][obj._meta.label] != obj:
raise exc.FrameworkError("handlers['%s']['%s'] already exists" %
(handler_type, obj._meta.label))
interface = backend.handlers[handler_type]['__interface__']
if hasattr(interface.IMeta, 'validator'):
interface.IMeta().validator(obj)
else:
LOG.debug("Interface '%s' does not have a validator() function!" % \
interface)
LOG.debug("Interface '%s' does not have a validator() function!" %
interface)
backend.handlers[handler_type][obj.Meta.label] = orig_obj
def registered(handler_type, handler_label):
"""
Check if a handler is registered.
:param handler_type: The type of handler (interface label)
:param handler_label: The label of the handler
:returns: True if the handler is registered, False otherwise
:rtype: boolean
"""
if handler_type in backend.handlers and \
handler_label in backend.handlers[handler_type]:
handler_label in backend.handlers[handler_type]:
return True
return False

View File

@ -5,95 +5,99 @@ from ..core import backend, exc
LOG = backend.minimal_logger(__name__)
def define(name):
"""
Define a hook namespace that plugins can register hooks in.
:param name: The name of the hook, stored as hooks['name']
:raises: cement.core.exc.FrameworkError
Usage:
.. code-block:: python
from cement.core import hook
hook.define('myhookname_hook')
"""
LOG.debug("defining hook '%s'", name)
if name in backend.hooks:
raise exc.FrameworkError("Hook name '%s' already defined!" % name)
backend.hooks[name] = []
def defined(hook_name):
"""
Test whether a hook name is defined.
:param hook_name: The name of the hook.
:param hook_name: The name of the hook.
I.e. ``my_hook_does_awesome_things``.
:returns: True if the hook is defined, False otherwise.
:rtype: boolean
"""
if hook_name in backend.hooks:
return True
else:
return False
return False
def register(name, func, weight=0):
"""
Register a function to a hook. The function will be called, in order of
weight, when the hook is run.
:param name: The name of the hook to register too. I.e. ``pre_setup``,
:param name: The name of the hook to register too. I.e. ``pre_setup``,
``post_run``, etc.
:param func: The function to register to the hook. This is an
:param func: The function to register to the hook. This is an
*un-instantiated*, non-instance method, simple function.
:param weight: The weight in which to order the hook function.
:param weight: The weight in which to order the hook function.
:type weight: integer
Usage:
.. code-block:: python
from cement.core import hook
def my_hook(*args, **kwargs):
# do something here
res = 'Something to return'
return res
hook.register('post_setup', my_hook)
"""
if name not in backend.hooks:
LOG.debug("hook name '%s' is not defined! ignoring..." % name)
return False
LOG.debug("registering hook '%s' from %s into hooks['%s']" % \
(func.__name__, func.__module__, name))
LOG.debug("registering hook '%s' from %s into hooks['%s']" %
(func.__name__, func.__module__, name))
# Hooks are as follows: (weight, name, func)
backend.hooks[name].append((int(weight), func.__name__, func))
def run(name, *args, **kwargs):
"""
Run all defined hooks in the namespace. Yields the result of each hook
function run.
:param name: The name of the hook function.
:param args: Additional arguments to be passed to the hook functions.
:param kwargs: Additional keyword arguments to be passed to the hook
:param kwargs: Additional keyword arguments to be passed to the hook
functions.
:raises: FrameworkError
Usage:
.. code-block:: python
from cement.core import hook
for result in hook.run('hook_name'):
# do something with result from each hook function
...
@ -102,12 +106,12 @@ def run(name, *args, **kwargs):
raise exc.FrameworkError("Hook name '%s' is not defined!" % name)
# Will order based on weight (the first item in the tuple)
backend.hooks[name].sort(key=operator.itemgetter(0))
backend.hooks[name].sort(key=operator.itemgetter(0))
for hook in backend.hooks[name]:
LOG.debug("running hook '%s' (%s) from %s" % \
LOG.debug("running hook '%s' (%s) from %s" %
(name, hook[2], hook[2].__module__))
res = hook[2](*args, **kwargs)
# Results are yielded, so you must fun a for loop on it, you can not
# simply call run_hooks().
yield res
# simply call run_hooks().
yield res

View File

@ -7,6 +7,7 @@ from ..core import exc
DEFAULT_META = ['interface', 'label', 'config_defaults', 'config_section']
class Interface(object):
"""
An interface definition class. All Interfaces should subclass from
@ -15,48 +16,50 @@ class Interface(object):
"""
def __init__(self):
raise exc.InterfaceError("Interfaces can not be used directly.")
class Attribute(object):
"""
An interface attribute definition.
:param description: The description of the attribute.
"""
def __init__(self, description):
self.description = description
def __repr__(self):
return "<interface.Attribute - '%s'>" % self.description
def validate(interface, obj, members=[], meta=DEFAULT_META):
"""
A wrapper to validate interfaces.
:param interface: The interface class to validate against
:param obj: The object to validate.
:param members: The object members that must exist.
:param meta: The meta object members that must exist.
:raises: cement.core.exc.InterfaceError
"""
invalid = []
if hasattr(obj, '_meta') and interface != obj._meta.interface:
raise exc.InterfaceError("%s does not implement %s." % \
(obj, interface))
raise exc.InterfaceError("%s does not implement %s." %
(obj, interface))
for member in members:
if not hasattr(obj, member):
invalid.append(member)
if not hasattr(obj, '_meta'):
invalid.append("_meta")
else:
for member in meta:
if not hasattr(obj._meta, member):
invalid.append("_meta.%s" % member)
if invalid:
raise exc.InterfaceError("Invalid or missing: %s in %s" % \
(invalid, obj))
raise exc.InterfaceError("Invalid or missing: %s in %s" %
(invalid, obj))

View File

@ -4,136 +4,139 @@ Cement core log module.
"""
from ..core import exc, backend, interface, handler
def log_validator(klass, obj):
"""Validates an handler implementation against the ILog interface."""
members = [
'_setup',
'clear_loggers',
'set_level',
'get_level',
'info',
'info',
'warn',
'error',
'fatal',
'debug',
]
interface.validate(ILog, obj, members)
'error',
'fatal',
'debug',
]
interface.validate(ILog, obj, members)
class ILog(interface.Interface):
"""
This class defines the Log Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Log Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import log
class MyLogHandler(object):
class Meta:
interface = log.ILog
label = 'my_log_handler'
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'log'
"""The string identifier of the interface."""
validator = log_validator
"""The interface validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler Meta-data')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:param app_obj: The application object.
"""
def clear_loggers():
"""Clear all existing loggers."""
def set_level():
"""
Set the log level. Must except atleast one of:
Set the log level. Must except atleast one of:
``['INFO', 'WARN', 'ERROR', 'DEBUG', or 'FATAL']``.
"""
def get_level():
"""Return a string representation of the log level."""
def info(msg):
"""
Log to the 'INFO' facility.
:param msg: The message to log.
"""
def warn(self, msg):
"""
Log to the 'WARN' facility.
:param msg: The message to log.
"""
def error(self, msg):
"""
Log to the 'ERROR' facility.
:param msg: The message to log.
"""
def fatal(self, msg):
"""
Log to the 'FATAL' facility.
:param msg: The message to log.
"""
def debug(self, msg):
"""
Log to the 'DEBUG' facility.
:param msg: The message to log.
"""
class CementLogHandler(handler.CementBaseHandler):
"""
Base class that all Log Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
label = None
"""The string identifier of this handler."""
interface = ILog
"""The interface that this class implements."""
def __init__(self, *args, **kw):
super(CementLogHandler, self).__init__(*args, **kw)

View File

@ -1,5 +1,6 @@
"""Cement core meta functionality."""
class Meta(object):
"""
Model that acts as a container class for a meta attributes for a larger
@ -13,7 +14,8 @@ class Meta(object):
def _merge(self, dict_obj):
for key in dict_obj.keys():
setattr(self, key, dict_obj[key])
class MetaMixin(object):
"""
Mixin that provides the Meta class support to add settings to instances
@ -24,14 +26,14 @@ class MetaMixin(object):
def __init__(self, *args, **kwargs):
# Get a List of all the Classes we in our MRO, find any attribute named
# Meta on them, and then merge them together in order of MRO
metas = reversed([x.Meta for x in self.__class__.mro() \
if hasattr(x, "Meta")])
metas = reversed([x.Meta for x in self.__class__.mro()
if hasattr(x, "Meta")])
final_meta = {}
# Merge the Meta classes into one dict
for meta in metas:
final_meta.update(dict([x for x in meta.__dict__.items() \
if not x[0].startswith("_")]))
final_meta.update(dict([x for x in meta.__dict__.items()
if not x[0].startswith("_")]))
# Update the final Meta with any kwargs passed in
for key in final_meta.keys():
@ -41,4 +43,4 @@ class MetaMixin(object):
self._meta = Meta(**final_meta)
# FIX ME: object.__init__() doesn't take params without exception
super(MetaMixin, self).__init__()
super(MetaMixin, self).__init__()

View File

@ -4,88 +4,90 @@ from ..core import backend, exc, interface, handler
LOG = backend.minimal_logger(__name__)
def output_validator(klass, obj):
"""Validates an handler implementation against the IOutput interface."""
members = [
'_setup',
'render',
]
interface.validate(IOutput, obj, members)
]
interface.validate(IOutput, obj, members)
class IOutput(interface.Interface):
"""
This class defines the Output Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Output Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import output
class MyOutputHandler(object):
class Meta:
interface = output.IOutput
label = 'my_output_handler'
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'output'
"""The string identifier of the interface."""
validator = output_validator
"""The interface validator function."""
# Must be provided by the implementation
Meta = interface.Attribute('Handler meta-data')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:param app_obj: The application object.
"""
def render(data_dict, template=None):
"""
Render the data_dict into output in some fashion.
:param data_dict: The dictionary whose data we need to render into
:param data_dict: The dictionary whose data we need to render into
output.
:param template: A template to use for rendering (in module form).
:param template: A template to use for rendering (in module form).
I.e ``myapp.templates.some_command``.
:returns: string or unicode string or None
"""
"""
class CementOutputHandler(handler.CementBaseHandler):
"""
Base class that all Output Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
label = None
"""The string identifier of this handler."""
interface = IOutput
"""The interface that this class implements."""
def __init__(self, *args, **kw):
super(CementOutputHandler, self).__init__(*args, **kw)

View File

@ -4,9 +4,10 @@ from ..core import backend, exc, interface, handler
LOG = backend.minimal_logger(__name__)
def plugin_validator(klass, obj):
"""Validates an handler implementation against the IPlugin interface."""
members = [
'_setup',
'load_plugin',
@ -14,90 +15,92 @@ def plugin_validator(klass, obj):
'get_loaded_plugins',
'get_enabled_plugins',
'get_disabled_plugins',
]
]
interface.validate(IPlugin, obj, members)
class IPlugin(interface.Interface):
"""
This class defines the Plugin Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
This class defines the Plugin Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
below.
Implementations do *not* subclass from interfaces.
Usage:
.. code-block:: python
from cement.core import plugin
class MyPluginHandler(object):
class Meta:
interface = plugin.IPlugin
label = 'my_plugin_handler'
...
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
label = 'plugin'
validator = plugin_validator
# Must be provided by the implementation
Meta = interface.Attribute('Handler meta-data')
def _setup(app_obj):
"""
The _setup function is called during application initialization and
must 'setup' the handler object making it ready for the framework
or the application to make further calls to it.
:param app_obj: The application object.
:param app_obj: The application object.
"""
def load_plugin(plugin_name):
"""
Load a plugin whose name is 'plugin_name'.
:param plugin_name: The name of the plugin to load.
"""
def load_plugins(plugin_list):
"""
Load all plugins from plugin_list.
:param plugin_list: A list of plugin names to load.
"""
def get_loaded_plugins():
"""Returns a list of plugins that have been loaded."""
def get_enabled_plugins():
"""Returns a list of plugins that are enabled in the config."""
def get_disabled_plugins():
"""Returns a list of plugins that are disabled in the config."""
class CementPluginHandler(handler.CementBaseHandler):
"""
Base class that all Plugin Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
Handler meta-data (can be passed as keyword arguments to the parent
class).
"""
label = None
"""The string identifier of this handler."""
interface = IPlugin
"""The interface that this class implements."""
def __init__(self, *args, **kw):
super(CementPluginHandler, self).__init__(*args, **kw)

View File

@ -1 +1 @@
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover

View File

@ -4,50 +4,55 @@ from argparse import ArgumentParser
from ..core import backend, arg, handler
LOG = backend.minimal_logger(__name__)
class ArgParseArgumentHandler(arg.CementArgumentHandler, ArgumentParser):
"""
This class implements the :ref:`IArgument <cement.core.arg>`
interface, and sub-classes from `argparse.ArgumentParser <http://docs.python.org/dev/library/argparse.html>`_.
This class implements the :ref:`IArgument <cement.core.arg>`
interface, and sub-classes from `argparse.ArgumentParser
<http://docs.python.org/dev/library/argparse.html>`_.
Please reference the argparse documentation for full usage of the
class.
Arguments and Keyword arguments are passed directly to ArgumentParser
on initialization.
"""
class Meta:
"""Handler meta-data."""
interface = arg.IArgument
"""The interface that this class implements."""
label = 'argparse'
"""The string identifier of the handler."""
def __init__(self, *args, **kw):
super(ArgParseArgumentHandler, self).__init__(*args, **kw)
self.config = None
def parse(self, arg_list):
"""
Parse a list of arguments, and return them as an object. Meaning an
Parse a list of arguments, and return them as an object. Meaning an
argument name of 'foo' will be stored as parsed_args.foo.
:param arg_list: A list of arguments (generally sys.argv) to be parsed.
:param arg_list: A list of arguments (generally sys.argv) to be
parsed.
:returns: object whose members are the arguments parsed.
"""
return self.parse_args(arg_list)
def add_argument(self, *args, **kw):
"""
Add an argument to the parser. Arguments and keyword arguments are
passed directly to ArgumentParser.add_argument().
"""
return super(ArgumentParser, self).add_argument(*args, **kw)
return super(ArgumentParser, self).add_argument(*args, **kw)
def load():
"""Called by the framework when the extension is 'loaded'."""
handler.register(ArgParseArgumentHandler)
handler.register(ArgParseArgumentHandler)

View File

@ -3,131 +3,138 @@
import os
import sys
if sys.version_info[0] < 3:
from ConfigParser import RawConfigParser # pragma: no cover
from ConfigParser import RawConfigParser # pragma: no cover
else:
from configparser import RawConfigParser # pragma: no cover
from configparser import RawConfigParser # pragma: no cover
from ..core import backend, config, handler
LOG = backend.minimal_logger(__name__)
class ConfigParserConfigHandler(config.CementConfigHandler, RawConfigParser):
"""
This class is an implementation of the :ref:`IConfig <cement.core.config>`
interface. It handles configuration file parsing and the like by
sub-classing from the standard `ConfigParser <http://docs.python.org/library/configparser.html>`_
This class is an implementation of the :ref:`IConfig <cement.core.config>`
interface. It handles configuration file parsing and the like by
sub-classing from the standard `ConfigParser
<http://docs.python.org/library/configparser.html>`_
library. Please see the ConfigParser documentation for full usage of the
class.
Additional arguments and keyword arguments are passed directly to
Additional arguments and keyword arguments are passed directly to
RawConfigParser on initialization.
"""
class Meta:
"""Handler meta-data."""
interface = config.IConfig
"""The interface that this handler implements."""
label = 'configparser'
"""The string identifier of this handler."""
def __init__(self, *args, **kw):
# ConfigParser is not a new style object, so you can't call super()
# super(ConfigParserConfigHandler, self).__init__(*args, **kw)
RawConfigParser.__init__(self, *args, **kw)
super(ConfigParserConfigHandler, self).__init__(*args, **kw)
self.app = None
def merge(self, dict_obj, override=True):
"""
Merge a dictionary into our config. If override is True then
Merge a dictionary into our config. If override is True then
existing config values are overridden by those passed in.
:param dict_obj: A dictionary of configuration keys/values to merge
:param dict_obj: A dictionary of configuration keys/values to merge
into our existing config (self).
:param override: Whether or not to override existing values in the
:param override: Whether or not to override existing values in the
config.
"""
for section in list(dict_obj.keys()):
if type(dict_obj[section]) == dict:
if not section in self.get_sections():
self.add_section(section)
for key in list(dict_obj[section].keys()):
if override:
self.set(section, key, dict_obj[section][key])
else:
# only set it if the key doesn't exist
if not self.has_key(section, key):
if not key in self.keys(section):
self.set(section, key, dict_obj[section][key])
# we don't support nested config blocks, so no need to go
# we don't support nested config blocks, so no need to go
# further down to more nested dicts.
def parse_file(self, file_path):
"""
Parse config file settings from file_path, overwriting existing
Parse config file settings from file_path, overwriting existing
config settings. If the file does not exist, returns False.
:param file_path: The file system path to the configuration file.
:returns: boolean
"""
file_path = os.path.abspath(os.path.expanduser(file_path))
if os.path.exists(file_path):
self.read(file_path)
return True
else:
LOG.debug("config file '%s' does not exist, skipping..." % \
LOG.debug("config file '%s' does not exist, skipping..." %
file_path)
return False
def keys(self, section):
"""
Return a list of keys within 'section'.
:param section: The config section (I.e. [block_section]).
:returns: List of keys in the `section`.
:rtype: list
"""
return self.options(section)
def has_key(self, section, key):
"""
Return whether or not a 'section' has the given 'key'.
:param section: The section of the configuration. I.e. [block_section].
:param section: The section of the configuration.
I.e. [block_section].
:param key: The key within 'section'.
:returns: True if the config `section` has `key`.
:rtype: boolean
*This function is deprecated as of Cement 2.1.1, and will be removed
in future versions. Use `if 'key' in config.keys('section')`
instead.*
"""
if key in self.options(section):
return True
else:
return False
def get_sections(self):
"""
Return a list of configuration sections or [blocks].
:returns: List of sections.
:rtype: list
"""
return self.sections()
def get_section_dict(self, section):
"""
Return a dict representation of a section.
:param section: The section of the configuration. I.e. [block_section]
:param section: The section of the configuration.
I.e. [block_section]
:returns: Dictionary reprisentation of the config section.
:rtype: dict
"""
dict_obj = dict()
for key in self.keys(section):
@ -137,12 +144,14 @@ class ConfigParserConfigHandler(config.CementConfigHandler, RawConfigParser):
def add_section(self, section):
"""
Adds a block section to the config.
:param section: The section to add.
"""
super(ConfigParserConfigHandler, self).add_section(section)
def load():
"""Called by the framework when the extension is 'loaded'."""
handler.register(ConfigParserConfigHandler)

View File

@ -6,72 +6,78 @@ from ..core import output, backend, hook, handler
LOG = backend.minimal_logger(__name__)
class JsonOutputHandler(output.CementOutputHandler):
"""
This class implements the :ref:`IOutput <cement.core.output>`
This class implements the :ref:`IOutput <cement.core.output>`
interface. It provides JSON output from a data dictionary using the
`json <http://docs.python.org/library/json.html>`_ module of the standard
library.
Note: The cement framework detects the '--json' option and suppresses
output (same as if passing --quiet). Therefore, if debugging or
output (same as if passing --quiet). Therefore, if debugging or
troubleshooting issues you must pass the --debug option to see whats
going on.
"""
class Meta:
"""Handler meta-data"""
interface = output.IOutput
"""The interface this class implements."""
label = 'json'
"""The string identifier of this handler."""
def __init__(self, *args, **kw):
super(JsonOutputHandler, self).__init__(*args, **kw)
def render(self, data_dict, template=None):
"""
Take a data dictionary and render it as Json output. Note that the
template option is received here per the interface, however this
template option is received here per the interface, however this
handler just ignores it.
:param data_dict: The data dictionary to render.
:param template: This option is completely ignored.
:returns: A JSON encoded string.
:rtype: str
"""
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)
def add_json_option(app):
"""
Adds the '--json' argument to the argument object.
:param app: The application object.
"""
app.args.add_argument('--json', dest='output_handler',
action='store_const', help='toggle json output handler', const='json')
app.args.add_argument('--json', dest='output_handler',
action='store_const',
help='toggle json output handler',
const='json')
def set_output_handler(app):
"""
Overrides the configured output handler if ``--json`` is passed at the
command line.
:param app: The application object.
"""
if '--json' in app._meta.argv:
app._meta.output_handler = 'json'
app._setup_output_handler()
def load():
"""Called by the framework when the extension is 'loaded'."""
hook.register('post_setup', add_json_option)
hook.register('pre_run', set_output_handler)
handler.register(JsonOutputHandler)
handler.register(JsonOutputHandler)

View File

@ -12,39 +12,42 @@ except AttributeError as e: # pragma: no cover
# Not supported on Python < 3.1/2.7 # pragma: no cover
class NullHandler(logging.Handler): # pragma: no cover
def handle(self, record): # pragma: no cover
pass # pragma: no cover
pass # pragma: no cover
# pragma: no cover
def emit(self, record): # pragma: no cover
pass # pragma: no cover
# pragma: no cover
def createLock(self): # pragma: no cover
def createLock(self): # pragma: no cover
self.lock = None # pragma: no cover
class LoggingLogHandler(log.CementLogHandler):
class LoggingLogHandler(log.CementLogHandler):
"""
This class is an implementation of the :ref:`ILog <cement.core.log>`
This class is an implementation of the :ref:`ILog <cement.core.log>`
interface, and sets up the logging facility using the standard Python
`logging <http://docs.python.org/library/logging.html>`_ module.
`logging <http://docs.python.org/library/logging.html>`_ module.
Configuration Options
The following configuration options are recognized in this class:
log.level
log.file
log.to_console
log.rotate
log.max_bytes
log.max_files
A sample config section (in any config file) might look like:
.. code-block:: text
[log]
@ -54,46 +57,48 @@ class LoggingLogHandler(log.CementLogHandler):
rotate = true
max_bytes = 512000
max_files = 4
"""
class Meta:
"""Handler meta-data."""
interface = log.ILog
"""The interface that this class implements."""
label = 'logging'
"""The string identifier of this handler."""
namespace = None
"""
The logging namespace.
Note: Although Meta.namespace defaults to None, Cement will set this
The logging namespace.
Note: Although Meta.namespace defaults to None, Cement will set this
to the application label (CementApp.Meta.label) if not set during
setup.
"""
file_format = "%(asctime)s (%(levelname)s) %(namespace)s : %(message)s"
file_format = "%(asctime)s (%(levelname)s) %(namespace)s : " + \
"%(message)s"
"""The logging format for the file logger."""
console_format = "%(levelname)s: %(message)s"
"""The logging format for the consoler logger."""
debug_format = "%(asctime)s (%(levelname)s) %(namespace)s : %(message)s"
debug_format = "%(asctime)s (%(levelname)s) %(namespace)s : " + \
"%(message)s"
"""The logging format for both file and console if ``debug==True``."""
clear_loggers = True
"""Whether of not to clear previous loggers first."""
# These are the default config values, overridden by any '[log]'
# These are the default config values, overridden by any '[log]'
# section in parsed config files.
config_section = 'log'
"""
The section of the application configuration that holds this handlers
configuration.
"""
config_defaults = dict(
file=None,
level='INFO',
@ -101,208 +106,208 @@ class LoggingLogHandler(log.CementLogHandler):
rotate=False,
max_bytes=512000,
max_files=4,
)
)
"""
The default configuration dictionary to populate the ``log`` section.
"""
levels = ['INFO', 'WARN', 'ERROR', 'DEBUG', 'FATAL']
def __init__(self, *args, **kw):
super(LoggingLogHandler, self).__init__(*args, **kw)
self.app = None
def _setup(self, app_obj):
super(LoggingLogHandler, self)._setup(app_obj)
if self._meta.namespace is None:
self._meta.namespace = self.app._meta.label
self.backend = logging.getLogger(self._meta.namespace)
# hack for application debugging
if is_true(self.app._meta.debug):
self.app.config.set('log', 'level', 'DEBUG')
self.set_level(self.app.config.get('log', 'level'))
# clear loggers?
if is_true(self._meta.clear_loggers):
self.clear_loggers()
# console
self._setup_console_log()
# file
self._setup_file_log()
self.debug("logging initialized for '%s' using LoggingLogHandler" % \
self.debug("logging initialized for '%s' using LoggingLogHandler" %
self._meta.namespace)
def set_level(self, level):
"""
Set the log level. Must be one of the log levels configured in
Set the log level. Must be one of the log levels configured in
self.levels which are ``['INFO', 'WARN', 'ERROR', 'DEBUG', 'FATAL']``.
:param level: The log level to set.
"""
level = level.upper()
if level not in self.levels:
level = 'INFO'
level = getattr(logging, level.upper())
self.backend.setLevel(level)
self.backend.setLevel(level)
for handler in logging.getLogger(self._meta.namespace).handlers:
handler.setLevel(level)
def get_level(self):
"""Returns the current log level."""
return logging.getLevelName(self.backend.level)
def clear_loggers(self):
"""Clear any previously configured logging namespaces."""
if not self._meta.namespace:
# _setup() probably wasn't run
return
for i in logging.getLogger(self._meta.namespace).handlers:
logging.getLogger(self._meta.namespace).removeHandler(i)
self.backend = logging.getLogger(self._meta.namespace)
def _setup_console_log(self):
"""Add a console log handler."""
if is_true(self.app.config.get('log', 'to_console')):
console_handler = logging.StreamHandler()
if self.get_level() == logging.getLevelName(logging.DEBUG):
format = logging.Formatter(self._meta.debug_format)
else:
format = logging.Formatter(self._meta.console_format)
console_handler.setFormatter(format)
console_handler.setLevel(getattr(logging, self.get_level()))
console_handler.setFormatter(format)
console_handler.setLevel(getattr(logging, self.get_level()))
else:
console_handler = NullHandler()
self.backend.addHandler(console_handler)
def _setup_file_log(self):
"""Add a file log handler."""
if self.app.config.get('log', 'file'):
file_path = fs.abspath(self.app.config.get('log', 'file'))
log_dir = os.path.dirname(file_path)
if not os.path.exists(log_dir):
os.makedirs(log_dir)
if self.app.config.get('log', 'rotate'):
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(
file_path,
maxBytes=int(self.app.config.get('log', 'max_bytes')),
file_path,
maxBytes=int(self.app.config.get('log', 'max_bytes')),
backupCount=int(self.app.config.get('log', 'max_files')),
)
)
else:
from logging import FileHandler
file_handler = FileHandler(file_path)
if self.get_level() == logging.getLevelName(logging.DEBUG):
format = logging.Formatter(self._meta.debug_format)
else:
format = logging.Formatter(self._meta.file_format)
file_handler.setFormatter(format)
file_handler.setLevel(getattr(logging, self.get_level()))
file_handler.setFormatter(format)
file_handler.setLevel(getattr(logging, self.get_level()))
else:
file_handler = NullHandler()
self.backend.addHandler(file_handler)
def _get_logging_kwargs(self, namespace, **kw):
if namespace is None:
namespace = self._meta.namespace
if 'extra' in kw.keys() and 'namespace' in kw['extra'].keys():
pass
elif 'extra' in kw.keys() and 'namespace' not in kw['extra'].keys():
kw['extra']['namespace'] = namespace
else:
kw['extra'] = dict(namespace=namespace)
return kw
def info(self, msg, namespace=None, **kw):
"""
Log to the INFO facility.
:param msg: The message the log.
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
None is passed.
:keyword kw: Keyword arguments are passed on to the backend logging
:keyword kw: Keyword arguments are passed on to the backend logging
system.
"""
kwargs = self._get_logging_kwargs(namespace, **kw)
self.backend.info(msg, **kwargs)
def warn(self, msg, namespace=None, **kw):
"""
Log to the WARN facility.
:param msg: The message the log.
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
None is passed.
:keyword kw: Keyword arguments are passed on to the backend logging
:keyword kw: Keyword arguments are passed on to the backend logging
system.
"""
kwargs = self._get_logging_kwargs(namespace, **kw)
self.backend.warn(msg, **kwargs)
def error(self, msg, namespace=None, **kw):
"""
Log to the ERROR facility.
:param msg: The message the log.
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
None is passed.
:keyword kw: Keyword arguments are passed on to the backend logging
:keyword kw: Keyword arguments are passed on to the backend logging
system.
"""
kwargs = self._get_logging_kwargs(namespace, **kw)
self.backend.error(msg, **kwargs)
def fatal(self, msg, namespace=None, **kw):
"""
Log to the FATAL (aka CRITICAL) facility.
:param msg: The message the log.
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
None is passed.
:keyword kw: Keyword arguments are passed on to the backend logging
:keyword kw: Keyword arguments are passed on to the backend logging
system.
"""
kwargs = self._get_logging_kwargs(namespace, **kw)
self.backend.fatal(msg, **kwargs)
def debug(self, msg, namespace=None, **kw):
"""
Log to the DEBUG facility.
:param msg: The message the log.
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
None is passed. For debugging, it can be useful to set this to
:param namespace: A log prefix, generally the module ``__name__`` that
the log is coming from. Will default to self._meta.namespace if
None is passed. For debugging, it can be useful to set this to
``__file__``, though ``__name__`` is much less verbose.
:keyword kw: Keyword arguments are passed on to the backend logging
:keyword kw: Keyword arguments are passed on to the backend logging
system.
"""
kwargs = self._get_logging_kwargs(namespace, **kw)
self.backend.debug(msg, **kwargs)

View File

@ -4,37 +4,39 @@ from ..core import backend, output, handler
Log = backend.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
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."""
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
: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():
"""Called by the framework when the extension is 'loaded'."""
handler.register(NullOutputHandler)

View File

@ -11,29 +11,31 @@ from ..utils.fs import abspath
LOG = backend.minimal_logger(__name__)
### FIX ME: This is a redundant name... ?
class CementPluginHandler(plugin.CementPluginHandler):
"""
This class is an internal implementation of the
:ref:`IPlugin <cement.core.plugin>` interface. It does not take any
This class is an internal implementation of the
:ref:`IPlugin <cement.core.plugin>` interface. It does not take any
parameters on initialization.
"""
class Meta:
"""Handler meta-data."""
interface = plugin.IPlugin
"""The interface that this class implements."""
label = 'cement'
"""The string identifier for this class."""
def __init__(self):
super(CementPluginHandler, self).__init__()
self._loaded_plugins = []
self._enabled_plugins = []
self._disabled_plugins = []
def _setup(self, app_obj):
super(CementPluginHandler, self)._setup(app_obj)
self.config_dir = abspath(self.app._meta.plugin_config_dir)
@ -45,20 +47,20 @@ class CementPluginHandler(plugin.CementPluginHandler):
# parse all app configs for plugins
for section in self.app.config.get_sections():
if not self.app.config.has_key(section, 'enable_plugin'):
if not 'enable_plugin' in self.app.config.keys(section):
continue
if is_true(self.app.config.get(section, 'enable_plugin')):
self._enabled_plugins.append(section)
else:
self._disabled_plugins.append(section)
# parse plugin config dir for enabled plugins, or return
# parse plugin config dir for enabled plugins, or return
if self.config_dir:
if not os.path.exists(self.config_dir):
LOG.debug('plugin config dir %s does not exist.' %
LOG.debug('plugin config dir %s does not exist.' %
self.config_dir)
return
for config in glob.glob("%s/*.conf" % self.config_dir):
config = os.path.abspath(os.path.expanduser(config))
LOG.debug("loading plugin config from '%s'." % config)
@ -69,114 +71,114 @@ class CementPluginHandler(plugin.CementPluginHandler):
if not pconfig.get_sections():
LOG.debug("config file '%s' has no sections." % config)
continue
plugin = pconfig.get_sections()[0]
if not pconfig.has_key(plugin, 'enable_plugin'):
if not 'enable_plugin' in pconfig.keys(plugin):
continue
if is_true(pconfig.get(plugin, 'enable_plugin')):
self._enabled_plugins.append(plugin)
self.app.config.add_section(plugin)
# set the app config per the already parsed plugin config
for key in pconfig.keys(plugin):
self.app.config.set(plugin, key, pconfig.get(plugin, key))
else:
self._disabled_plugins.append(section)
def _load_plugin_from_dir(self, plugin_name, plugin_dir):
"""
Load a plugin from file within a plugin directory rather than a
Load a plugin from file within a plugin directory rather than a
python package within sys.path.
:param plugin_name: The name of the plugin, also the name of the file
:param plugin_name: The name of the plugin, also the name of the file
with '.py' appended to the name.
:param plugin_dir: The filesystem directory path where to find the
:param plugin_dir: The filesystem directory path where to find the
file.
"""
full_path = os.path.join(plugin_dir, "%s.py" % plugin_name)
if not os.path.exists(full_path):
LOG.debug("plugin file '%s' does not exist." % full_path)
return False
LOG.debug("attempting to load '%s' from '%s'" % (plugin_name,
LOG.debug("attempting to load '%s' from '%s'" % (plugin_name,
plugin_dir))
# We don't catch this because it would make debugging a nightmare
f, path, desc = imp.find_module(plugin_name, [plugin_dir])
mod = imp.load_module(plugin_name, f, path, desc)
if mod and hasattr(mod, 'load'):
mod.load()
return True
def _load_plugin_from_bootstrap(self, plugin_name, base_package):
"""
Load a plugin from a python package. Returns True if no ImportError
is encountered.
:param plugin_name: The name of the plugin, also the name of the
module to load from base_package.
:param plugin_name: The name of the plugin, also the name of the
module to load from base_package.
I.e. ``myapp.bootstrap.myplugin``
:type plugin_name: str
:param base_package: The base python package to load the plugin module from. I.e.
'myapp.bootstrap' or similar.
:param base_package: The base python package to load the plugin module
from. I.e.'myapp.bootstrap' or similar.
:type base_package: str
:returns: True is the plugin was loaded, False otherwise
:raises: ImportError
"""
if base_package is None:
LOG.debug("plugin bootstrap module is set to None, unusable.")
return False
full_module = '%s.%s' % (base_package, plugin_name)
LOG.debug("attempting to load '%s' from '%s'" % (plugin_name,
LOG.debug("attempting to load '%s' from '%s'" % (plugin_name,
base_package))
# We don't catch this because it would make debugging a nightmare
if full_module not in sys.modules:
__import__(full_module, globals(), locals(), [], 0)
if hasattr(sys.modules[full_module], 'load'):
sys.modules[full_module].load()
return True
def load_plugin(self, plugin_name):
"""
Load a plugin whose name is 'plugin_name'. First attempt to load
from a plugin directory (plugin_dir), secondly attempt to load from a
bootstrap module (plugin_bootstrap) determined by
from a plugin directory (plugin_dir), secondly attempt to load from a
bootstrap module (plugin_bootstrap) determined by
self.app._meta.plugin_bootstrap.
Upon successful loading of a plugin, the plugin name is appended to
the self._loaded_plugins list.
:param plugin_name: The name of the plugin to load.
:type plugin_name: str
:raises: cement.core.exc.FrameworkError
"""
LOG.debug("loading application plugin '%s'" % plugin_name)
# first attempt to load from plugin_dir, then from a bootstrap module
if self._load_plugin_from_dir(plugin_name, self.load_dir):
self._loaded_plugins.append(plugin_name)
elif self._load_plugin_from_bootstrap(plugin_name, self.bootstrap):
self._loaded_plugins.append(plugin_name)
else:
raise exc.FrameworkError("Unable to load plugin '%s'." %
plugin_name)
raise exc.FrameworkError("Unable to load plugin '%s'." %
plugin_name)
def load_plugins(self, plugin_list):
"""
Load a list of plugins. Each plugin name is passed to
Load a list of plugins. Each plugin name is passed to
self.load_plugin().
:param plugin_list: A list of plugin names to load.
"""
for plugin_name in plugin_list:
self.load_plugin(plugin_name)
@ -184,7 +186,7 @@ class CementPluginHandler(plugin.CementPluginHandler):
def get_loaded_plugins(self):
"""List of plugins that have been loaded."""
return self._loaded_plugins
def get_enabled_plugins(self):
"""List of plugins that are enabled (not necessary loaded yet)."""
return self._enabled_plugins
@ -193,6 +195,7 @@ class CementPluginHandler(plugin.CementPluginHandler):
"""List of disabled plugins"""
return self._disabled_plugins
def load():
"""Called by the framework when the extension is 'loaded'."""
handler.register(CementPluginHandler)

View File

@ -3,27 +3,29 @@
import os
import shutil
def abspath(path):
"""
Return an absolute path, while also expanding the '~' user directory
shortcut.
:param path: The original path to expand.
:rtype: str
"""
return os.path.abspath(os.path.expanduser(path))
def backup(path, suffix='.bak'):
"""
Rename a file or directory safely without overwriting an existing
backup of the same name.
:param path: The path to the file or directory to make a backup of.
:param suffix: The suffix to rename files with.
:returns: The new path of backed up file/directory
:rtype: str
"""
count = -1
new_path = None
@ -44,4 +46,4 @@ def backup(path, suffix='.bak'):
break
else:
break
return new_path
return new_path

View File

@ -1,5 +1,6 @@
"""Misc utilities."""
def is_true(item):
"""
Given a value, determine if it is one of [True, 'True', 'true', 1, '1'].
@ -8,7 +9,7 @@ def is_true(item):
:returns: True if `item` is in ``[True, 'True', 'true', 1, '1']``, False
otherwise.
:rtype: boolean
"""
if item in [True, 'True', 'true', 1, '1']:
return True

View File

@ -3,62 +3,67 @@
from subprocess import Popen, PIPE
from multiprocessing import Process
from threading import Thread
def exec_cmd(cmd_args, shell=False):
"""
Execute a shell call using Subprocess.
:param cmd_args: List of command line arguments.
:type cmd_args: list
:param shell: See `Subprocess <http://docs.python.org/library/subprocess.html>`_
:param shell: See `Subprocess
<http://docs.python.org/library/subprocess.html>`_
:type shell: boolean
:returns: The (stdout, stderror, return_code) of the command
:rtype: tuple
Usage:
.. code-block:: python
from cement.utils import shell
stdout, stderr, exitcode = shell.exec_cmd(['echo', 'helloworld'])
"""
proc = Popen(cmd_args, stdout=PIPE, stderr=PIPE, shell=shell)
(stdout, stderr) = proc.communicate()
proc.wait()
return (stdout, stderr, proc.returncode)
def exec_cmd2(cmd_args, shell=False):
"""
Similar to exec_cmd, however does not capture stdout, stderr (therefore
allowing it to print to console).
:param cmd_args: List of command line arguments.
:type cmd_args: list
:param shell: See `Subprocess <http://docs.python.org/library/subprocess.html>`_
:param shell: See `Subprocess
<http://docs.python.org/library/subprocess.html>`_
:type shell: boolean
:returns: The integer return code of the command.
:rtype: int
Usage:
.. code-block:: python
from cement.utils import shell
exitcode = shell.exec_cmd2(['echo', 'helloworld'])
"""
proc = Popen(cmd_args, shell=shell)
proc.wait()
return proc.returncode
def spawn_process(target, start=True, join=False, *args, **kwargs):
"""
A quick wrapper around multiprocessing.Process(). By default the start()
function will be called before the spawned process object is returned.
:param target: The target function to execute in the sub-process.
:param start: Call start() on the process before returning the process
object.
@ -67,11 +72,11 @@ def spawn_process(target, start=True, join=False, *args, **kwargs):
:param args: Additional arguments are passed to Process().
:param kwargs: Additional keyword arguments are passed to Process().
:returns: The process object returned by Process().
Usage:
.. code-block:: python
from cement.utils import shell
def add(a, b):
@ -79,7 +84,7 @@ def spawn_process(target, start=True, join=False, *args, **kwargs):
p = shell.spawn_process(add, args=(12, 27))
p.join()
"""
proc = Process(target=target, *args, **kwargs)
@ -90,11 +95,12 @@ def spawn_process(target, start=True, join=False, *args, **kwargs):
proc.join()
return proc
def spawn_thread(target, start=True, join=False, *args, **kwargs):
"""
A quick wrapper around threading.Thread(). By default the start()
function will be called before the spawned thread object is returned
:param target: The target function to execute in the thread.
:param start: Call start() on the thread before returning the thread
object.
@ -103,11 +109,11 @@ def spawn_thread(target, start=True, join=False, *args, **kwargs):
:param args: Additional arguments are passed to Thread().
:param kwargs: Additional keyword arguments are passed to Thread().
:returns: The thread object returned by Thread().
Usage:
.. code-block:: python
from cement.utils import shell
def add(a, b):
@ -115,7 +121,7 @@ def spawn_thread(target, start=True, join=False, *args, **kwargs):
t = shell.spawn_thread(add, args=(12, 27))
t.join()
"""
thr = Thread(target=target, *args, **kwargs)
@ -125,4 +131,3 @@ def spawn_thread(target, start=True, join=False, *args, **kwargs):
thr.start()
thr.join()
return thr

View File

@ -9,41 +9,43 @@ from nose.tools import eq_ as eq
from nose.tools import raises
from nose import SkipTest
class TestApp(foundation.CementApp):
"""
Basic CementApp for generic testing.
"""
class Meta:
label = 'test'
config_files = []
argv = []
class CementTestCase(unittest.TestCase):
"""
A sub-class of unittest.TestCase.
A sub-class of unittest.TestCase.
"""
app_class = TestApp
"""The test class that is used by self.make_app to create an app."""
def __init__(self, *args, **kw):
super(CementTestCase, self).__init__(*args, **kw)
def setUp(self):
"""
Sets up self.app with a generic TestApp(). Also resets the backend
hooks and handlers so that everytime an app is created it is setup
clean each time.
"""
self.app = self.make_app()
def make_app(self, *args, **kw):
"""
Create a generic app using TestApp. Arguments and Keyword Arguments
are passed to the app.
"""
self.reset_backend()
return self.app_class(*args, **kw)
@ -51,17 +53,17 @@ class CementTestCase(unittest.TestCase):
def reset_backend(self):
"""
Remove all registered hooks and handlers from the backend.
"""
for _handler in backend.handlers.copy():
del backend.handlers[_handler]
for _hook in backend.hooks.copy():
del backend.hooks[_hook]
del backend.hooks[_hook]
def ok(self, expr, msg=None):
"""Shorthand for assert."""
return ok(expr, msg)
def eq(self, a, b, msg=None):
"""Shorthand for 'assert a == b, "%r != %r" % (a, b)'. """
return eq(a, b, msg)
return eq(a, b, msg)

View File

@ -32,14 +32,14 @@ Core features include (but are not limited to):
* Cache handler interface adds caching support for improved performance
* Controller handler supports sub-commands, and nested controllers
* Zero external dependencies* (ext's with dependencies ship separately)
* 100% test coverage
* Extensive documentation
* 100% test coverage using Nose
* 100% PEP8 compliant using `pep8` and `autopep8` tools
* Extensive Sphinx documentation
* Tested on Python 2.6, 2.7, 3.1, and 3.2
*Note that argparse is required as an external dependency for Python < 2.7
and < 3.2.*
Getting More Information
------------------------
@ -50,7 +50,6 @@ Getting More Information
* T-CI: http://travis-ci.org/cement/cement
* HELP: cement@librelist.org - #cement
Documentation
-------------

View File

@ -2,3 +2,4 @@
nose
coverage
sphinx
pep8