from __future__ import print_function, unicode_literals
from io import StringIO
import textwrap
import json
import argparse
# from devtools import debug
from .configs import Config, typefns, identity
DEFAULT_NONE = '____'
[docs]
class Argument(object):
def __init__(self, *flags, **args):
if 'choices' in args and args['choices'] is None:
del args['choices']
if 'container' in args:
del args['container']
self._args = args
self._flags = flags
# self.__dict__.update(args)
def __json__(self):
return self.__dict__
[docs]
def typename(self):
if 'kind' in self._args:
return self._args['kind']
if self._args.get('action') in {'store_true', 'store_false'}:
return 'BOOL'
tp = self._args.get('type')
if tp is not None:
return tp.__name__.upper()
v = self._args.get('default')
if v is None:
return DEFAULT_NONE
return v.__class__.__name__.upper()
[docs]
def typefn(self):
return typefns.get(self.typename(), identity)
[docs]
def pytype(self):
argname = self.argname()
if argname == 'fname':
return 'str'
if 'choices' in self._args:
return 'Literal[%s]' % ', '.join(repr(x) for x in self._args['choices'])
if 'type' in self._args:
res = self._args['type']
elif self._args.get('action') in {'store_true', 'store_false'}:
res = bool
elif self._args.get('kind', 'unknown') is None:
return 'None'
elif self._args.get('kind', '').startswith("FNAME"):
res = str
else:
res = self._args.get('default').__class__
if res is None:
return 'None'
return res.__name__
[docs]
def argname(self):
if 'dest' in self._args:
return self._args['dest']
return self._flags[0].lstrip('-').replace('-', '_')
# return self._flags[-1].lstrip('-').replace('-', '_')
[docs]
def help(self):
if 'help' in self._args:
return self._args['help']
return ''
[docs]
def default(self):
if 'default' in self._args:
return self._args['default']
if self._args.get('action') == 'store_true':
return False
if self._args.get('action') == 'store_false':
return True
return DEFAULT_NONE
[docs]
def add_to_parser(self, parser):
args = self._args
args.pop('default', None) # remove default value
args.pop('kind', None) # remove our attributes
if args.get('action') in {'store_true', 'store_false'}:
args['default'] = None
# debug("ADD_TO_PARSER:", self._flags, args)
parser.add_argument(*self._flags, **args)
[docs]
class Namespace(object):
def __init__(self, ns):
self.ns = ns
def __repr__(self):
return json.dumps(vars(self.ns), indent=4, sort_keys=True)
[docs]
def items(self):
return list(dict(vars(self.ns)).items())
def __getitem__(self, key):
return getattr(self.ns, key)
def __setitem__(self, key, val):
setattr(self.ns, key, val)
def __delitem__(self, key):
delattr(self.ns, key)
def __getattr__(self, key):
return getattr(self.ns, key)
[docs]
class Arguments(object):
def __init__(self, config_files=None, debug=False, *posargs, parents=None, **kwargs):
if config_files is None:
config_files = []
# passthrough to argparse
self.posargs = posargs
self.kwargs = kwargs
# print("CONFIG_FILES:", config_files)
self.debug = debug
self.arglist = []
self.args = {}
self.config_files = config_files
self.argtypes = {}
self.defaults = {}
self.parents = parents
[docs]
def load_config_files(self):
for filename in self.config_files:
if filename == '.pydeps' or filename.endswith('.pydeps'):
self.load_pydeps_config(filename)
[docs]
def write_default_config(self):
"""Utility function to create .configs.Config
# XXX: a more general utility to create configs from argparse
# would be nice...
"""
fp = StringIO()
# fp = sys.stdout
print("class Config(object):", file=fp)
arglist = self.arglist
if self.parents:
parent_actions = []
for parent in self.parents:
for action in parent._actions:
# if isinstance(action, argparse._StoreAction):
parent_actions.append(Argument(
*action.option_strings, **action.__dict__
))
arglist = parent_actions + arglist
for arg in arglist:
debug(arg._args)
default = arg.default()
if default == DEFAULT_NONE:
default = None
elif isinstance(default, str):
default = repr(default)
help = textwrap.wrap(arg.help(), 80 - 7) # 7 = len(" #: ")
for helpline in help:
print("", file=fp)
print(" #: {help}".format(help=helpline), end="", file=fp)
print("", file=fp)
typename = arg.pytype()
if typename == 'list' and default is None:
typename = 'Optional[List[str]]'
elif default is None:
typename = f'Optional[{typename}]'
elif default == [] and typename == 'list':
typename = 'List[str]'
# add py3 type annotations
# print(f" {arg.argname()}: {typename} = {default}", file=fp)
# witout type annotations
print(" {argname} = {default}".format(argname=arg.argname(), default=default), file=fp)
print("", file=fp)
print(" def set_field(self, field, value):", file=fp)
for arg in self.arglist:
print(" if field == '{argname}':".format(argname=arg.argname()), file=fp)
print(" self.{argname} = {typefn}(value)".format(argname=arg.argname(), typefn=arg.typefn().__name__), file=fp)
print("", file=fp)
# debug(res)
res = fp.getvalue()
print(res)
return res
[docs]
def parse_args(self, argv):
self.kwargs['parents'] = self.parents
p = argparse.ArgumentParser(*self.posargs, **self.kwargs)
for arg in self.arglist:
arg.add_to_parser(p)
args = Namespace(p.parse_args(argv))
config = Config.load(self.config_files)
config.update({k: v for k, v in args.items() if v is not None})
# print('---- yaml ---------------')
# print(config.write_yaml())
# print('---- json ---------------')
# print(config.write_json())
# print('---- ini ---------------')
# print(config.write_ini())
# print('---- toml ---------------')
# print(config.write_toml())
# print('-------------------')
return config
[docs]
def add(self, *flags, **kwargs):
# debug(flags, kwargs)
arg = Argument(*flags, **kwargs)
self.arglist.append(arg)
argname = arg.argname()
self.args[argname] = arg
self.argtypes[argname] = arg.typename()
self.defaults[argname] = arg.default()
def __repr__(self):
return json.dumps(dict(
types=self.argtypes,
defaults=self.defaults,
), indent=4, sort_keys=True)