1# (c) 2006 Ian Bicking, Philip Jenvey and contributors
2# Written for Paste (http://pythonpaste.org)
3# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
4"""Paste Configuration Middleware and Objects"""
5from paste.registry import RegistryManager, StackedObjectProxy
6
7__all__ = ['DispatchingConfig', 'CONFIG', 'ConfigMiddleware']
8
9class DispatchingConfig(StackedObjectProxy):
10    """
11    This is a configuration object that can be used globally,
12    imported, have references held onto.  The configuration may differ
13    by thread (or may not).
14
15    Specific configurations are registered (and deregistered) either
16    for the process or for threads.
17    """
18    # @@: What should happen when someone tries to add this
19    # configuration to itself?  Probably the conf should become
20    # resolved, and get rid of this delegation wrapper
21
22    def __init__(self, name='DispatchingConfig'):
23        super(DispatchingConfig, self).__init__(name=name)
24        self.__dict__['_process_configs'] = []
25
26    def push_thread_config(self, conf):
27        """
28        Make ``conf`` the active configuration for this thread.
29        Thread-local configuration always overrides process-wide
30        configuration.
31
32        This should be used like::
33
34            conf = make_conf()
35            dispatching_config.push_thread_config(conf)
36            try:
37                ... do stuff ...
38            finally:
39                dispatching_config.pop_thread_config(conf)
40        """
41        self._push_object(conf)
42
43    def pop_thread_config(self, conf=None):
44        """
45        Remove a thread-local configuration.  If ``conf`` is given,
46        it is checked against the popped configuration and an error
47        is emitted if they don't match.
48        """
49        self._pop_object(conf)
50
51    def push_process_config(self, conf):
52        """
53        Like push_thread_config, but applies the configuration to
54        the entire process.
55        """
56        self._process_configs.append(conf)
57
58    def pop_process_config(self, conf=None):
59        self._pop_from(self._process_configs, conf)
60
61    def _pop_from(self, lst, conf):
62        popped = lst.pop()
63        if conf is not None and popped is not conf:
64            raise AssertionError(
65                "The config popped (%s) is not the same as the config "
66                "expected (%s)"
67                % (popped, conf))
68
69    def _current_obj(self):
70        try:
71            return super(DispatchingConfig, self)._current_obj()
72        except TypeError:
73            if self._process_configs:
74                return self._process_configs[-1]
75            raise AttributeError(
76                "No configuration has been registered for this process "
77                "or thread")
78    current = current_conf = _current_obj
79
80CONFIG = DispatchingConfig()
81
82no_config = object()
83class ConfigMiddleware(RegistryManager):
84    """
85    A WSGI middleware that adds a ``paste.config`` key (by default)
86    to the request environment, as well as registering the
87    configuration temporarily (for the length of the request) with
88    ``paste.config.CONFIG`` (or any other ``DispatchingConfig``
89    object).
90    """
91
92    def __init__(self, application, config, dispatching_config=CONFIG,
93                 environ_key='paste.config'):
94        """
95        This delegates all requests to `application`, adding a *copy*
96        of the configuration `config`.
97        """
98        def register_config(environ, start_response):
99            popped_config = environ.get(environ_key, no_config)
100            current_config = environ[environ_key] = config.copy()
101            environ['paste.registry'].register(dispatching_config,
102                                               current_config)
103
104            try:
105                app_iter = application(environ, start_response)
106            finally:
107                if popped_config is no_config:
108                    environ.pop(environ_key, None)
109                else:
110                    environ[environ_key] = popped_config
111            return app_iter
112
113        super(self.__class__, self).__init__(register_config)
114
115def make_config_filter(app, global_conf, **local_conf):
116    conf = global_conf.copy()
117    conf.update(local_conf)
118    return ConfigMiddleware(app, conf)
119
120make_config_middleware = ConfigMiddleware.__doc__
121