summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--paste/__init__.py6
-rw-r--r--paste/configmiddleware.py26
-rw-r--r--paste/pyconfig.py83
-rwxr-xr-xpaste/server.py4
-rw-r--r--paste/util/threadinglocal.py3
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 = {}