|
import argparse |
|
import copy |
|
|
|
import yaml |
|
|
|
|
|
class NestedDictAction(argparse.Action): |
|
"""Action class to append items to dict object. |
|
|
|
Examples: |
|
>>> parser = argparse.ArgumentParser() |
|
>>> _ = parser.add_argument('--conf', action=NestedDictAction, |
|
... default={'a': 4}) |
|
>>> parser.parse_args(['--conf', 'a=3', '--conf', 'c=4']) |
|
Namespace(conf={'a': 3, 'c': 4}) |
|
>>> parser.parse_args(['--conf', 'c.d=4']) |
|
Namespace(conf={'a': 4, 'c': {'d': 4}}) |
|
>>> parser.parse_args(['--conf', 'c.d=4', '--conf', 'c=2']) |
|
Namespace(conf={'a': 4, 'c': 2}) |
|
>>> parser.parse_args(['--conf', '{d: 5, e: 9}']) |
|
Namespace(conf={'d': 5, 'e': 9}) |
|
|
|
""" |
|
|
|
_syntax = """Syntax: |
|
{op} <key>=<yaml-string> |
|
{op} <key>.<key2>=<yaml-string> |
|
{op} <python-dict> |
|
{op} <yaml-string> |
|
e.g. |
|
{op} a=4 |
|
{op} a.b={{c: true}} |
|
{op} {{"c": True}} |
|
{op} {{a: 34.5}} |
|
""" |
|
|
|
def __init__( |
|
self, |
|
option_strings, |
|
dest, |
|
nargs=None, |
|
default=None, |
|
choices=None, |
|
required=False, |
|
help=None, |
|
metavar=None, |
|
): |
|
super().__init__( |
|
option_strings=option_strings, |
|
dest=dest, |
|
nargs=nargs, |
|
default=copy.deepcopy(default), |
|
type=None, |
|
choices=choices, |
|
required=required, |
|
help=help, |
|
metavar=metavar, |
|
) |
|
|
|
def __call__(self, parser, namespace, values, option_strings=None): |
|
|
|
if "=" in values: |
|
indict = copy.deepcopy(getattr(namespace, self.dest, {})) |
|
key, value = values.split("=", maxsplit=1) |
|
if not value.strip() == "": |
|
value = yaml.load(value, Loader=yaml.Loader) |
|
if not isinstance(indict, dict): |
|
indict = {} |
|
|
|
keys = key.split(".") |
|
d = indict |
|
for idx, k in enumerate(keys): |
|
if idx == len(keys) - 1: |
|
d[k] = value |
|
else: |
|
if not isinstance(d.setdefault(k, {}), dict): |
|
|
|
d[k] = {} |
|
d = d[k] |
|
|
|
|
|
setattr(namespace, self.dest, indict) |
|
else: |
|
try: |
|
|
|
|
|
|
|
value = eval(values, {}, {}) |
|
if not isinstance(value, dict): |
|
syntax = self._syntax.format(op=option_strings) |
|
mes = f"must be interpreted as dict: but got {values}\n{syntax}" |
|
raise argparse.ArgumentTypeError(self, mes) |
|
except Exception: |
|
|
|
value = yaml.load(values, Loader=yaml.Loader) |
|
if not isinstance(value, dict): |
|
syntax = self._syntax.format(op=option_strings) |
|
mes = f"must be interpreted as dict: but got {values}\n{syntax}" |
|
raise argparse.ArgumentError(self, mes) |
|
|
|
d = getattr(namespace, self.dest, None) |
|
if isinstance(d, dict): |
|
d.update(value) |
|
else: |
|
|
|
setattr(namespace, self.dest, value) |
|
|