summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/config.py21
-rw-r--r--python/vyos/utils/dict.py2
-rw-r--r--python/vyos/xml_ref/__init__.py3
-rw-r--r--python/vyos/xml_ref/definition.py49
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