Skip to content
Permalink
Browse files

Merge fdf78e2 into 23bc0d8

  • Loading branch information...
sh0oki committed Apr 21, 2017
2 parents 23bc0d8 + fdf78e2 commit 530f77a1f50b770bbb0384b8cca1c754cbcf9d40
Showing with 211 additions and 54 deletions.
  1. +3 −0 .gitignore
  2. +4 −1 .travis.yml
  3. +2 −2 README.rst
  4. +2 −1 begin/__init__.py
  5. +78 −35 begin/cmdline.py
  6. +43 −7 begin/extensions.py
  7. +8 −0 tests/config_test.cfg
  8. +1 −1 tests/test_begins.py
  9. +43 −3 tests/test_cmdline.py
  10. +1 −1 tests/test_extensions.py
  11. +26 −3 tests/test_start.py
@@ -51,3 +51,6 @@ docs/_build/

# PyBuilder
target/

# PyCharm
.idea
@@ -5,14 +5,17 @@ python:
- 3.2
- 3.3
- 3.4
- 3.5
- pypy
install:
- pip install -r requirements.txt
# coverage 4 dropped support for 3.2 so we need to downgrade
- if [[ $TRAVIS_PYTHON_VERSION == '3.2' ]]; then pip install 'coverage<4'; fi
- python setup.py install
script:
- coverage run setup.py test
- coverage report --show-missing
after_success:
- coveralls
notifications:
email: aaron.iles+travis-ci@gmail.com
email: aaron.iles+travis-ci@gmail.com
@@ -298,6 +298,6 @@ be made using GitHub's `issues system`_.
:target: https://coveralls.io/r/aliles/begins?branch=master
:alt: Latest PyPI version

.. |pypi_version| image:: https://pypip.in/v/begins/badge.png
:target: https://crate.io/packages/begins/
.. |pypi_version| image:: https://img.shields.io/pypi/v/bloscpack.svg
:target: https://pypi.python.org/pypi/begins
:alt: Latest PyPI version
@@ -7,7 +7,8 @@
from begin.version import __version__

from begin.extensions import tracebacks
from begin.extensions import logger as logging
from begin.extensions import logger_func as logging
from begin.extensions import logger

import begin.formatters
import begin.utils
@@ -2,6 +2,7 @@
import argparse
import os
import sys
import warnings

try:
import configparser
@@ -18,7 +19,6 @@
__all__ = ['create_parser', 'populate_parser',
'apply_options', 'call_function']


NODEFAULT = object()


@@ -37,10 +37,11 @@ def __init__(self, env_prefix=None, config_file=None, config_section=None):
self._use_env = env_prefix is not None
self._prefix = '' if not self._use_env else env_prefix
self._parser = configparser.ConfigParser()
self._section = config_section
self._section = None
self.section = config_section
if config_file is not None:
self._parser.read([config_file,
os.path.join(os.path.expanduser('~'), config_file)])
os.path.join(os.path.expanduser('~'), config_file)])

def metavar(self, name):
"Generate meta variable name for parameter"
@@ -56,18 +57,45 @@ def from_param(self, param, default=NODEFAULT):

def from_name(self, name, default=NODEFAULT, section=None):
"Get default value from argument name"
if len(self._parser.sections()) > 0:
section = self._section if section is None else section
sections = self._get_list_section(section) if section is not None else self.section
for sec in sections:
try:
default = self._parser.get(section, name)
default = self._parser.get(sec, name)
break
except (configparser.NoSectionError, configparser.NoOptionError):
pass
if self._use_env:
default = os.environ.get(self.metavar(name), default)
return default

@property
def section(self):
return self._section

@section.setter
def section(self, section):
self._section = self._get_list_section(section)

@staticmethod
def _get_list_section(section):
if isinstance(section, list):
_section = section
elif isinstance(section, str):
_section = list()
_section.append(section)
elif section is None:
return list()
else:
raise TypeError(
"'config_section' should be of {0} or {1}. You passed {2}.".format(
type(list()),
type(str()),
type(section)))
return _section

def set_config_section(self, section):
self._section = section
warnings.warn("deprecated. Please, use 'DefaultsManager.section' setter", DeprecationWarning)
self.section = section


def program_name(filename, func):
@@ -94,11 +122,11 @@ def populate_flag(parser, param, defaults):
if param.annotation is not param.empty:
help = param.annotation + ' '
parser.add_argument('--' + param.name.replace('_', '-'),
action='store_true', default=default, dest=param.name,
help=(help + '(default: %(default)s)'if not default else ''))
action='store_true', default=default, dest=param.name,
help=(help + '(default: %(default)s)' if not default else ''))
parser.add_argument('--no-' + param.name.replace('_', '-'),
action='store_false', default=default, dest=param.name,
help=(help + '(default: %(default)s)' if default else ''))
action='store_false', default=default, dest=param.name,
help=(help + '(default: %(default)s)' if default else ''))


def populate_option(parser, param, defaults, short_args):
@@ -133,8 +161,8 @@ def populate_parser(parser, defaults, funcsig, short_args, lexical_order):
params = sorted(params, key=lambda p: p.name)
for param in params:
if param.kind == param.POSITIONAL_OR_KEYWORD or \
param.kind == param.KEYWORD_ONLY or \
param.kind == param.POSITIONAL_ONLY:
param.kind == param.KEYWORD_ONLY or \
param.kind == param.POSITIONAL_ONLY:
if isinstance(param.default, bool):
populate_flag(parser, param, defaults)
else:
@@ -151,8 +179,8 @@ def populate_parser(parser, defaults, funcsig, short_args, lexical_order):


def create_parser(func, env_prefix=None, config_file=None, config_section=None,
short_args=True, lexical_order=False, sub_group=None, plugins=None,
collector=None, formatter_class=argparse.HelpFormatter):
short_args=True, lexical_order=False, sub_group=None, plugins=None,
collector=None, formatter_class=argparse.HelpFormatter):
"""Create and OptionParser object from a function definition.
Use the function's signature to generate an OptionParser object. Default
@@ -163,38 +191,51 @@ def create_parser(func, env_prefix=None, config_file=None, config_section=None,
arguments will raise a ValueError exception. A prefix on expected
environment variables can be added using the env_prefix argument.
"""
defaults = DefaultsManager(env_prefix, config_file, func.__name__)
section = func.__name__
if config_section is not None:
if config_file is not None:
section = config_section
else:
warnings.warn("You passed 'config_section' but 'config_file' is missing.")
defaults = DefaultsManager(env_prefix, config_file, section)
parser = argparse.ArgumentParser(
prog=program_name(sys.argv[0], func),
argument_default=NODEFAULT,
conflict_handler='resolve',
description = func.__doc__,
formatter_class=formatter_class
prog=program_name(sys.argv[0], func),
argument_default=NODEFAULT,
conflict_handler='resolve',
description=func.__doc__,
formatter_class=formatter_class
)
# Main function
while hasattr(func, '__wrapped__') and not hasattr(func, '__signature__'):
if isinstance(func, extensions.Extension):
func.add_arguments(parser, defaults)
func = getattr(func, '__wrapped__')
funcsig = signature(func)
populate_parser(parser, defaults, funcsig, short_args, lexical_order)
# Subcommands
collector = collector if collector is not None else subcommands.COLLECTORS[sub_group]
if plugins is not None:
collector.load_plugins(plugins)
if len(collector) > 0:
subparsers = parser.add_subparsers(title='Available subcommands',
dest='_subcommand')
dest='_subcommand')
subparsers.required = True
for subfunc in collector.commands():
funcsig = signature(subfunc)
funcsig = signature(subfunc)
help = None
if subfunc.__doc__ is not None:
help = subfunc.__doc__.splitlines()[0]
subparser = subparsers.add_parser(subfunc.__name__, help=help,
conflict_handler='resolve', description=subfunc.__doc__,
formatter_class=formatter_class)
defaults.set_config_section(subfunc.__name__)
conflict_handler='resolve', description=subfunc.__doc__,
formatter_class=formatter_class)
section = subfunc.__name__
if config_section is not None:
if config_file is not None:
section = config_section
else:
warnings.warn("You passed 'config_section' but 'config_file' is missing.")
defaults.section = section
populate_parser(subparser, defaults, funcsig, short_args, lexical_order)
have_extensions = False
while hasattr(func, '__wrapped__') and not hasattr(func, '__signature__'):
if isinstance(func, extensions.Extension):
func.add_arguments(parser, defaults)
have_extensions = True
func = getattr(func, '__wrapped__')
funcsig = signature(func)
populate_parser(parser, defaults, funcsig, short_args, lexical_order)
return parser


@@ -207,6 +248,7 @@ def call_function(func, funcsig, opts):
options object or to failure to use the command line arguments list will
result in a CommandLineError being raised.
"""

def getoption(opts, name, default=None):
if not hasattr(opts, name):
msg = "Missing command line options '{0}'".format(name)
@@ -220,11 +262,12 @@ def getoption(opts, name, default=None):
else:
value = default
return value

pargs = []
kwargs = {}
for param in funcsig.parameters.values():
if param.kind == param.POSITIONAL_OR_KEYWORD or \
param.kind == param.POSITIONAL_ONLY:
param.kind == param.POSITIONAL_ONLY:
pargs.append(getoption(opts, param.name))
elif param.kind == param.VAR_POSITIONAL:
pargs.extend(getoption(opts, param.name, []))
@@ -62,6 +62,21 @@ class Logging(Extension):

section = 'logging'

def __init__(self, func, **kwargs):

super(Logging, self).__init__(func)
self.arg_logger = kwargs.pop("logger", None)
self.arg_handler = kwargs.pop("handler", None)
self.args = kwargs

def logLevelName(self, value):
if value is None:
return value
if isinstance(value, str):
return value
return logging.getLevelName(value)


def add_arguments(self, parser, defaults):
"Add command line arguments for configuring the logging module"
exclusive = parser.add_mutually_exclusive_group()
@@ -75,16 +90,16 @@ def add_arguments(self, parser, defaults):
'Detailed control of logging output')
group.add_argument('--loglvl',
default=defaults.from_name(
'level', section=self.section, default=None),
'level', section=self.section, default=self.logLevelName(self.args.get('level'))),
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
help='Set explicit log level')
group.add_argument('--logfile',
default=defaults.from_name(
'file', section=self.section, default=None),
'file', section=self.section, default=self.args.get('filename')),
help='Output log messages to file')
group.add_argument('--logfmt',
default=defaults.from_name(
'format', section=self.section, default=None),
'format', section=self.section, default=self.args.get('format')),
help='Log message format')

def run(self, opts):
@@ -98,29 +113,50 @@ def run(self, opts):
elif opts.quiet:
level = logging.WARNING
# logger
logger = logging.getLogger()

logger = self.arg_logger if self.arg_logger else logging.getLogger()
for handler in logger.handlers:
logger.removeHandler(handler)
logger.setLevel(level)
# handler

if opts.logfile is None:
handler = logging.StreamHandler(sys.stdout)
elif platform.system() != 'Windows':
handler = logging.handlers.WatchedFileHandler(opts.logfile)
else:
handler = logging.FileHandler(opts.logfile)
logger.addHandler(handler)

logger.addHandler(self.arg_handler if self.arg_handler else handler)

# formatter
fmt = opts.logfmt
if fmt is None:
if opts.logfile is None:
if sys.stdout.isatty() and opts.logfile is None:
fmt = '%(message)s'
else:
fmt = '[%(asctime)s] [%(levelname)s] [%(pathname)s:%(lineno)s] %(message)s'
formatter = logging.Formatter(fmt)
handler.setFormatter(formatter)


def logger(func):
def logger_func(func=None, **kwargs):
"Add command line extension for logging module"
def _logger(func):
return Logging(func, **kwargs)

# logger() is a decorator factory
if func is None and len(kwargs) > 0:
return _logger
# not correctly used to decorate a function
elif not callable(func):
raise ValueError("Function '{0!r}' is not callable".format(func))
return Logging(func)


def logger(**kwargs):
"Add command line extension for logging module"
def decorator(func):
return Logging(func, **kwargs)
return decorator

@@ -1,2 +1,10 @@
[main]
arg = value

[foo]
arg = alt_value
foo_arg = foo_value

[bar]
bar_arg = bar_value
arg = bar_alt_value
@@ -29,4 +29,4 @@ def test_readme(self):


if __name__ == '__main__':
unittest.begin()
unittest.main()

0 comments on commit 530f77a

Please sign in to comment.
You can’t perform that action at this time.