add BaseConfig class and its dependencies
[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[key]
74
75     def get_path_prefix(self):
76         """
77         Returns the platform dependant path prefixer
78
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 will be calculated
96         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):
113         """
114         Loads the configuration from disk
115
116         @type path: str
117         @param path: relative path to configuration. The absolute path
118         will be calculated depending on the platform
119
120         @return: True if loaded from disk correctly, False otherwise
121         """
122
123         config_path = os.path.join(self.get_path_prefix(),
124                                    path)
125
126         self._config_checker = PluggableConfig(format="json")
127         self._config_checker.options = copy.deepcopy(self._get_spec())
128
129         try:
130             if data is None:
131                 self._config_checker.load(fromfile=config_path, mtime=mtime)
132             else:
133                 self._config_checker.load(data, mtime=mtime)
134         except Exception as e:
135             logger.warning("Something went wrong while loading " +
136                            "the config from %s\n%s" % (config_path, e))
137             self._config_checker = None
138             return False
139         return True
140
141
142 class LocalizedKey(object):
143     """
144     Decorator used for keys that are localized in a configuration
145     """
146
147     def __init__(self, func, **kwargs):
148         self._func = func
149
150     def __call__(self, instance, lang="en"):
151         """
152         Tries to return the string for the specified language, otherwise
153         informs the problem and returns an empty string
154
155         @param lang: language code
156         @type lang: str
157
158         @return: localized value from the possible values returned by
159         self._func
160         """
161         descriptions = self._func(instance)
162         description_lang = ""
163         config_lang = "en"
164         for key in descriptions.keys():
165             if lang.startswith(key):
166                 config_lang = key
167                 break
168
169         description_lang = descriptions[config_lang]
170         return description_lang
171
172     def __get__(self, instance, instancetype):
173         """
174         Implement the descriptor protocol to make decorating instance
175         method possible.
176         """
177         # Return a partial function with the first argument is the instance
178         # of the class decorated.
179         return functools.partial(self.__call__, instance)
180
181 if __name__ == "__main__":
182     try:
183         config = BaseConfig()  # should throw TypeError for _get_spec
184     except Exception as e:
185         assert isinstance(e, TypeError), "Something went wrong"
186         print "Abstract BaseConfig class is working as expected"