1# -*- coding: utf-8 -*-
2"""
3    webapp2_extras.config
4    =====================
5
6    Configuration object for webapp2.
7
8    This module is deprecated. See :class:`webapp2.WSGIApplication.config`.
9
10    :copyright: 2011 by tipfy.org.
11    :license: Apache Sotware License, see LICENSE for details.
12"""
13from __future__ import absolute_import
14
15import warnings
16
17import webapp2
18
19warnings.warn(DeprecationWarning(
20    'webapp2_extras.config is deprecated. '
21    'The WSGIApplication uses webapp2.Config instead.'),
22    stacklevel=1)
23
24#: Value used for missing default values.
25DEFAULT_VALUE = object()
26
27#: Value used for required values.
28REQUIRED_VALUE = object()
29
30
31class Config(dict):
32    """A simple configuration dictionary keyed by module name. This is a
33    dictionary of dictionaries. It requires all values to be dictionaries
34    and applies updates and default values to the inner dictionaries instead
35    of the first level one.
36
37    The configuration object can be set as a ``config`` attribute of
38    :class:`WSGIApplication`::
39
40        import webapp2
41        from webapp2_extras import config as webapp2_config
42
43        my_config = {}
44
45        my_config['my.module'] = {
46            'foo': 'bar',
47        }
48
49        app = webapp2.WSGIApplication(routes=[
50            webapp2.Route('/', name='home', handler=MyHandler)
51        ])
52        app.config = webapp2_config.Config(my_config)
53
54    Then to read configuration values, get them from the app::
55
56        class MyHandler(RequestHandler):
57            def get(self):
58                foo = self.app.config['my.module']['foo']
59
60                # ...
61    """
62    #: Loaded module configurations.
63    loaded = None
64
65    def __init__(self, values=None, defaults=None):
66        """Initializes the configuration object.
67
68        :param values:
69            A dictionary of configuration dictionaries for modules.
70        :param defaults:
71            A dictionary of configuration dictionaries for initial default
72            values. These modules are marked as loaded.
73        """
74        self.loaded = []
75        if values is not None:
76            assert isinstance(values, dict)
77            for module, config in values.iteritems():
78                self.update(module, config)
79
80        if defaults is not None:
81            assert isinstance(defaults, dict)
82            for module, config in defaults.iteritems():
83                self.setdefault(module, config)
84                self.loaded.append(module)
85
86    def __getitem__(self, module):
87        """Returns the configuration for a module. If it is not already
88        set, loads a ``default_config`` variable from the given module and
89        updates the configuration with those default values
90
91        Every module that allows some kind of configuration sets a
92        ``default_config`` global variable that is loaded by this function,
93        cached and used in case the requested configuration was not defined
94        by the user.
95
96        :param module:
97            The module name.
98        :returns:
99            A configuration value.
100        """
101        if module not in self.loaded:
102            # Load default configuration and update config.
103            values = webapp2.import_string(module + '.default_config',
104                                           silent=True)
105            if values:
106                self.setdefault(module, values)
107
108            self.loaded.append(module)
109
110        try:
111            return dict.__getitem__(self, module)
112        except KeyError:
113            raise KeyError('Module %r is not configured.' % module)
114
115    def __setitem__(self, module, values):
116        """Sets a configuration for a module, requiring it to be a dictionary.
117
118        :param module:
119            A module name for the configuration, e.g.: `webapp2.ext.i18n`.
120        :param values:
121            A dictionary of configurations for the module.
122        """
123        assert isinstance(values, dict), 'Module configuration must be a dict.'
124        dict.__setitem__(self, module, SubConfig(module, values))
125
126    def get(self, module, default=DEFAULT_VALUE):
127        """Returns a configuration for a module. If default is not provided,
128        returns an empty dict if the module is not configured.
129
130        :param module:
131            The module name.
132        :params default:
133            Default value to return if the module is not configured. If not
134            set, returns an empty dict.
135        :returns:
136            A module configuration.
137        """
138        if default is DEFAULT_VALUE:
139            default = {}
140
141        return dict.get(self, module, default)
142
143    def setdefault(self, module, values):
144        """Sets a default configuration dictionary for a module.
145
146        :param module:
147            The module to set default configuration, e.g.: `webapp2.ext.i18n`.
148        :param values:
149            A dictionary of configurations for the module.
150        :returns:
151            The module configuration dictionary.
152        """
153        assert isinstance(values, dict), 'Module configuration must be a dict.'
154        if module not in self:
155            dict.__setitem__(self, module, SubConfig(module))
156
157        module_dict = dict.__getitem__(self, module)
158
159        for key, value in values.iteritems():
160            module_dict.setdefault(key, value)
161
162        return module_dict
163
164    def update(self, module, values):
165        """Updates the configuration dictionary for a module.
166
167        :param module:
168            The module to update the configuration, e.g.: `webapp2.ext.i18n`.
169        :param values:
170            A dictionary of configurations for the module.
171        """
172        assert isinstance(values, dict), 'Module configuration must be a dict.'
173        if module not in self:
174            dict.__setitem__(self, module, SubConfig(module))
175
176        dict.__getitem__(self, module).update(values)
177
178    def get_config(self, module, key=None, default=REQUIRED_VALUE):
179        """Returns a configuration value for a module and optionally a key.
180        Will raise a KeyError if they the module is not configured or the key
181        doesn't exist and a default is not provided.
182
183        :param module:
184            The module name.
185        :params key:
186            The configuration key.
187        :param default:
188            Default value to return if the key doesn't exist.
189        :returns:
190            A module configuration.
191        """
192        module_dict = self.__getitem__(module)
193
194        if key is None:
195            return module_dict
196
197        return module_dict.get(key, default)
198
199
200class SubConfig(dict):
201    def __init__(self, module, values=None):
202        dict.__init__(self, values or ())
203        self.module = module
204
205    def __getitem__(self, key):
206        try:
207            value = dict.__getitem__(self, key)
208        except KeyError:
209            raise KeyError('Module %r does not have the config key %r' %
210                (self.module, key))
211
212        if value is REQUIRED_VALUE:
213            raise KeyError('Module %r requires the config key %r to be '
214                'set.' % (self.module, key))
215
216        return value
217
218    def get(self, key, default=None):
219        if key not in self:
220            value = default
221        else:
222            value = dict.__getitem__(self, key)
223
224        if value is REQUIRED_VALUE:
225            raise KeyError('Module %r requires the config key %r to be '
226                'set.' % (self.module, key))
227
228        return value
229