"""Actions to support jsonnet."""
import json
from typing import Optional, Union, Dict, Tuple, Any
from argparse import Action
from .actions import _is_action_value_list
from .jsonschema import ActionJsonSchema
from .loaders_dumpers import get_loader_exceptions, load_value, load_value_context
from .optionals import (
import_jsonschema,
import_jsonnet,
get_config_read_mode,
get_jsonschema_exceptions,
)
from .util import ParserError, Path
__all__ = [
'ActionJsonnetExtVars',
'ActionJsonnet',
]
[docs]class ActionJsonnetExtVars(ActionJsonSchema):
"""Action to add argument to provide ext_vars for jsonnet parsing."""
[docs] def __init__(self, **kwargs):
"""Initializer for ActionJsonnetExtVars instance."""
super().__init__(schema={'type': 'object'}, with_meta=False)
[docs] def __call__(self, *args, **kwargs):
if len(args) == 0 and 'default' not in kwargs:
kwargs['default'] = {}
return super().__call__(*args, **kwargs)
[docs]class ActionJsonnet(Action):
"""Action to parse a jsonnet, optionally validating against a jsonschema."""
[docs] def __init__(
self,
ext_vars: str = None,
schema: Union[str, Dict] = None,
**kwargs
):
"""Initializer for ActionJsonnet instance.
Args:
ext_vars: Key where to find the external variables required to parse the jsonnet.
schema: Schema to validate values against.
Raises:
ValueError: If a parameter is invalid.
jsonschema.exceptions.SchemaError: If the schema is invalid.
"""
if '_validator' not in kwargs:
import_jsonnet('ActionJsonnet')
if not isinstance(ext_vars, (str, type(None))):
raise ValueError('ext_vars has to be either None or a string.')
self._ext_vars = ext_vars
if schema is not None:
jsonvalidator = import_jsonschema('ActionJsonnet')[1]
if isinstance(schema, str):
with load_value_context('yaml'):
try:
schema = load_value(schema)
except get_loader_exceptions() as ex:
raise ValueError(f'Problems parsing schema :: {ex}') from ex
jsonvalidator.check_schema(schema)
self._validator = ActionJsonSchema._extend_jsonvalidator_with_default(jsonvalidator)(schema)
else:
self._validator = None
else:
self._ext_vars = kwargs.pop('_ext_vars')
self._validator = kwargs.pop('_validator')
super().__init__(**kwargs)
[docs] def __call__(self, *args, **kwargs):
"""Parses an argument as jsonnet using ext_vars if defined.
Raises:
TypeError: If the argument is not valid.
"""
if len(args) == 0:
kwargs['_ext_vars'] = self._ext_vars
kwargs['_validator'] = self._validator
if 'help' in kwargs and '%s' in kwargs['help'] and self._validator is not None:
kwargs['help'] = kwargs['help'] % json.dumps(self._validator.schema, sort_keys=True)
return ActionJsonnet(**kwargs)
setattr(args[1], self.dest, self._check_type(args[2], cfg=args[1]))
def _check_type(self, value, cfg):
islist = _is_action_value_list(self)
ext_vars = cfg.get(self._ext_vars, {})
if not islist:
value = [value]
for num, val in enumerate(value):
try:
if isinstance(val, str):
val = self.parse(val, ext_vars=ext_vars, with_meta=True)
elif self._validator is not None:
self._validator.validate(val)
value[num] = val
except (TypeError, RuntimeError) + get_jsonschema_exceptions() + get_loader_exceptions() as ex:
elem = '' if not islist else ' element '+str(num+1)
raise TypeError(f'Parser key "{self.dest}"{elem}: {ex}') from ex
return value if islist else value[0]
[docs] @staticmethod
def split_ext_vars(ext_vars: Optional[Dict[str, Any]]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
"""Splits an ext_vars dict into the ext_codes and ext_vars required by jsonnet.
Args:
ext_vars: External variables. Values can be strings or any other basic type.
"""
if ext_vars is None:
ext_vars = {}
ext_codes = {k: json.dumps(v) for k, v in ext_vars.items() if not isinstance(v, str)}
ext_vars = {k: v for k, v in ext_vars.items() if isinstance(v, str)}
return ext_vars, ext_codes
[docs] def parse(
self,
jsonnet: Union[str, Path],
ext_vars: Optional[Dict[str, Any]] = None,
with_meta: bool = False,
) -> Dict:
"""Method that can be used to parse jsonnet independent from an ArgumentParser.
Args:
jsonnet: Either a path to a jsonnet file or the jsonnet content.
ext_vars: External variables. Values can be strings or any other basic type.
with_meta: Whether to include metadata in config object.
Returns:
The parsed jsonnet object.
Raises:
TypeError: If the input is neither a path to an existent file nor a jsonnet.
"""
_jsonnet = import_jsonnet('ActionJsonnet')
ext_vars, ext_codes = self.split_ext_vars(ext_vars)
fpath = None
fname = 'snippet'
snippet = jsonnet
try:
fpath = Path(jsonnet, mode=get_config_read_mode())
except TypeError:
pass
else:
fname = jsonnet(absolute=False) if isinstance(jsonnet, Path) else jsonnet
snippet = fpath.get_content()
try:
with load_value_context('yaml'):
values = load_value(_jsonnet.evaluate_snippet(fname, snippet, ext_vars=ext_vars, ext_codes=ext_codes))
except RuntimeError as ex:
raise ParserError(f'Problems evaluating jsonnet "{fname}" :: {ex}') from ex
if self._validator is not None:
self._validator.validate(values)
if with_meta:
if fpath is not None:
values['__path__'] = fpath
values['__orig__'] = snippet
return values