allow absolute paths to config.load
[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
49                        return the system
50                        default for configuration storage.
51     :type standalone: bool
52     """
53     standalone = False
54
55     def __init__(self):
56         self._data = {}
57         self._config_checker = None
58
59     @abstractmethod
60     def _get_spec(self):
61         """
62         Returns the spec object for the specific configuration.
63         """
64         return None
65
66     def _safe_get_value(self, key):
67         """
68         Tries to return a value only if the config has already been loaded.
69
70         @rtype: depends on the config structure, dict, str, array, int
71         @return: returns the value for the specified key in the config
72         """
73         leap_assert(self._config_checker, "Load the config first")
74         return self._config_checker.config.get(key, None)
75
76     def get_path_prefix(self):
77         """
78         Returns the platform dependant path prefixer
79         """
80         return get_platform_prefixer().get_path_prefix(
81             standalone=self.standalone)
82
83     def loaded(self):
84         """
85         Returns True if the configuration has been already
86         loaded. False otherwise
87         """
88         return self._config_checker is not None
89
90     def save(self, path_list):
91         """
92         Saves the current configuration to disk.
93
94         :param path_list: list of components that form the relative
95                           path to configuration. The absolute path
96                           will be calculated depending on the platform.
97         :type path_list: list
98
99         :return: True if saved to disk correctly, False otherwise
100         """
101         config_path = os.path.join(self.get_path_prefix(), *(path_list[:-1]))
102         mkdir_p(config_path)
103
104         try:
105             self._config_checker.serialize(os.path.join(config_path,
106                                                         path_list[-1]))
107         except Exception as e:
108             logger.warning("%s" % (e,))
109             raise
110         return True
111
112     def load(self, path="", data=None, mtime=None, relative=True):
113         """
114         Loads the configuration from disk.
115
116         :param path: if relative=True, this is a relative path
117                      to configuration. The absolute path
118                      will be calculated depending on the platform
119         :type path: str
120
121         :param relative: if True, path is relative. If False, it's absolute.
122         :type relative: bool
123
124         :return: True if loaded from disk correctly, False otherwise
125         :rtype: bool
126         """
127
128         if relative is True:
129             config_path = os.path.join(
130                 self.get_path_prefix(), path)
131         else:
132             config_path = path
133
134         self._config_checker = PluggableConfig(format="json")
135         self._config_checker.options = copy.deepcopy(self._get_spec())
136
137         try:
138             if data is None:
139                 self._config_checker.load(fromfile=config_path, mtime=mtime)
140             else:
141                 self._config_checker.load(data, mtime=mtime)
142         except Exception as e:
143             logger.error("Something went wrong while loading " +
144                          "the config from %s\n%s" % (config_path, e))
145             self._config_checker = None
146             return False
147         return True
148
149
150 class LocalizedKey(object):
151     """
152     Decorator used for keys that are localized in a configuration.
153     """
154
155     def __init__(self, func, **kwargs):
156         self._func = func
157
158     def __call__(self, instance, lang="en"):
159         """
160         Tries to return the string for the specified language, otherwise
161         informs the problem and returns an empty string.
162
163         :param lang: language code
164         :type lang: str
165
166         :return: localized value from the possible values returned by
167                  self._func
168         """
169         descriptions = self._func(instance)
170         description_lang = ""
171         config_lang = "en"
172         for key in descriptions.keys():
173             if lang.startswith(key):
174                 config_lang = key
175                 break
176
177         description_lang = descriptions[config_lang]
178         return description_lang
179
180     def __get__(self, instance, instancetype):
181         """
182         Implement the descriptor protocol to make decorating instance
183         method possible.
184         """
185         # Return a partial function with the first argument is the instance
186         # of the class decorated.
187         return functools.partial(self.__call__, instance)
188
189 if __name__ == "__main__":
190     try:
191         config = BaseConfig()  # should throw TypeError for _get_spec
192     except Exception as e:
193         assert isinstance(e, TypeError), "Something went wrong"
194         print "Abstract BaseConfig class is working as expected"