1"""
2Lazily-evaluated data structures, primarily used by Salt's loader
3"""
4
5
6import logging
7from collections.abc import MutableMapping
8
9import salt.exceptions
10
11log = logging.getLogger(__name__)
12
13
14def verify_fun(lazy_obj, fun):
15    """
16    Check that the function passed really exists
17    """
18    if not fun:
19        raise salt.exceptions.SaltInvocationError(
20            "Must specify a function to run!\nex: manage.up"
21        )
22    if fun not in lazy_obj:
23        # If the requested function isn't available, lets say why
24        raise salt.exceptions.CommandExecutionError(lazy_obj.missing_fun_string(fun))
25
26
27class LazyDict(MutableMapping):
28    """
29    A base class of dict which will lazily load keys once they are needed
30
31    TODO: negative caching? If you ask for 'foo' and it doesn't exist it will
32    look EVERY time unless someone calls load_all()
33    As of now this is left to the class which inherits from this base
34    """
35
36    def __init__(self):
37        self.clear()
38
39    def __nonzero__(self):
40        # we are zero if dict is empty and loaded is true
41        return bool(self._dict or not self.loaded)
42
43    def __bool__(self):
44        # we are zero if dict is empty and loaded is true
45        return self.__nonzero__()
46
47    def clear(self):
48        """
49        Clear the dict
50        """
51        # create a dict to store loaded values in
52        self._dict = getattr(self, "mod_dict_class", dict)()
53
54        # have we already loded everything?
55        self.loaded = False
56
57    def _load(self, key):
58        """
59        Load a single item if you have it
60        """
61        raise NotImplementedError()
62
63    def _load_all(self):
64        """
65        Load all of them
66        """
67        raise NotImplementedError()
68
69    def _missing(self, key):
70        """
71        Whether or not the key is missing (meaning we know it's not there)
72        """
73        return False
74
75    def missing_fun_string(self, function_name):
76        """
77        Return the error string for a missing function.
78
79        Override this to return a more meaningfull error message if possible
80        """
81        return "'{}' is not available.".format(function_name)
82
83    def __setitem__(self, key, val):
84        self._dict[key] = val
85
86    def __delitem__(self, key):
87        del self._dict[key]
88
89    def __getitem__(self, key):
90        """
91        Check if the key is ttld out, then do the get
92        """
93        if self._missing(key):
94            raise KeyError(key)
95
96        if key not in self._dict and not self.loaded:
97            # load the item
98            if self._load(key):
99                log.debug("LazyLoaded %s", key)
100                return self._dict[key]
101            else:
102                log.debug(
103                    "Could not LazyLoad %s: %s", key, self.missing_fun_string(key)
104                )
105                raise KeyError(key)
106        else:
107            return self._dict[key]
108
109    def __len__(self):
110        # if not loaded,
111        if not self.loaded:
112            self._load_all()
113        return len(self._dict)
114
115    def __iter__(self):
116        if not self.loaded:
117            self._load_all()
118        return iter(self._dict)
119