diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/config.py | 21 | ||||
-rw-r--r-- | python/vyos/utils/dict.py | 2 | ||||
-rw-r--r-- | python/vyos/xml_ref/__init__.py | 3 | ||||
-rw-r--r-- | python/vyos/xml_ref/definition.py | 49 |
4 files changed, 63 insertions, 12 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py index c3bb68373..b0dbc5c2a 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -68,10 +68,16 @@ import json from copy import deepcopy import vyos.configtree -from vyos.xml_ref import multi_to_list, merge_defaults, relative_defaults +from vyos.xml_ref import multi_to_list, from_source +from vyos.xml_ref import merge_defaults, relative_defaults from vyos.utils.dict import get_sub_dict, mangle_dict_keys from vyos.configsource import ConfigSource, ConfigSourceSession +class ConfigDict(dict): + _from_defaults = {} + def from_defaults(self, path: list[str]): + return from_source(self._from_defaults, path) + class Config(object): """ The class of config access objects. @@ -250,6 +256,7 @@ class Config(object): conf_dict = multi_to_list(rpath, conf_dict) if with_defaults or with_recursive_defaults: + conf_dict = ConfigDict(conf_dict) conf_dict = merge_defaults(lpath, conf_dict, get_first_key=get_first_key, recursive=with_recursive_defaults) @@ -263,7 +270,17 @@ class Config(object): isinstance(key_mangling[1], str)): raise ValueError("key_mangling must be a tuple of two strings") - conf_dict = mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1], abs_path=rpath, no_tag_node_value_mangle=no_tag_node_value_mangle) + def mangle(obj): + return mangle_dict_keys(obj, key_mangling[0], key_mangling[1], + abs_path=rpath, + no_tag_node_value_mangle=no_tag_node_value_mangle) + + if isinstance(conf_dict, ConfigDict): + from_defaults = mangle(conf_dict._from_defaults) + conf_dict = mangle(conf_dict) + conf_dict._from_defaults = from_defaults + else: + conf_dict = mangle(conf_dict) return conf_dict diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 28d32bb8d..66fe6dad3 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -87,7 +87,7 @@ def mangle_dict_keys(data, regex, replacement, abs_path=None, no_tag_node_value_ if abs_path is None: abs_path = [] - new_dict = {} + new_dict = type(data)() for k in data.keys(): if no_tag_node_value_mangle and is_tag_value(abs_path + [k]): diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py index 62d3680a1..ad2130dca 100644 --- a/python/vyos/xml_ref/__init__.py +++ b/python/vyos/xml_ref/__init__.py @@ -48,6 +48,9 @@ def is_leaf(path: list) -> bool: def cli_defined(path: list, node: str, non_local=False) -> bool: return load_reference().cli_defined(path, node, non_local=non_local) +def from_source(d: dict, path: list) -> bool: + return load_reference().from_source(d, path) + def component_version() -> dict: return load_reference().component_version() diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py index 7634773d6..43101bb4e 100644 --- a/python/vyos/xml_ref/definition.py +++ b/python/vyos/xml_ref/definition.py @@ -13,7 +13,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with this library. If not, see <http://www.gnu.org/licenses/>. -from typing import Optional, Union, Any +from typing import Optional, Union, Any, TYPE_CHECKING + +# https://peps.python.org/pep-0484/#forward-references +# for type 'ConfigDict' +if TYPE_CHECKING: + from vyos.config import ConfigDict class Xml: def __init__(self): @@ -207,19 +212,42 @@ class Xml: return False return True + def _set_source_recursive(self, o: Union[dict, str, list], b: bool): + d = {} + if not isinstance(o, dict): + d = {'_source': b} + else: + for k, v in o.items(): + d[k] = self._set_source_recursive(v, b) + d |= {'_source': b} + return d + # use local copy of function in module configdict, to avoid circular # import + # + # extend dict_merge to keep track of keys only in source def _dict_merge(self, source, destination): from copy import deepcopy - tmp = deepcopy(destination) + dest = deepcopy(destination) + from_source = {} for key, value in source.items(): - if key not in tmp: - tmp[key] = value + if key not in dest: + dest[key] = value + from_source[key] = self._set_source_recursive(value, True) elif isinstance(source[key], dict): - tmp[key] = self._dict_merge(source[key], tmp[key]) + dest[key], f = self._dict_merge(source[key], dest[key]) + f |= {'_source': False} + from_source[key] = f + + return dest, from_source - return tmp + def from_source(self, d: dict, path: list) -> bool: + for key in path: + d = d[key] if key in d else {} + if not d or not isinstance(d, dict): + return False + return d.get('_source', False) def _relative_defaults(self, rpath: list, conf: dict, recursive=False) -> dict: res: dict = {} @@ -257,13 +285,16 @@ class Xml: return res - def merge_defaults(self, path: list, conf: dict, get_first_key=False, - recursive=False) -> dict: + def merge_defaults(self, path: list, conf: Union[dict, 'ConfigDict'], + 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. """ d = self.relative_defaults(path, conf, get_first_key=get_first_key, recursive=recursive) - d = self._dict_merge(d, conf) + d, f = self._dict_merge(d, conf) + d = type(conf)(d) + if hasattr(d, '_from_defaults'): + setattr(d, '_from_defaults', f) return d |