diff options
| -rw-r--r-- | paste/__init__.py | 6 | ||||
| -rw-r--r-- | paste/configmiddleware.py | 26 | ||||
| -rw-r--r-- | paste/pyconfig.py | 83 | ||||
| -rwxr-xr-x | paste/server.py | 4 | ||||
| -rw-r--r-- | paste/util/threadinglocal.py | 3 |
5 files changed, 119 insertions, 3 deletions
diff --git a/paste/__init__.py b/paste/__init__.py index 792d600..5b85f25 100644 --- a/paste/__init__.py +++ b/paste/__init__.py @@ -1 +1,5 @@ -# +import pyconfig + +__all__ = ["CONFIG"] + +CONFIG = pyconfig.DispatchingConfig() diff --git a/paste/configmiddleware.py b/paste/configmiddleware.py index 20226f6..bf4768a 100644 --- a/paste/configmiddleware.py +++ b/paste/configmiddleware.py @@ -1,7 +1,29 @@ +from paste.util import threadinglocal +from paste import CONFIG +from paste import wsgilib + def config_middleware(app, config): def replacement_app(environ, start_response): - environ['paste.config'] = config.copy() - return app(environ, start_response) + conf = environ['paste.config'] = config.copy() + app_iter = None + CONFIG.push_thread_config(conf) + try: + app_iter = app(environ, start_response) + finally: + if app_iter is None: + # An error occurred... + CONFIG.pop_thread_config(conf) + if type(app_iter) in (list, tuple): + # Because it is a concrete iterator (not a generator) we + # know the configuration for this thread is no longer + # needed: + CONFIG.pop_thread_config(conf) + return app_iter + else: + def close_config(): + CONFIG.pop_thread_config(conf) + new_app_iter = wsgilib.add_close(app_iter, close_config) + return new_app_iter return replacement_app diff --git a/paste/pyconfig.py b/paste/pyconfig.py index 070a50a..b9d15c5 100644 --- a/paste/pyconfig.py +++ b/paste/pyconfig.py @@ -18,6 +18,8 @@ import os from paste.util import thirdparty UserDict = thirdparty.load_new_module('UserDict', (2, 3)) from paste.reloader import watch_file +from paste.util.threadinglocal import local +import threading def load(filename): conf = Config() @@ -203,3 +205,84 @@ class Config(UserDict.DictMixin): return value +class DispatchingConfig(object): + + """ + This is a configuration object that can be used globally, + imported, have references held onto. The configuration may differ + by thread (or may not). + + Specific configurations are registered (and deregistered) either + for the process or for threads. + """ + + _constructor_lock = threading.Lock() + + def __init__(self): + self._constructor_lock.acquire() + try: + self.dispatching_id = 0 + while 1: + self._local_key = 'paste.processconfig_%i' % self.dispatching_id + if not local.wsgi.has_key(self._local_key): + break + self.dispatching_id += 1 + finally: + self._constructor_lock.release() + local.wsgi[self._local_key] = [] + self._process_configs = [] + + def push_thread_config(self, conf): + """ + Make ``conf`` the active configuration for this thread. + Thread-local configuration always overrides process-wide + configuration. + + This should be used like: + + conf = make_conf() + dispatching_config.push_thread_config(conf) + try: + ... do stuff ... + finally: + dispatching_config.pop_thread_config(conf) + """ + local.wsgi[self._local_key].append(conf) + + def pop_thread_config(self, conf=None): + """ + Remove a thread-local configuration. If ``conf`` is given, + it is checked against the popped configuration and an error + is emitted if they don't match. + """ + self._pop_from(local.wsgi[self._local_key], conf) + + def _pop_from(self, lst, conf): + popped = lst.pop() + if conf is not None and popped is not conf: + raise AssertionError( + "The config popped (%s) is not the same as the config " + "expected (%s)" + % (popped, conf)) + + def push_process_config(self, conf): + """ + Like push_thread_config, but applies the configuration to + the entire process. + """ + self._process_configs.append(conf) + + def pop_process_config(self, conf=None): + self._pop_from(self._process_config, conf) + + def __getattr__(self, attr): + thread_configs = local.wsgi[self._local_key] + if thread_configs: + conf = thread_configs[-1] + elif self._process_configs: + conf = self._process_configs[-1] + else: + raise AttributeError( + "No configuration has been registered for this process " + "or thread") + return getattr(conf, attr) diff --git a/paste/server.py b/paste/server.py index c262108..7121b67 100755 --- a/paste/server.py +++ b/paste/server.py @@ -31,6 +31,7 @@ import sys import os from paste import reloader from paste import wsgilib +from paste import CONFIG # This way you can run this out of a checkout, and we'll fix up # the path... @@ -257,6 +258,9 @@ def make_app(conf): print 'Use publish_dir instead' print "You must provide publish_dir or publish_app" sys.exit(2) + # @@: we can't do this when we support making multiple applications + # with this function: + CONFIG.push_process_config(conf) return config_middleware(app, conf) def restart_with_reloader(conf): diff --git a/paste/util/threadinglocal.py b/paste/util/threadinglocal.py index 48559e8..2b17a0c 100644 --- a/paste/util/threadinglocal.py +++ b/paste/util/threadinglocal.py @@ -33,3 +33,6 @@ else: raise AttributeError( "No variable %s defined for thread %s" % (attr, g())) + +# All the Paste thread-local stuff goes in this dictionary: +local.wsgi = {} |
