Commit e203408a authored by Theophile GENTILHOMME's avatar Theophile GENTILHOMME
Browse files

use click for config command

parent d245aaa3
......@@ -24,56 +24,19 @@
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
"""Configuration manipulation and display
Usage:
%(prog)s config show
%(prog)s config set [--local] (<key> <value>)...
%(prog)s config get <key>...
%(prog)s config --help
Commands:
show Lists the configuration after resolving defaults and saved variables
set Sets a specific known field to a value
get Prints out the contents of a single field
Options:
-h, --help Display this screen
-l, --local Save values on the local configuration file (.beatrc) instead
of using the global file (~/.beatrc)
Examples:
To show currently set configuration parameters and defaults:
$ %(prog)s config show
To save a different user name token to a file and save results locally - i.e.
doesn't override the global configuration file (notice you can pass multiple
parameters at once using key-value pairs):
$ %(prog)s config set --local user "me" prefix `pwd`/prefix
To query for a specific parameter:
$ %(prog)s config get token
1234567890abcdef1234567890abcde
"""
"""Configuration manipulation and display"""
import os
import sys
import copy
import logging
logger = logging.getLogger(__name__)
import simplejson
import getpass
import click
from .scripts.click_helper import verbosity_option
import simplejson
logger = logging.getLogger(__name__)
DEFAULTS = {
......@@ -99,123 +62,195 @@ DOC = {
class Configuration(object):
'''Keeps track of configuration elements'''
'''Keeps track of configuration elements'''
def __init__(self, args):
def __init__(self, args):
self.files = [
os.path.expanduser('~/.beatrc'),
os.path.realpath('./.beatrc'),
self.files = [
os.path.expanduser('~/.beatrc'),
os.path.realpath('./.beatrc'),
]
self.__data = copy.deepcopy(DEFAULTS)
for k in self.files:
if os.path.exists(k):
with open(k, 'rt') as f: tmp = simplejson.load(f)
self.__data.update(tmp)
logger.info("Loaded configuration file `%s'", k)
self.__data = copy.deepcopy(DEFAULTS)
for k in self.files:
if os.path.exists(k):
with open(k, 'rt') as f: tmp = simplejson.load(f)
self.__data.update(tmp)
logger.info("Loaded configuration file `%s'", k)
for key in DEFAULTS:
self.__data[key] = args.get('--%s' % key) or self.__data[key]
for key in DEFAULTS:
self.__data[key] = args.get('--%s' % key) or self.__data[key]
@property
def path(self):
'''The directory for the prefix'''
@property
def path(self):
'''The directory for the prefix'''
return self.__data['prefix']
return self.__data['prefix']
@property
def cache(self):
'''The directory for the cache'''
@property
def cache(self):
'''The directory for the cache'''
if os.path.isabs(self.__data['cache']):
return self.__data['cache']
return os.path.join(self.__data['prefix'], self.__data['cache'])
if os.path.isabs(self.__data['cache']):
return self.__data['cache']
return os.path.join(self.__data['prefix'], self.__data['cache'])
@property
def database_paths(self):
'''A dict of paths for databases'''
@property
def database_paths(self):
'''A dict of paths for databases'''
return dict((k, self.__data[k]) for k in self.__data if self.is_database_key(k))
return dict(
(k, self.__data[k]) for k in self.__data if self.is_database_key(k)
)
def set(self, key, value, local=False):
'''Sets or resets a field in the configuration'''
def set(self, key, value, local=False):
'''Sets or resets a field in the configuration'''
if not self._is_valid_key(key):
logger.error("Don't know about parameter `%s'", key)
sys.exit(1)
if not self._is_valid_key(key):
logger.error("Don't know about parameter `%s'", key)
sys.exit(1)
if value is not None:
self.__data[key] = value
elif key in DEFAULTS:
self.__data[key] = DEFAULTS[key]
if value is not None:
self.__data[key] = value
elif key in DEFAULTS:
self.__data[key] = DEFAULTS[key]
self.save(local)
self.save(local)
def save(self, local=False):
'''Saves contents to configuration file
def save(self, local=False):
'''Saves contents to configuration file
Parameters:
Parameters:
local (:py:class:`bool`, Optional): if set to ``True``, then save
configuration values to local configuration file (typically
``.beatrc``)
local (:py:class:`bool`, Optional): if set to ``True``, then save
configuration values to local configuration file (typically
``.beatrc``)
'''
'''
path = self.files[0]
if local: path = self.files[1]
path = self.files[0]
if local:
path = self.files[1]
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
with os.fdopen(os.open(path, flags, 0o600), 'wt') as f:
f.write(simplejson.dumps(self.__data, sort_keys=True, indent=4,
separators=(',', ': ')))
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
with os.fdopen(os.open(path, flags, 0o600), 'wt') as f:
f.write(simplejson.dumps(
self.__data, sort_keys=True, indent=4,
separators=(',', ': ')
))
def _is_valid_key(self, key):
return key in DEFAULTS or self.is_database_key(key)
def _is_valid_key(self, key):
return key in DEFAULTS or self.is_database_key(key)
def is_database_key(self, key):
return key.startswith('database/')
def is_database_key(self, key):
return key.startswith('database/')
def __str__(self):
def __str__(self):
return simplejson.dumps(
self.__data, sort_keys=True,
indent=4, separators=(',', ': ')
)
return simplejson.dumps(self.__data, sort_keys=True,
indent=4, separators=(',', ': '))
def as_dict(self):
return copy.copy(self.__data)
def as_dict(self):
return copy.copy(self.__data)
def __getattr__(self, key):
return self.__data[key]
def __getattr__(self, key):
@click.group()
@verbosity_option()
@click.pass_context
def config(ctx):
"""The manager for beat cmdline configuration."""
pass
return self.__data[key]
@config.command()
@click.pass_context
def show(ctx):
"""Shows the configuration.
Lists the configuration after resolving defaults and saved variables
"""
click.echo(ctx.meta['config'])
def process(args):
if args['show']:
print(args['config'])
return 0
@config.command()
@click.argument('key')
@click.pass_context
def get(ctx, key):
"""Prints out the contents of a single field.
elif args['set']:
for k,v in zip(args['<key>'], args['<value>']):
args['config'].set(k, v, args['--local'])
return 0
To query for a specific parameter:
$ %(prog)s config get token
1234567890abcdef1234567890abcde
elif args['get']:
for k in args['<key>']: print(getattr(args['config'], k))
return 0
# Should not happen
logger.error("Unrecognized `config' subcommand")
return 1
\b
Arguments
---------
key : str
The key to return its value from the configuration.
\b
Fails
-----
* If the key is not found.
"""
value = getattr(ctx.meta['config'], key)
if value is None:
# Exit the command line with ClickException in case of errors.
raise click.ClickException(
"The requested key `{}' does not exist".format(key))
click.echo(value)
@config.command()
@click.argument('args', nargs=-1)
@click.option('--local/--not-local', default=False, help='Save values on the '
'local configuration file (.beatrc) instead of using the global '
'file (~/.beatrc)')
@click.pass_context
def set(ctx, args, local):
"""Sets the value for a key.
Sets a specific known field to a value
To save a different user name token to a file and save results locally - i.e.
doesn't override the global configuration file (notice you can pass multiple
parameters at once using key-value pairs):
\b
Arguments
---------
key : str
The key to set the value for.
value : str
The value of the key.
local : bool
Save locally or not
\b
Fails
-----
* If something goes wrong.
"""
if len(args) % 2 != 0:
raise click.BadParameter('You must provide pair(s) of key/value')
try:
for idx in range(0, len(args), 2):
ctx.meta['config'].set(args[idx], args[idx + 1], local)
except Exception:
raise click.ClickException("Failed to change the configuration.")
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
"""Sets-up logging, centrally for beat cmdline
"""
import sys
import logging
# get the default root logger of Beat
_logger = logging.getLogger('beat')
# by default, warning and error messages should be written to sys.stderr
_warn_err = logging.StreamHandler(sys.stderr)
_warn_err.setLevel(logging.WARNING)
_logger.addHandler(_warn_err)
# debug and info messages are written to sys.stdout
class _InfoFilter:
def filter(self, record):
return record.levelno <= logging.INFO
_debug_info = logging.StreamHandler(sys.stdout)
_debug_info.setLevel(logging.DEBUG)
_debug_info.addFilter(_InfoFilter())
_logger.addHandler(_debug_info)
# helper functions to instantiate and set-up logging
def setup(logger_name,
format="%(name)s@%(asctime)s -- %(levelname)s: %(message)s"):
"""This function returns a logger object that is set up to perform logging
using beat loggers.
Parameters
----------
logger_name : str
The name of the module to generate logs for
format : :obj:`str`, optional
The format of the logs, see :py:class:`logging.LogRecord` for more
details. By default, the log contains the logger name, the log time, the
log level and the massage.
Returns
-------
logger : :py:class:`logging.Logger`
The logger configured for logging. The same logger can be retrieved using
the :py:func:`logging.getLogger` function.
"""
# generate new logger object
logger = logging.getLogger(logger_name)
# add log the handlers if not yet done
if not logger_name.startswith("beat") and not logger.handlers:
logger.addHandler(_warn_err)
logger.addHandler(_debug_info)
# this formats the logger to print the desired information
formatter = logging.Formatter(format)
# we have to set the formatter to all handlers registered in the current
# logger
for handler in logger.handlers:
handler.setFormatter(formatter)
# set the same formatter for beat loggers
for handler in _logger.handlers:
handler.setFormatter(formatter)
return logger
def set_verbosity_level(logger, level):
"""Sets the log level for the given logger.
Parameters
----------
logger : :py:class:`logging.Logger` or str
The logger to generate logs for, or the name of the module to generate
logs for.
level : int
Possible log levels are: 0: Error; 1: Warning; 2: Info; 3: Debug.
Raises
------
ValueError
If the level is not in range(0, 4).
"""
if level not in range(0, 4):
raise ValueError(
"The verbosity level %d does not exist. Please reduce the number of "
"'--verbose' parameters in your command line" % level
)
# set up the verbosity level of the logging system
log_level = {
0: logging.ERROR,
1: logging.WARNING,
2: logging.INFO,
3: logging.DEBUG
}[level]
# set this log level to the logger with the specified name
if isinstance(logger, str):
logger = logging.getLogger(logger)
logger.setLevel(log_level)
# set the same log level for the beat logger
_logger.setLevel(log_level)
__all__ = [_ for _ in dir() if not _.startswith('_')]
import logging
import click
from ..log import set_verbosity_level
# This needs to be beat so that logger is configured for all beat packages.
logger = logging.getLogger('beat')
try:
basestring
except NameError:
basestring = str
def verbosity_option(**kwargs):
"""Adds a -v/--verbose option to a click command.
Parameters
----------
**kwargs
All kwargs are passed to click.option.
Returns
-------
callable
A decorator to be used for adding this option.
"""
def custom_verbosity_option(f):
def callback(ctx, param, value):
ctx.meta['verbosity'] = value
set_verbosity_level(logger, value)
logger.debug("Logging of the `beat' logger was set to %d", value)
return value
return click.option(
'-v', '--verbose', count=True,
expose_value=False,
help="Increase the verbosity level from 0 (only error messages) to 1 "
"(warnings), 2 (log messages), 3 (debug information) by adding the "
"--verbose option as often as desired (e.g. '-vvv' for debug).",
callback=callback, **kwargs)(f)
return custom_verbosity_option
"""This is the main entry to beat's cmdlines scripts.
"""
import logging
import pkg_resources
import click
from click_plugins import with_plugins
from .click_helper import verbosity_option
from ..config import Configuration
@with_plugins(pkg_resources.iter_entry_points('beat.cli'))
@click.group()
@click.option('--test-mode/--no-test-mode', default=False)
@verbosity_option()
@click.pass_context
def main(ctx, test_mode):
"""The main command line interface for beat cmdline. Look below for available
commands."""
# Check that we are in a BEAT working folder
config = Configuration(ctx.meta)
# Sets up the central logger
if not test_mode:
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) #lets everything pass by default
# Console logging
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # default level
format_str = "%(message)s"
if 'verbosity' in ctx.meta and ctx.meta['verbosity'] >= 2:
format_str = "[%(asctime)s - %(name)s] %(levelname)s: %(message)s"
formatter = logging.Formatter(format_str, datefmt="%d/%b/%Y %H:%M:%S")
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# Execute the command
ctx.meta['config'] = config
......@@ -53,7 +53,15 @@ setup(
entry_points={
'console_scripts': [
'beat = beat.cmdline.scripts.beat:main',
'beat = beat.cmdline.scripts.main_cli:main',
],
'beat.cli': [
'config = beat.cmdline.config:config',
],
'beat.config.cli': [
'show = beat.cmdline.config:show',
'get = beat.cmdline.config:get',
'set = beat.cmdline.config:set',
],
},
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment