146f1e4c76dcd6f72622e32541a2a9822f902b74
[leap_pycommon.git] / src / leap / common / config / baseconfig.py
1 # -*- coding: utf-8 -*-
2 # baseconfig.py
3 # Copyright (C) 2013 LEAP
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 """
19 Implements the abstract base class for configuration
20 """
21
22 import copy
23 import logging
24 import functools
25 import os
26
27 from abc import ABCMeta, abstractmethod
28
29 from leap.common.check import leap_assert
30 from leap.common.files import mkdir_p
31 from leap.common.config.pluggableconfig import PluggableConfig
32 from leap.common.config.prefixers import get_platform_prefixer
33
34 logger = logging.getLogger(__name__)
35
36
37 class BaseConfig:
38     """
39     Abstract base class for any JSON based configuration
40     """
41
42     __metaclass__ = ABCMeta
43
44     """
45     Standalone is a class wide parameter
46
47     @param standalone: if True it will return the prefix for a
48     standalone application. Otherwise, it will return the system
49     default for configuration storage.
50     @type standalone: bool
51     """
52     standalone = False
53
54     def __init__(self):
55         self._data = {}
56         self._config_checker = None
57
58     @abstractmethod
59     def _get_spec(self):
60         """
61         Returns the spec object for the specific configuration
62         """
63         return None
64
65     def _safe_get_value(self, key):
66         """
67         Tries to return a value only if the config has already been loaded
68
69         @rtype: depends on the config structure, dict, str, array, int
70         @return: returns the value for the specified key in the config
71         """
72         leap_assert(self._config_checker, "Load the config first")
73         return self._config_checker.config.get(key, None)
74
75     def get_path_prefix(self):
76         """
77         Returns the platform dependant path prefixer
78         """
79         return get_platform_prefixer().get_path_prefix(
80             standalone=self.standalone)
81
82     def loaded(self):
83         """
84         Returns True if the configuration has been already
85         loaded. False otherwise
86         """
87         return self._config_checker is not None
88
89     def save(self, path_list):
90         """
91         Saves the current configuration to disk
92
93         @param path_list: list of components that form the relative
94         path to configuration. The absolute path will be calculated
95         depending on the platform.
96         @type path_list: list
97
98         @return: True if saved to disk correctly, False otherwise
99         """
100         config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1]))
101         mkdir_p(config_path)
102
103         try:
104             self._config_checker.serialize(os.path.join(config_path,
105                                                         path_list[-1]))
106         except Exception as e:
107             logger.warning("%s" % (e,))
108             raise
109         return True
110
111     def load(self, path="", data=None, mtime=None):
112         """
113         Loads the configuration from disk
114
115         @type path: str
116         @param path: relative path to configuration. The absolute path
117         will be calculated depending on the platform
118
119         @return: True if loaded from disk correctly, False otherwise
120         """
121
122         config_path = os.path.join(self.get_path_prefix(),
123                                    path)
124
125         self._config_checker = PluggableConfig(format="json")
126         self._config_checker.options = copy.deepcopy(self._get_spec())
127
128         try:
129             if data is None:
130                 self._config_checker.load(fromfile=config_path, mtime=mtime)
131             else:
132                 self._config_checker.load(data, mtime=mtime)
133         except Exception as e:
134             logger.warning("Something went wrong while loading " +
135                            "the config from %s\n%s" % (config_path, e))
136             self._config_checker = None
137             return False
138         return True
139
140
141 class LocalizedKey(object):
142     """
143     Decorator used for keys that are localized in a configuration
144     """
145
146     def __init__(self, func, **kwargs):
147         self._func = func
148
149     def __call__(self, instance, lang="en"):
150         """
151         Tries to return the string for the specified language, otherwise
152         informs the problem and returns an empty string
153
154         @param lang: language code
155         @type lang: str
156
157         @return: localized value from the possible values returned by
158         self._func
159         """
160         descriptions = self._func(instance)
161         description_lang = ""
162         config_lang = "en"
163         for key in descriptions.keys():
164             if lang.startswith(key):
165                 config_lang = key
166                 break
167
168         description_lang = descriptions[config_lang]
169         return description_lang
170
171     def __get__(self, instance, instancetype):
172         """
173         Implement the descriptor protocol to make decorating instance
174         method possible.
175         """
176         # Return a partial function with the first argument is the instance
177         # of the class decorated.
178         return functools.partial(self.__call__, instance)
179
180 if __name__ == "__main__":
181     try:
182         config = BaseConfig()  # should throw TypeError for _get_spec
183     except Exception as e:
184         assert isinstance(e, TypeError), "Something went wrong"
185         print "Abstract BaseConfig class is working as expected"