[3] | 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""" |
---|
| 5 | from paste.registry import RegistryManager, StackedObjectProxy |
---|
| 6 | |
---|
| 7 | __all__ = ['DispatchingConfig', 'CONFIG', 'ConfigMiddleware'] |
---|
| 8 | |
---|
| 9 | class 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 | |
---|
| 80 | CONFIG = DispatchingConfig() |
---|
| 81 | |
---|
| 82 | no_config = object() |
---|
| 83 | class 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 | |
---|
| 115 | def make_config_filter(app, global_conf, **local_conf): |
---|
| 116 | conf = global_conf.copy() |
---|
| 117 | conf.update(local_conf) |
---|
| 118 | return ConfigMiddleware(app, conf) |
---|
| 119 | |
---|
| 120 | make_config_middleware = ConfigMiddleware.__doc__ |
---|