# -*- coding: utf-8 -*- # (c) 2023, Alexei Znamensky # Copyright (c) 2023, Ansible Project # Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) # SPDX-License-Identifier: BSD-2-Clause from __future__ import absolute_import, division, print_function __metaclass__ = type import copy class _Variable(object): NOTHING = object() def __init__(self, diff=False, output=True, change=None, fact=False, verbosity=0): self.init = False self.initial_value = None self.value = None self.diff = None self._change = None self.output = None self.fact = None self._verbosity = None self.set_meta(output=output, diff=diff, change=change, fact=fact, verbosity=verbosity) def getchange(self): return self.diff if self._change is None else self._change def setchange(self, value): self._change = value def getverbosity(self): return self._verbosity def setverbosity(self, v): if not (0 <= v <= 4): raise ValueError("verbosity must be an int in the range 0 to 4") self._verbosity = v change = property(getchange, setchange) verbosity = property(getverbosity, setverbosity) def set_meta(self, output=None, diff=None, change=None, fact=None, initial_value=NOTHING, verbosity=None): """Set the metadata for the variable Args: output (bool, optional): flag indicating whether the variable should be in the output of the module. Defaults to None. diff (bool, optional): flag indicating whether to generate diff mode output for this variable. Defaults to None. change (bool, optional): flag indicating whether to track if changes happened to this variable. Defaults to None. fact (bool, optional): flag indicating whether the varaiable should be exposed as a fact of the module. Defaults to None. initial_value (any, optional): initial value of the variable, to be used with `change`. Defaults to NOTHING. verbosity (int, optional): level of verbosity in which this variable is reported by the module as `output`, `fact` or `diff`. Defaults to None. """ if output is not None: self.output = output if change is not None: self.change = change if diff is not None: self.diff = diff if fact is not None: self.fact = fact if initial_value is not _Variable.NOTHING: self.initial_value = copy.deepcopy(initial_value) if verbosity is not None: self.verbosity = verbosity def as_dict(self, meta_only=False): d = { "diff": self.diff, "change": self.change, "output": self.output, "fact": self.fact, "verbosity": self.verbosity, } if not meta_only: d["initial_value"] = copy.deepcopy(self.initial_value) d["value"] = self.value return d def set_value(self, value): if not self.init: self.initial_value = copy.deepcopy(value) self.init = True self.value = value return self def is_visible(self, verbosity): return self.verbosity <= verbosity @property def has_changed(self): return self.change and (self.initial_value != self.value) @property def diff_result(self): if self.diff and self.has_changed: return {'before': self.initial_value, 'after': self.value} return def __str__(self): return "<_Variable: value={0!r}, initial={1!r}, diff={2}, output={3}, change={4}, verbosity={5}>".format( self.value, self.initial_value, self.diff, self.output, self.change, self.verbosity ) class VarDict(object): reserved_names = ('__vars__', '_var', 'var', 'set_meta', 'get_meta', 'set', 'output', 'diff', 'facts', 'has_changed', 'as_dict') def __init__(self): self.__vars__ = dict() def __getitem__(self, item): return self.__vars__[item].value def __setitem__(self, key, value): self.set(key, value) def __getattr__(self, item): try: return self.__vars__[item].value except KeyError: return getattr(super(VarDict, self), item) def __setattr__(self, key, value): if key == '__vars__': super(VarDict, self).__setattr__(key, value) else: self.set(key, value) def _var(self, name): return self.__vars__[name] def var(self, name): return self._var(name).as_dict() def set_meta(self, name, **kwargs): """Set the metadata for the variable Args: name (str): name of the variable having its metadata changed output (bool, optional): flag indicating whether the variable should be in the output of the module. Defaults to None. diff (bool, optional): flag indicating whether to generate diff mode output for this variable. Defaults to None. change (bool, optional): flag indicating whether to track if changes happened to this variable. Defaults to None. fact (bool, optional): flag indicating whether the varaiable should be exposed as a fact of the module. Defaults to None. initial_value (any, optional): initial value of the variable, to be used with `change`. Defaults to NOTHING. verbosity (int, optional): level of verbosity in which this variable is reported by the module as `output`, `fact` or `diff`. Defaults to None. """ self._var(name).set_meta(**kwargs) def get_meta(self, name): return self._var(name).as_dict(meta_only=True) def set(self, name, value, **kwargs): """Set the value and optionally metadata for a variable. The variable is not required to exist prior to calling `set`. For details on the accepted metada see the documentation for method `set_meta`. Args: name (str): name of the variable being changed value (any): the value of the variable, it can be of any type Raises: ValueError: Raised if trying to set a variable with a reserved name. """ if name in self.reserved_names: raise ValueError("Name {0} is reserved".format(name)) if name in self.__vars__: var = self._var(name) var.set_meta(**kwargs) else: var = _Variable(**kwargs) var.set_value(value) self.__vars__[name] = var def output(self, verbosity=0): return dict((n, v.value) for n, v in self.__vars__.items() if v.output and v.is_visible(verbosity)) def diff(self, verbosity=0): diff_results = [(n, v.diff_result) for n, v in self.__vars__.items() if v.diff_result and v.is_visible(verbosity)] if diff_results: before = dict((n, dr['before']) for n, dr in diff_results) after = dict((n, dr['after']) for n, dr in diff_results) return {'before': before, 'after': after} return None def facts(self, verbosity=0): facts_result = dict((n, v.value) for n, v in self.__vars__.items() if v.fact and v.is_visible(verbosity)) return facts_result if facts_result else None @property def has_changed(self): return any(var.has_changed for var in self.__vars__.values()) def as_dict(self): return dict((name, var.value) for name, var in self.__vars__.items())