Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When submodules are defined, require a submodule. #60

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
84d9f68
When submodules are defined, require a submodule.
Oct 9, 2016
f84fed3
add facility to provide (program) defaults to logging extension
samjacobson Oct 17, 2016
542be02
Fix for config_section bug with subcommands
tbartlett0 Nov 4, 2016
d50a776
Enbaled the possibility to provide a logger and handler to @begin.log…
Jan 6, 2017
ba012a1
Fix the issue with old way to declare begin.logging decorator.
Jan 6, 2017
7f8b1b0
Trying to pass all unit test in extesions file.
Jan 6, 2017
bf50228
Trying to pass unittest, update from Pullrequest in previous repo.
Jan 6, 2017
c9e646f
Merged with @samjacobson/begins idea of deafults loggers
Jan 7, 2017
2006c43
Merge pull request #1 from isman7/master
sh0oki Jan 7, 2017
6858946
Merge pull request #4 from tbartlett0/config_section_fix
sh0oki Jan 23, 2017
cf9a5db
Remove unused have_extensions variable
Jan 23, 2017
c7681a2
fix urls
esc Dec 19, 2016
07021ae
Use file log format when stdout is not a tty
ropez May 22, 2015
6f42919
Merge pull request #5 from tbartlett0/ropez_and_esc
sh0oki Jan 24, 2017
287f6d1
Fix 'config_section' parameter not working.
manchoz Feb 21, 2017
07a87d0
Add support for multiple sections if 'config_section' is a list.
manchoz Feb 21, 2017
5ab296d
Merge branch 'feature_multiple_sections'
manchoz Feb 21, 2017
4f58170
Refactor config_section management (multiple sections too) and add te…
manchoz Feb 22, 2017
b9f281c
Remove not needed conditional, fix bug and break when value found. Ad…
manchoz Mar 7, 2017
45de8d7
Explicit Set creation. Try to fix Python 2.6 CI error.
manchoz Mar 7, 2017
bbebf89
Explicitly number the format fields. Try to fix Python 2.6 CI error.
manchoz Apr 21, 2017
fdf78e2
Merge pull request #7 from manchoz/master
sh0oki Apr 21, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ docs/_build/

# PyBuilder
target/

# PyCharm
.idea
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: [email protected]
email: [email protected]
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion begin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
113 changes: 78 additions & 35 deletions begin/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import argparse
import os
import sys
import warnings

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


NODEFAULT = object()


Expand All @@ -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"
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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


Expand All @@ -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)
Expand All @@ -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, []))
Expand Down
50 changes: 43 additions & 7 deletions begin/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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):
Expand All @@ -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

8 changes: 8 additions & 0 deletions tests/config_test.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
[main]
arg = value

[foo]
arg = alt_value
foo_arg = foo_value

[bar]
bar_arg = bar_value
arg = bar_alt_value
2 changes: 1 addition & 1 deletion tests/test_begins.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ def test_readme(self):


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