first commit

This commit is contained in:
2020-11-03 18:30:14 -08:00
commit 31d8522470
1881 changed files with 345408 additions and 0 deletions

View File

@@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
"""
Translate an options data structure into command line args
"""
# Import python libs
import sys
import inspect
import argparse
import functools
import collections
import pop.hub
__virtualname__ = "args"
__contracts__ = [__virtualname__]
class ActionWrapper:
"""
This class wraps argparse.Action instances in order to mark arguments passed
on CLI as explicitly passed
"""
def __init__(self, action):
self._action = action
functools.update_wrapper(self, action)
def __call__(self, parser, namespace, values, option_string):
# Let's store the call to this option as an explicit CLI call for later
# use when overwriting any configuration settings on file with those
# from CLI
if getattr(parser, "_explicit_cli_args_", None) is None:
setattr(parser, "_explicit_cli_args_", set())
parser._explicit_cli_args_.add(
self._action.dest
) # pylint: disable=protected-access
# Carry on regular operation
return self._action(parser, namespace, values, option_string)
def __getattribute__(self, name):
if name == "_action":
return object.__getattribute__(self, name)
# Proxy any attribute's search to the _action instance
return getattr(self._action, name)
def __repr__(self):
return repr(self._action)
class ActionClassWrapper:
"""
This class wraps argparse.Action classes in order to mark arguments passed
on CLI as explicitly passed
"""
def __init__(self, klass):
self._klass = klass
def __call__(self, *args, **kwargs):
return ActionWrapper(self._klass(*args, **kwargs))
def __repr__(self):
return repr(self._klass)
def __getattribute__(self, name):
if name == "_klass":
return object.__getattribute__(self, name)
# Proxy any attributes search to the _klass instance
return getattr(self._klass, name)
class ArgumentParser(argparse.ArgumentParser):
def register(self, name, value, obj): # pylint: disable=arguments-differ
if name == "action":
# Let's wrap it on our action class wrapper so we can latter store
# which options were explicitly passed from CLI
return super(ArgumentParser, self).register(
name, value, ActionClassWrapper(obj)
)
return super(ArgumentParser, self).register(name, value, obj)
def parse_known_args(self, args=None, namespace=None):
namespace, arg_strings = super().parse_known_args(args, namespace)
explicit_cli_args = getattr(self, "_explicit_cli_args_", set())
if "_explicit_cli_args_" not in namespace:
setattr(namespace, "_explicit_cli_args_", set())
namespace._explicit_cli_args_.update(explicit_cli_args)
return namespace, arg_strings
def __init__(hub: "pop.hub.Hub"):
"""
Set up the local memory copy of the parser
"""
hub.conf._mem["args"] = {}
def _init_parser(hub: "pop.hub.Hub", opts):
if "parser" not in hub.conf._mem["args"]:
# Instantiate the parser
hub.conf._mem["args"]["parser"] = ArgumentParser(**opts.get("_argparser_", {}))
def _keys(opts):
"""
Return the keys in the right order
"""
if isinstance(opts, collections.OrderedDict):
return sorted(
list(opts), key=lambda k: opts[k].get("display_priority", sys.maxsize)
)
return sorted(opts, key=lambda k: (opts[k].get("display_priority", sys.maxsize), k))
def subs(hub: "pop.hub.Hub", opts):
"""
Set up sub parsers, if using sub parsers this needs to be called
before calling setup.
opts dict:
<sub_title>:
[desc]: 'Some subparser'
help: 'subparser!'
"""
_init_parser(hub, opts)
hub.conf._mem["args"]["sub"] = hub.conf._mem["args"]["parser"].add_subparsers(
dest="_subparser_"
)
hub.conf._mem["args"]["subs"] = {}
for arg in _keys(opts):
if arg in ("_argparser_",):
continue
comps = opts[arg]
kwargs = {}
if "help" in comps:
kwargs["help"] = comps["help"]
if "desc" in comps:
kwargs["description"] = comps["desc"]
hub.conf._mem["args"]["subs"][arg] = hub.conf._mem["args"]["sub"].add_parser(
arg, **kwargs
)
return {"result": True, "return": True}
def setup(hub: "pop.hub.Hub", opts):
"""
Take in a pre-defined opts dict and translate it to args
opts dict:
<arg>:
[group]: foo
[default]: bar
[action]: store_true
[options]: # arg will be turned into --arg
- '-A'
- '--cheese'
[choices]:
- foo
- bar
- baz
[nargs]: +
[type]: int
[dest]: cheese
help: Some great help message
"""
_init_parser(hub, opts)
defaults = {}
groups = {}
ex_groups = {}
for arg in _keys(opts):
if arg in ("_argparser_",):
continue
comps = opts[arg]
positional = comps.pop("positional", False)
if positional:
args = [arg]
else:
long_opts = ["--{}".format(arg.replace("_", "-"))]
short_opts = []
for o_str in comps.get("options", []):
if not o_str.startswith("--") and o_str.startswith("-"):
short_opts.append(o_str)
continue
long_opts.append(o_str)
args = short_opts + long_opts
kwargs = {}
kwargs["action"] = action = comps.get("action", None)
if action is None:
# Non existing option defaults to a StoreAction in argparse
action = hub.conf._mem["args"]["parser"]._registry_get(
"action", action
) # pylint: disable=protected-access
if isinstance(action, str):
signature = inspect.signature(
hub.conf._mem["args"]["parser"]._registry_get("action", action).__init__
) # pylint: disable=protected-access
else:
signature = inspect.signature(action.__init__)
for param in signature.parameters:
if param == "self" or param not in comps:
continue
if param == "dest":
kwargs["dest"] = comps.get("dest", arg)
continue
if param == "help":
kwargs["help"] = comps.get("help", "THIS NEEDS SOME DOCUMENTATION!!")
continue
if param == "default":
defaults[comps.get("dest", arg)] = comps[param]
kwargs[param] = comps[param]
if "group" in comps:
group = comps["group"]
if group not in groups:
groups[group] = hub.conf._mem["args"]["parser"].add_argument_group(
group
)
groups[group].add_argument(*args, **kwargs)
continue
if "ex_group" in comps:
group = comps["ex_group"]
if group not in ex_groups:
ex_groups[group] = hub.conf._mem["args"][
"parser"
].add_mutually_exclusive_group()
ex_groups[group].add_argument(*args, **kwargs)
continue
if "sub" in comps:
subs = comps["sub"]
if not isinstance(subs, list):
subs = [subs]
for sub in subs:
sparse = hub.conf._mem["args"]["subs"].get(sub)
if not sparse:
# Maybe raise exception here? Malformed config?
continue
sparse.add_argument(*args, **kwargs)
continue
hub.conf._mem["args"]["parser"].add_argument(*args, **kwargs)
return {"result": True, "return": defaults}
def parse(
hub: "pop.hub.Hub", args=None, namespace=None, only_parse_known_arguments=False
):
"""
Parse the command line options
"""
if only_parse_known_arguments:
opts, unknown_args = hub.conf._mem["args"]["parser"].parse_known_args(
args, namespace
)
opts_dict = opts.__dict__
opts_dict["_unknown_args_"] = unknown_args
else:
opts = hub.conf._mem["args"]["parser"].parse_args(args, namespace)
opts_dict = opts.__dict__
return {"result": True, "return": opts_dict}
def render(hub: "pop.hub.Hub", defaults, cli_opts, explicit_cli_args):
"""
For options specified as such, take the string passed into the cli and
render it using the specified render flag
"""
for key in explicit_cli_args:
rend = defaults.get(key, {}).get("render")
if rend:
ref = f"conf.{rend}.render"
cli_opts[key] = hub.pop.ref.last(ref)(cli_opts[key])
return cli_opts