mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 11:16:46 +00:00
Resolved Issue 172 - PEP8 Compliance
This commit is contained in:
parent
dccd46e67e
commit
9f8b6dc0c6
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
[](http://travis-ci.org/cement/cement)
|
||||
[](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
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover
|
||||
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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__()
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -1 +1 @@
|
||||
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover
|
||||
__import__('pkg_resources').declare_namespace(__name__) # pragma: no cover
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
-------------
|
||||
|
||||
|
||||
@ -2,3 +2,4 @@
|
||||
nose
|
||||
coverage
|
||||
sphinx
|
||||
pep8
|
||||
Loading…
Reference in New Issue
Block a user