From f2f6b963b755ca5da3321e84738bfec1d08fb1ea Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 2 Jun 2023 23:29:47 -0500 Subject: xml: T5218: fix error and simplify logic in recursive option --- python/vyos/xml_ref/__init__.py | 7 ++- python/vyos/xml_ref/definition.py | 103 ++++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 50 deletions(-) (limited to 'python/vyos/xml_ref') diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py index ae5184746..d3fb4ab07 100644 --- a/python/vyos/xml_ref/__init__.py +++ b/python/vyos/xml_ref/__init__.py @@ -62,5 +62,8 @@ def get_config_defaults(rpath: list, conf: dict, get_first_key=False, get_first_key=get_first_key, recursive=recursive) -def merge_defaults(path: list, conf: dict) -> dict: - return load_reference().merge_defaults(path, conf) +def merge_defaults(path: list, conf: dict, get_first_key=False, + recursive=False) -> dict: + return load_reference().merge_defaults(path, conf, + get_first_key=get_first_key, + recursive=recursive) diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index 429331577..970dd915f 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see . -from typing import Union, Any +from typing import Optional, Union, Any from vyos.configdict import dict_merge class Xml: @@ -116,9 +116,17 @@ class Xml: return res - def _get_default_value(self, node: dict): + def _get_default_value(self, node: dict) -> Optional[str]: return self._get_ref_node_data(node, "default_value") + def _get_default(self, node: dict) -> Optional[Union[str, list]]: + default = self._get_default_value(node) + if default is None: + return None + if self._is_multi_node(node) and not isinstance(default, list): + return [default] + return default + def get_defaults(self, path: list, get_first_key=False, recursive=False) -> dict: """Return dict containing default values below path @@ -128,18 +136,23 @@ class Xml: 'relative_defaults' """ res: dict = {} + if self.is_tag(path): + return res + d = self._get_ref_path(path) + + if self._is_leaf_node(d): + default_value = self._get_default(d) + if default_value is not None: + return {path[-1]: default_value} if path else {} + for k in list(d): if k in ('node_data', 'component_version') : continue - d_k = d[k] - if self._is_leaf_node(d_k): - default_value = self._get_default_value(d_k) + if self._is_leaf_node(d[k]): + default_value = self._get_default(d[k]) if default_value is not None: - pos = default_value - if self._is_multi_node(d_k) and not isinstance(pos, list): - pos = [pos] - res |= {k: pos} + res |= {k: default_value} elif self.is_tag(path + [k]): # tag node defaults are used as suggestion, not default value; # should this change, append to path and continue if recursive @@ -150,8 +163,6 @@ class Xml: res |= pos if res: if get_first_key or not path: - if not isinstance(res, dict): - raise TypeError("Cannot get_first_key as data under node is not of type dict") return res return {path[-1]: res} @@ -163,7 +174,7 @@ class Xml: return [next(iter(c.keys()))] if c else [] try: tmp = step(conf) - if self.is_tag_value(path + tmp): + if tmp and self.is_tag_value(path + tmp): c = conf[tmp[0]] if not isinstance(c, dict): raise ValueError @@ -175,57 +186,53 @@ class Xml: return False return True - def relative_defaults(self, rpath: list, conf: dict, get_first_key=False, + def _relative_defaults(self, rpath: list, conf: dict, recursive=False) -> dict: + res: dict = {} + res = self.get_defaults(rpath, recursive=recursive, + get_first_key=True) + for k in list(conf): + if isinstance(conf[k], dict): + step = self._relative_defaults(rpath + [k], conf=conf[k], + recursive=recursive) + res |= step + + if res: + return {rpath[-1]: res} if rpath else res + + return {} + + def relative_defaults(self, path: list, conf: dict, get_first_key=False, recursive=False) -> dict: """Return dict containing defaults along paths of a config dict """ if not conf: - return self.get_defaults(rpath, get_first_key=get_first_key, + return self.get_defaults(path, get_first_key=get_first_key, recursive=recursive) - if rpath and rpath[-1] in list(conf): - conf = conf[rpath[-1]] - if not isinstance(conf, dict): - raise TypeError('conf at path is not of type dict') + if path and path[-1] in list(conf): + conf = conf[path[-1]] + conf = {} if not isinstance(conf, dict) else conf - if not self._well_defined(rpath, conf): + if not self._well_defined(path, conf): print('path to config dict does not define full config paths') return {} - res: dict = {} - for k in list(conf): - pos = self.get_defaults(rpath + [k], recursive=recursive) - res |= pos - - if isinstance(conf[k], dict): - step = self.relative_defaults(rpath + [k], conf=conf[k], - recursive=recursive) - res |= step + res = self._relative_defaults(path, conf, recursive=recursive) - if res: - if get_first_key: - return res - return {rpath[-1]: res} if rpath else res + if get_first_key and path: + if res.values(): + res = next(iter(res.values())) + else: + res = {} - return {} + return res - def merge_defaults(self, path: list, conf: dict) -> dict: + def merge_defaults(self, path: list, conf: dict, get_first_key=False, + recursive=False) -> dict: """Return config dict with defaults non-destructively merged This merges non-recursive defaults relative to the config dict. """ - if path[-1] in list(conf): - config = conf[path[-1]] - if not isinstance(config, dict): - raise TypeError('conf at path is not of type dict') - shift = False - else: - config = conf - shift = True - - if not self._well_defined(path, config): - print('path to config dict does not define config paths; conf returned unchanged') - return conf - - d = self.relative_defaults(path, conf=config, get_first_key=shift) + d = self.relative_defaults(path, conf, get_first_key=get_first_key, + recursive=recursive) d = dict_merge(d, conf) return d -- cgit v1.2.3 From d57b7d327cef20dc607550bda1c5632c3d24a719 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 2 Jun 2023 23:29:56 -0500 Subject: config: T5228: use local _dict_merge to avoid circular import --- python/vyos/xml_ref/definition.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'python/vyos/xml_ref') diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index 970dd915f..92d069f05 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -14,7 +14,6 @@ # along with this library. If not, see . from typing import Optional, Union, Any -from vyos.configdict import dict_merge class Xml: def __init__(self): @@ -186,6 +185,20 @@ class Xml: return False return True + # use local copy of function in module configdict, to avoid circular + # import + def _dict_merge(self, source, destination): + from copy import deepcopy + tmp = deepcopy(destination) + + for key, value in source.items(): + if key not in tmp: + tmp[key] = value + elif isinstance(source[key], dict): + tmp[key] = self._dict_merge(source[key], tmp[key]) + + return tmp + def _relative_defaults(self, rpath: list, conf: dict, recursive=False) -> dict: res: dict = {} res = self.get_defaults(rpath, recursive=recursive, @@ -234,5 +247,5 @@ class Xml: """ d = self.relative_defaults(path, conf, get_first_key=get_first_key, recursive=recursive) - d = dict_merge(d, conf) + d = self._dict_merge(d, conf) return d -- cgit v1.2.3 From d19e7e5ce633f42f2a084ebd17e8c1e3dfef6b03 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 2 Jun 2023 23:35:00 -0500 Subject: config: T5228: add get_config_defaults options to match get_config_dict For those cases not covered by automatic merging of defaults in get_config_dict(..., with_defaults=True), get_config_defaults should take arguments consistent with those of get_config_dict, for ease of merging results. --- python/vyos/config.py | 27 ++++++++++++++++++++++++++- python/vyos/xml_ref/__init__.py | 6 +++--- 2 files changed, 29 insertions(+), 4 deletions(-) (limited to 'python/vyos/xml_ref') diff --git a/python/vyos/config.py b/python/vyos/config.py index 1b9e61651..c3bb68373 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -68,7 +68,7 @@ import json from copy import deepcopy import vyos.configtree -from vyos.xml_ref import multi_to_list, merge_defaults +from vyos.xml_ref import multi_to_list, merge_defaults, relative_defaults from vyos.utils.dict import get_sub_dict, mangle_dict_keys from vyos.configsource import ConfigSource, ConfigSourceSession @@ -267,6 +267,31 @@ class Config(object): return conf_dict + def get_config_defaults(self, path=[], effective=False, key_mangling=None, + no_tag_node_value_mangle=False, get_first_key=False, + recursive=False) -> dict: + lpath = self._make_path(path) + root_dict = self.get_cached_root_dict(effective) + conf_dict = get_sub_dict(root_dict, lpath, get_first_key) + + defaults = relative_defaults(lpath, conf_dict, + get_first_key=get_first_key, + recursive=recursive) + if key_mangling is None: + return defaults + + rpath = lpath if get_first_key else lpath[:-1] + + if not (isinstance(key_mangling, tuple) and \ + (len(key_mangling) == 2) and \ + isinstance(key_mangling[0], str) and \ + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + + defaults = mangle_dict_keys(defaults, key_mangling[0], key_mangling[1], abs_path=rpath, no_tag_node_value_mangle=no_tag_node_value_mangle) + + return defaults + def is_multi(self, path): """ Args: diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py index d3fb4ab07..53ca6ed98 100644 --- a/python/vyos/xml_ref/__init__.py +++ b/python/vyos/xml_ref/__init__.py @@ -55,10 +55,10 @@ def get_defaults(path: list, get_first_key=False, recursive=False) -> dict: return load_reference().get_defaults(path, get_first_key=get_first_key, recursive=recursive) -def get_config_defaults(rpath: list, conf: dict, get_first_key=False, - recursive=False) -> dict: +def relative_defaults(rpath: list, conf: dict, get_first_key=False, + recursive=False) -> dict: - return load_reference().relative_defaults(rpath, conf=conf, + return load_reference().relative_defaults(rpath, conf, get_first_key=get_first_key, recursive=recursive) -- cgit v1.2.3