summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2022-05-15 20:24:52 -0500
committerMichael Merickel <michael@merickel.org>2022-05-15 20:41:32 -0500
commit617bc4c2eba62701de64196e1199c93cc7277ce4 (patch)
tree435aa3d6456da966a81cac6febeace7021c3ec89 /src
parentee1d364dabd544fe4bf355b2c6578e4237b03992 (diff)
downloadpastedeploy-git-617bc4c2eba62701de64196e1199c93cc7277ce4.tar.gz
refactor to a src folder
Diffstat (limited to 'src')
-rw-r--r--src/paste/__init__.py18
-rw-r--r--src/paste/deploy/__init__.py3
-rw-r--r--src/paste/deploy/config.py305
-rw-r--r--src/paste/deploy/converters.py37
-rw-r--r--src/paste/deploy/loadwsgi.py713
-rw-r--r--src/paste/deploy/paster_templates.py34
-rw-r--r--src/paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl23
-rw-r--r--src/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl25
-rw-r--r--src/paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl22
-rw-r--r--src/paste/deploy/util.py71
10 files changed, 1251 insertions, 0 deletions
diff --git a/src/paste/__init__.py b/src/paste/__init__.py
new file mode 100644
index 0000000..cdb6121
--- /dev/null
+++ b/src/paste/__init__.py
@@ -0,0 +1,18 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+try:
+ import pkg_resources
+ pkg_resources.declare_namespace(__name__)
+except ImportError:
+ # don't prevent use of paste if pkg_resources isn't installed
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
+
+try:
+ import modulefinder
+except ImportError:
+ pass
+else:
+ for p in __path__:
+ modulefinder.AddPackagePath(__name__, p)
+
diff --git a/src/paste/deploy/__init__.py b/src/paste/deploy/__init__.py
new file mode 100644
index 0000000..94c63a8
--- /dev/null
+++ b/src/paste/deploy/__init__.py
@@ -0,0 +1,3 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+from paste.deploy.loadwsgi import *
diff --git a/src/paste/deploy/config.py b/src/paste/deploy/config.py
new file mode 100644
index 0000000..f448350
--- /dev/null
+++ b/src/paste/deploy/config.py
@@ -0,0 +1,305 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+"""Paste Configuration Middleware and Objects"""
+import threading
+import re
+
+# Loaded lazily
+wsgilib = None
+local = None
+
+__all__ = ['DispatchingConfig', 'CONFIG', 'ConfigMiddleware', 'PrefixMiddleware']
+
+
+def local_dict():
+ global config_local, local
+ try:
+ return config_local.wsgi_dict
+ except NameError:
+ config_local = threading.local()
+ config_local.wsgi_dict = result = {}
+ return result
+ except AttributeError:
+ config_local.wsgi_dict = result = {}
+ return result
+
+
+class DispatchingConfig:
+
+ """
+ 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.
+ """
+
+ # @@: What should happen when someone tries to add this
+ # configuration to itself? Probably the conf should become
+ # resolved, and get rid of this delegation wrapper
+
+ _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 self._local_key in local_dict():
+ break
+ self.dispatching_id += 1
+ finally:
+ self._constructor_lock.release()
+ 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_dict().setdefault(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_dict()[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_configs, conf)
+
+ def __getattr__(self, attr):
+ conf = self.current_conf()
+ if conf is None:
+ raise AttributeError(
+ "No configuration has been registered for this process "
+ "or thread")
+ return getattr(conf, attr)
+
+ def current_conf(self):
+ thread_configs = local_dict().get(self._local_key)
+ if thread_configs:
+ return thread_configs[-1]
+ elif self._process_configs:
+ return self._process_configs[-1]
+ else:
+ return None
+
+ def __getitem__(self, key):
+ # I thought __getattr__ would catch this, but apparently not
+ conf = self.current_conf()
+ if conf is None:
+ raise TypeError(
+ "No configuration has been registered for this process "
+ "or thread")
+ return conf[key]
+
+ def __contains__(self, key):
+ # I thought __getattr__ would catch this, but apparently not
+ return key in self
+
+ def __setitem__(self, key, value):
+ # I thought __getattr__ would catch this, but apparently not
+ conf = self.current_conf()
+ conf[key] = value
+
+CONFIG = DispatchingConfig()
+
+
+class ConfigMiddleware:
+
+ """
+ A WSGI middleware that adds a ``paste.config`` key to the request
+ environment, as well as registering the configuration temporarily
+ (for the length of the request) with ``paste.CONFIG``.
+ """
+
+ def __init__(self, application, config):
+ """
+ This delegates all requests to `application`, adding a *copy*
+ of the configuration `config`.
+ """
+ self.application = application
+ self.config = config
+
+ def __call__(self, environ, start_response):
+ global wsgilib
+ if wsgilib is None:
+ import pkg_resources
+ pkg_resources.require('Paste')
+ from paste import wsgilib
+ popped_config = None
+ if 'paste.config' in environ:
+ popped_config = environ['paste.config']
+ conf = environ['paste.config'] = self.config.copy()
+ app_iter = None
+ CONFIG.push_thread_config(conf)
+ try:
+ app_iter = self.application(environ, start_response)
+ finally:
+ if app_iter is None:
+ # An error occurred...
+ CONFIG.pop_thread_config(conf)
+ if popped_config is not None:
+ environ['paste.config'] = popped_config
+ 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)
+ if popped_config is not None:
+ environ['paste.config'] = popped_config
+ 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
+
+
+def make_config_filter(app, global_conf, **local_conf):
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ return ConfigMiddleware(app, conf)
+
+make_config_middleware = ConfigMiddleware.__doc__
+
+
+class PrefixMiddleware:
+ """Translate a given prefix into a SCRIPT_NAME for the filtered
+ application.
+
+ PrefixMiddleware provides a way to manually override the root prefix
+ (SCRIPT_NAME) of your application for certain, rare situations.
+
+ When running an application under a prefix (such as '/james') in
+ FastCGI/apache, the SCRIPT_NAME environment variable is automatically
+ set to to the appropriate value: '/james'. Pylons' URL generating
+ functions, such as url_for, always take the SCRIPT_NAME value into account.
+
+ One situation where PrefixMiddleware is required is when an application
+ is accessed via a reverse proxy with a prefix. The application is accessed
+ through the reverse proxy via the the URL prefix '/james', whereas the
+ reverse proxy forwards those requests to the application at the prefix '/'.
+
+ The reverse proxy, being an entirely separate web server, has no way of
+ specifying the SCRIPT_NAME variable; it must be manually set by a
+ PrefixMiddleware instance. Without setting SCRIPT_NAME, url_for will
+ generate URLs such as: '/purchase_orders/1', when it should be
+ generating: '/james/purchase_orders/1'.
+
+ To filter your application through a PrefixMiddleware instance, add the
+ following to the '[app:main]' section of your .ini file:
+
+ .. code-block:: ini
+
+ filter-with = proxy-prefix
+
+ [filter:proxy-prefix]
+ use = egg:PasteDeploy#prefix
+ prefix = /james
+
+ The name ``proxy-prefix`` simply acts as an identifier of the filter
+ section; feel free to rename it.
+
+ Also, unless disabled, the ``X-Forwarded-Server`` header will be
+ translated to the ``Host`` header, for cases when that header is
+ lost in the proxying. Also ``X-Forwarded-Host``,
+ ``X-Forwarded-Scheme``, and ``X-Forwarded-Proto`` are translated.
+
+ If ``force_port`` is set, SERVER_PORT and HTTP_HOST will be
+ rewritten with the given port. You can use a number, string (like
+ '80') or the empty string (whatever is the default port for the
+ scheme). This is useful in situations where there is port
+ forwarding going on, and the server believes itself to be on a
+ different port than what the outside world sees.
+
+ You can also use ``scheme`` to explicitly set the scheme (like
+ ``scheme = https``).
+ """
+ def __init__(self, app, global_conf=None, prefix='/',
+ translate_forwarded_server=True,
+ force_port=None, scheme=None):
+ self.app = app
+ self.prefix = prefix.rstrip('/')
+ self.translate_forwarded_server = translate_forwarded_server
+ self.regprefix = re.compile("^%s(.*)$" % self.prefix)
+ self.force_port = force_port
+ self.scheme = scheme
+
+ def __call__(self, environ, start_response):
+ url = environ['PATH_INFO']
+ url = re.sub(self.regprefix, r'\1', url)
+ if not url:
+ url = '/'
+ environ['PATH_INFO'] = url
+ environ['SCRIPT_NAME'] = self.prefix
+ if self.translate_forwarded_server:
+ if 'HTTP_X_FORWARDED_SERVER' in environ:
+ environ['SERVER_NAME'] = environ['HTTP_HOST'] = environ.pop('HTTP_X_FORWARDED_SERVER').split(',')[0]
+ if 'HTTP_X_FORWARDED_HOST' in environ:
+ environ['HTTP_HOST'] = environ.pop('HTTP_X_FORWARDED_HOST').split(',')[0]
+ if 'HTTP_X_FORWARDED_FOR' in environ:
+ environ['REMOTE_ADDR'] = environ.pop('HTTP_X_FORWARDED_FOR').split(',')[0]
+ if 'HTTP_X_FORWARDED_SCHEME' in environ:
+ environ['wsgi.url_scheme'] = environ.pop('HTTP_X_FORWARDED_SCHEME')
+ elif 'HTTP_X_FORWARDED_PROTO' in environ:
+ environ['wsgi.url_scheme'] = environ.pop('HTTP_X_FORWARDED_PROTO')
+ if self.force_port is not None:
+ host = environ.get('HTTP_HOST', '').split(':', 1)[0]
+ if self.force_port:
+ host = f'{host}:{self.force_port}'
+ environ['SERVER_PORT'] = str(self.force_port)
+ else:
+ if environ['wsgi.url_scheme'] == 'http':
+ port = '80'
+ else:
+ port = '443'
+ environ['SERVER_PORT'] = port
+ environ['HTTP_HOST'] = host
+ if self.scheme is not None:
+ environ['wsgi.url_scheme'] = self.scheme
+ return self.app(environ, start_response)
+
+
+def make_prefix_middleware(
+ app, global_conf, prefix='/',
+ translate_forwarded_server=True,
+ force_port=None, scheme=None):
+ from paste.deploy.converters import asbool
+ translate_forwarded_server = asbool(translate_forwarded_server)
+ return PrefixMiddleware(
+ app, prefix=prefix,
+ translate_forwarded_server=translate_forwarded_server,
+ force_port=force_port, scheme=scheme)
+
+make_prefix_middleware.__doc__ = PrefixMiddleware.__doc__
diff --git a/src/paste/deploy/converters.py b/src/paste/deploy/converters.py
new file mode 100644
index 0000000..30a3290
--- /dev/null
+++ b/src/paste/deploy/converters.py
@@ -0,0 +1,37 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+truthy = frozenset(['true', 'yes', 'on', 'y', 't', '1'])
+falsy = frozenset(['false', 'no', 'off', 'n', 'f', '0'])
+
+
+def asbool(obj):
+ if isinstance(obj, str):
+ obj = obj.strip().lower()
+ if obj in truthy:
+ return True
+ elif obj in falsy:
+ return False
+ else:
+ raise ValueError("String is not true/false: %r" % obj)
+ return bool(obj)
+
+
+def asint(obj):
+ try:
+ return int(obj)
+ except (TypeError, ValueError):
+ raise ValueError("Bad integer value: %r" % obj)
+
+
+def aslist(obj, sep=None, strip=True):
+ if isinstance(obj, str):
+ lst = obj.split(sep)
+ if strip:
+ lst = [v.strip() for v in lst]
+ return lst
+ elif isinstance(obj, (list, tuple)):
+ return obj
+ elif obj is None:
+ return []
+ else:
+ return [obj]
diff --git a/src/paste/deploy/loadwsgi.py b/src/paste/deploy/loadwsgi.py
new file mode 100644
index 0000000..c5471e5
--- /dev/null
+++ b/src/paste/deploy/loadwsgi.py
@@ -0,0 +1,713 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+from configparser import ConfigParser
+import os
+import pkg_resources
+import re
+import sys
+from urllib.parse import unquote
+
+
+from paste.deploy.util import fix_call, lookup_object
+
+__all__ = ['loadapp', 'loadserver', 'loadfilter', 'appconfig']
+
+
+############################################################
+## Utility functions
+############################################################
+
+
+def import_string(s):
+ ep = pkg_resources.EntryPoint.parse("x=" + s)
+ if hasattr(ep, 'resolve'):
+ # this is available on setuptools >= 10.2
+ return ep.resolve()
+ else:
+ # this causes a DeprecationWarning on setuptools >= 11.3
+ return ep.load(False)
+
+
+def _aslist(obj):
+ """
+ Turn object into a list; lists and tuples are left as-is, None
+ becomes [], and everything else turns into a one-element list.
+ """
+ if obj is None:
+ return []
+ elif isinstance(obj, (list, tuple)):
+ return obj
+ else:
+ return [obj]
+
+
+def _flatten(lst):
+ """
+ Flatten a nested list.
+ """
+ if not isinstance(lst, (list, tuple)):
+ return [lst]
+ result = []
+ for item in lst:
+ result.extend(_flatten(item))
+ return result
+
+
+class NicerConfigParser(ConfigParser):
+
+ def __init__(self, filename, *args, **kw):
+ ConfigParser.__init__(self, *args, **kw)
+ self.filename = filename
+ self._interpolation = self.InterpolateWrapper(self._interpolation)
+
+ def defaults(self):
+ """Return the defaults, with their values interpolated (with the
+ defaults dict itself)
+
+ Mainly to support defaults using values such as %(here)s
+ """
+ defaults = ConfigParser.defaults(self).copy()
+ for key, val in defaults.items():
+ defaults[key] = self.get('DEFAULT', key) or val
+ return defaults
+
+ class InterpolateWrapper:
+ def __init__(self, original):
+ self._original = original
+
+ def __getattr__(self, name):
+ return getattr(self._original, name)
+
+ def before_get(self, parser, section, option, value, defaults):
+ try:
+ return self._original.before_get(parser, section, option,
+ value, defaults)
+ except Exception:
+ e = sys.exc_info()[1]
+ args = list(e.args)
+ args[0] = f'Error in file {parser.filename}: {e}'
+ e.args = tuple(args)
+ e.message = args[0]
+ raise
+
+
+############################################################
+## Object types
+############################################################
+
+
+class _ObjectType:
+
+ name = None
+ egg_protocols = None
+ config_prefixes = None
+
+ def __init__(self):
+ # Normalize these variables:
+ self.egg_protocols = [_aslist(p) for p in _aslist(self.egg_protocols)]
+ self.config_prefixes = [_aslist(p) for p in _aslist(self.config_prefixes)]
+
+ def __repr__(self):
+ return '<{} protocols={!r} prefixes={!r}>'.format(
+ self.name, self.egg_protocols, self.config_prefixes)
+
+ def invoke(self, context):
+ assert context.protocol in _flatten(self.egg_protocols)
+ return fix_call(context.object,
+ context.global_conf, **context.local_conf)
+
+
+class _App(_ObjectType):
+
+ name = 'application'
+ egg_protocols = ['paste.app_factory', 'paste.composite_factory',
+ 'paste.composit_factory']
+ config_prefixes = [['app', 'application'], ['composite', 'composit'],
+ 'pipeline', 'filter-app']
+
+ def invoke(self, context):
+ if context.protocol in ('paste.composit_factory',
+ 'paste.composite_factory'):
+ return fix_call(context.object,
+ context.loader, context.global_conf,
+ **context.local_conf)
+ elif context.protocol == 'paste.app_factory':
+ return fix_call(context.object, context.global_conf, **context.local_conf)
+ else:
+ assert 0, "Protocol %r unknown" % context.protocol
+
+APP = _App()
+
+
+class _Filter(_ObjectType):
+ name = 'filter'
+ egg_protocols = [['paste.filter_factory', 'paste.filter_app_factory']]
+ config_prefixes = ['filter']
+
+ def invoke(self, context):
+ if context.protocol == 'paste.filter_factory':
+ return fix_call(context.object,
+ context.global_conf, **context.local_conf)
+ elif context.protocol == 'paste.filter_app_factory':
+ def filter_wrapper(wsgi_app):
+ # This should be an object, so it has a nicer __repr__
+ return fix_call(context.object,
+ wsgi_app, context.global_conf,
+ **context.local_conf)
+ return filter_wrapper
+ else:
+ assert 0, "Protocol %r unknown" % context.protocol
+
+FILTER = _Filter()
+
+
+class _Server(_ObjectType):
+ name = 'server'
+ egg_protocols = [['paste.server_factory', 'paste.server_runner']]
+ config_prefixes = ['server']
+
+ def invoke(self, context):
+ if context.protocol == 'paste.server_factory':
+ return fix_call(context.object,
+ context.global_conf, **context.local_conf)
+ elif context.protocol == 'paste.server_runner':
+ def server_wrapper(wsgi_app):
+ # This should be an object, so it has a nicer __repr__
+ return fix_call(context.object,
+ wsgi_app, context.global_conf,
+ **context.local_conf)
+ return server_wrapper
+ else:
+ assert 0, "Protocol %r unknown" % context.protocol
+
+SERVER = _Server()
+
+
+# Virtual type: (@@: There's clearly something crufty here;
+# this probably could be more elegant)
+class _PipeLine(_ObjectType):
+ name = 'pipeline'
+
+ def invoke(self, context):
+ app = context.app_context.create()
+ filters = [c.create() for c in context.filter_contexts]
+ filters.reverse()
+ for filter in filters:
+ app = filter(app)
+ return app
+
+PIPELINE = _PipeLine()
+
+
+class _FilterApp(_ObjectType):
+ name = 'filter_app'
+
+ def invoke(self, context):
+ next_app = context.next_context.create()
+ filter = context.filter_context.create()
+ return filter(next_app)
+
+FILTER_APP = _FilterApp()
+
+
+class _FilterWith(_App):
+ name = 'filtered_with'
+
+ def invoke(self, context):
+ filter = context.filter_context.create()
+ filtered = context.next_context.create()
+ if context.next_context.object_type is APP:
+ return filter(filtered)
+ else:
+ # filtering a filter
+ def composed(app):
+ return filter(filtered(app))
+ return composed
+
+FILTER_WITH = _FilterWith()
+
+
+############################################################
+## Loaders
+############################################################
+
+
+def loadapp(uri, name=None, **kw):
+ return loadobj(APP, uri, name=name, **kw)
+
+
+def loadfilter(uri, name=None, **kw):
+ return loadobj(FILTER, uri, name=name, **kw)
+
+
+def loadserver(uri, name=None, **kw):
+ return loadobj(SERVER, uri, name=name, **kw)
+
+
+def appconfig(uri, name=None, relative_to=None, global_conf=None):
+ context = loadcontext(APP, uri, name=name,
+ relative_to=relative_to,
+ global_conf=global_conf)
+ return context.config()
+
+_loaders = {}
+
+
+def loadobj(object_type, uri, name=None, relative_to=None,
+ global_conf=None):
+ context = loadcontext(
+ object_type, uri, name=name, relative_to=relative_to,
+ global_conf=global_conf)
+ return context.create()
+
+
+def loadcontext(object_type, uri, name=None, relative_to=None,
+ global_conf=None):
+ if '#' in uri:
+ if name is None:
+ uri, name = uri.split('#', 1)
+ else:
+ # @@: Ignore fragment or error?
+ uri = uri.split('#', 1)[0]
+ if name is None:
+ name = 'main'
+ if ':' not in uri:
+ raise LookupError("URI has no scheme: %r" % uri)
+ scheme, path = uri.split(':', 1)
+ scheme = scheme.lower()
+ if scheme not in _loaders:
+ raise LookupError(
+ "URI scheme not known: %r (from %s)"
+ % (scheme, ', '.join(_loaders.keys())))
+ return _loaders[scheme](
+ object_type,
+ uri, path, name=name, relative_to=relative_to,
+ global_conf=global_conf)
+
+
+def _loadconfig(object_type, uri, path, name, relative_to,
+ global_conf):
+ isabs = os.path.isabs(path)
+ # De-Windowsify the paths:
+ path = path.replace('\\', '/')
+ if not isabs:
+ if not relative_to:
+ raise ValueError(
+ "Cannot resolve relative uri %r; no relative_to keyword "
+ "argument given" % uri)
+ relative_to = relative_to.replace('\\', '/')
+ if relative_to.endswith('/'):
+ path = relative_to + path
+ else:
+ path = relative_to + '/' + path
+ if path.startswith('///'):
+ path = path[2:]
+ path = unquote(path)
+ loader = ConfigLoader(path)
+ if global_conf:
+ loader.update_defaults(global_conf, overwrite=False)
+ return loader.get_context(object_type, name, global_conf)
+
+_loaders['config'] = _loadconfig
+
+
+def _loadegg(object_type, uri, spec, name, relative_to,
+ global_conf):
+ loader = EggLoader(spec)
+ return loader.get_context(object_type, name, global_conf)
+
+_loaders['egg'] = _loadegg
+
+
+def _loadfunc(object_type, uri, spec, name, relative_to,
+ global_conf):
+
+ loader = FuncLoader(spec)
+ return loader.get_context(object_type, name, global_conf)
+
+_loaders['call'] = _loadfunc
+
+############################################################
+## Loaders
+############################################################
+
+
+class _Loader:
+
+ def get_app(self, name=None, global_conf=None):
+ return self.app_context(
+ name=name, global_conf=global_conf).create()
+
+ def get_filter(self, name=None, global_conf=None):
+ return self.filter_context(
+ name=name, global_conf=global_conf).create()
+
+ def get_server(self, name=None, global_conf=None):
+ return self.server_context(
+ name=name, global_conf=global_conf).create()
+
+ def app_context(self, name=None, global_conf=None):
+ return self.get_context(
+ APP, name=name, global_conf=global_conf)
+
+ def filter_context(self, name=None, global_conf=None):
+ return self.get_context(
+ FILTER, name=name, global_conf=global_conf)
+
+ def server_context(self, name=None, global_conf=None):
+ return self.get_context(
+ SERVER, name=name, global_conf=global_conf)
+
+ _absolute_re = re.compile(r'^[a-zA-Z]+:')
+
+ def absolute_name(self, name):
+ """
+ Returns true if the name includes a scheme
+ """
+ if name is None:
+ return False
+ return self._absolute_re.search(name)
+
+
+class ConfigLoader(_Loader):
+
+ def __init__(self, filename):
+ self.filename = filename = filename.strip()
+ defaults = {
+ 'here': os.path.dirname(os.path.abspath(filename)),
+ '__file__': os.path.abspath(filename)
+ }
+ self.parser = NicerConfigParser(filename, defaults=defaults)
+ self.parser.optionxform = str # Don't lower-case keys
+ with open(filename) as f:
+ self.parser.read_file(f)
+
+ def update_defaults(self, new_defaults, overwrite=True):
+ for key, value in new_defaults.items():
+ if not overwrite and key in self.parser._defaults:
+ continue
+ self.parser._defaults[key] = value
+
+ def get_context(self, object_type, name=None, global_conf=None):
+ if self.absolute_name(name):
+ return loadcontext(object_type, name,
+ relative_to=os.path.dirname(self.filename),
+ global_conf=global_conf)
+ section = self.find_config_section(
+ object_type, name=name)
+ defaults = self.parser.defaults()
+ _global_conf = defaults.copy()
+ if global_conf is not None:
+ _global_conf.update(global_conf)
+ global_conf = _global_conf
+ local_conf = {}
+ global_additions = {}
+ get_from_globals = {}
+ for option in self.parser.options(section):
+ if option.startswith('set '):
+ name = option[4:].strip()
+ global_additions[name] = global_conf[name] = (
+ self.parser.get(section, option))
+ elif option.startswith('get '):
+ name = option[4:].strip()
+ get_from_globals[name] = self.parser.get(section, option)
+ else:
+ if option in defaults:
+ # @@: It's a global option (?), so skip it
+ continue
+ local_conf[option] = self.parser.get(section, option)
+ for local_var, glob_var in get_from_globals.items():
+ local_conf[local_var] = global_conf[glob_var]
+ if object_type in (APP, FILTER) and 'filter-with' in local_conf:
+ filter_with = local_conf.pop('filter-with')
+ else:
+ filter_with = None
+ if 'require' in local_conf:
+ for spec in local_conf['require'].split():
+ pkg_resources.require(spec)
+ del local_conf['require']
+ if section.startswith('filter-app:'):
+ context = self._filter_app_context(
+ object_type, section, name=name,
+ global_conf=global_conf, local_conf=local_conf,
+ global_additions=global_additions)
+ elif section.startswith('pipeline:'):
+ context = self._pipeline_app_context(
+ object_type, section, name=name,
+ global_conf=global_conf, local_conf=local_conf,
+ global_additions=global_additions)
+ elif 'use' in local_conf:
+ context = self._context_from_use(
+ object_type, local_conf, global_conf, global_additions,
+ section)
+ else:
+ context = self._context_from_explicit(
+ object_type, local_conf, global_conf, global_additions,
+ section)
+ if filter_with is not None:
+ filter_with_context = LoaderContext(
+ obj=None,
+ object_type=FILTER_WITH,
+ protocol=None,
+ global_conf=global_conf, local_conf=local_conf,
+ loader=self)
+ filter_with_context.filter_context = self.filter_context(
+ name=filter_with, global_conf=global_conf)
+ filter_with_context.next_context = context
+ return filter_with_context
+ return context
+
+ def _context_from_use(self, object_type, local_conf, global_conf,
+ global_additions, section):
+ use = local_conf.pop('use')
+ context = self.get_context(
+ object_type, name=use, global_conf=global_conf)
+ context.global_conf.update(global_additions)
+ context.local_conf.update(local_conf)
+ if '__file__' in global_conf:
+ # use sections shouldn't overwrite the original __file__
+ context.global_conf['__file__'] = global_conf['__file__']
+ # @@: Should loader be overwritten?
+ context.loader = self
+
+ if context.protocol is None:
+ # Determine protocol from section type
+ section_protocol = section.split(':', 1)[0]
+ if section_protocol in ('application', 'app'):
+ context.protocol = 'paste.app_factory'
+ elif section_protocol in ('composit', 'composite'):
+ context.protocol = 'paste.composit_factory'
+ else:
+ # This will work with 'server' and 'filter', otherwise it
+ # could fail but there is an error message already for
+ # bad protocols
+ context.protocol = 'paste.%s_factory' % section_protocol
+
+ return context
+
+ def _context_from_explicit(self, object_type, local_conf, global_conf,
+ global_addition, section):
+ possible = []
+ for protocol_options in object_type.egg_protocols:
+ for protocol in protocol_options:
+ if protocol in local_conf:
+ possible.append((protocol, local_conf[protocol]))
+ break
+ if len(possible) > 1:
+ raise LookupError(
+ "Multiple protocols given in section %r: %s"
+ % (section, possible))
+ if not possible:
+ raise LookupError(
+ "No loader given in section %r" % section)
+ found_protocol, found_expr = possible[0]
+ del local_conf[found_protocol]
+ value = import_string(found_expr)
+ context = LoaderContext(
+ value, object_type, found_protocol,
+ global_conf, local_conf, self)
+ return context
+
+ def _filter_app_context(self, object_type, section, name,
+ global_conf, local_conf, global_additions):
+ if 'next' not in local_conf:
+ raise LookupError(
+ "The [%s] section in %s is missing a 'next' setting"
+ % (section, self.filename))
+ next_name = local_conf.pop('next')
+ context = LoaderContext(None, FILTER_APP, None, global_conf,
+ local_conf, self)
+ context.next_context = self.get_context(
+ APP, next_name, global_conf)
+ if 'use' in local_conf:
+ context.filter_context = self._context_from_use(
+ FILTER, local_conf, global_conf, global_additions,
+ section)
+ else:
+ context.filter_context = self._context_from_explicit(
+ FILTER, local_conf, global_conf, global_additions,
+ section)
+ return context
+
+ def _pipeline_app_context(self, object_type, section, name,
+ global_conf, local_conf, global_additions):
+ if 'pipeline' not in local_conf:
+ raise LookupError(
+ "The [%s] section in %s is missing a 'pipeline' setting"
+ % (section, self.filename))
+ pipeline = local_conf.pop('pipeline').split()
+ if local_conf:
+ raise LookupError(
+ "The [%s] pipeline section in %s has extra "
+ "(disallowed) settings: %s"
+ % (section, self.filename, ', '.join(local_conf.keys())))
+ context = LoaderContext(None, PIPELINE, None, global_conf,
+ local_conf, self)
+ context.app_context = self.get_context(
+ APP, pipeline[-1], global_conf)
+ context.filter_contexts = [
+ self.get_context(FILTER, name, global_conf)
+ for name in pipeline[:-1]]
+ return context
+
+ def find_config_section(self, object_type, name=None):
+ """
+ Return the section name with the given name prefix (following the
+ same pattern as ``protocol_desc`` in ``config``. It must have the
+ given name, or for ``'main'`` an empty name is allowed. The
+ prefix must be followed by a ``:``.
+
+ Case is *not* ignored.
+ """
+ possible = []
+ for name_options in object_type.config_prefixes:
+ for name_prefix in name_options:
+ found = self._find_sections(
+ self.parser.sections(), name_prefix, name)
+ if found:
+ possible.extend(found)
+ break
+ if not possible:
+ raise LookupError(
+ "No section %r (prefixed by %s) found in config %s"
+ % (name,
+ ' or '.join(map(repr, _flatten(object_type.config_prefixes))),
+ self.filename))
+ if len(possible) > 1:
+ raise LookupError(
+ "Ambiguous section names %r for section %r (prefixed by %s) "
+ "found in config %s"
+ % (possible, name,
+ ' or '.join(map(repr, _flatten(object_type.config_prefixes))),
+ self.filename))
+ return possible[0]
+
+ def _find_sections(self, sections, name_prefix, name):
+ found = []
+ if name is None:
+ if name_prefix in sections:
+ found.append(name_prefix)
+ name = 'main'
+ for section in sections:
+ if section.startswith(name_prefix + ':'):
+ if section[len(name_prefix) + 1:].strip() == name:
+ found.append(section)
+ return found
+
+
+class EggLoader(_Loader):
+
+ def __init__(self, spec):
+ self.spec = spec
+
+ def get_context(self, object_type, name=None, global_conf=None):
+ if self.absolute_name(name):
+ return loadcontext(object_type, name,
+ global_conf=global_conf)
+ entry_point, protocol, ep_name = self.find_egg_entry_point(
+ object_type, name=name)
+ return LoaderContext(
+ entry_point,
+ object_type,
+ protocol,
+ global_conf or {}, {},
+ self,
+ distribution=pkg_resources.get_distribution(self.spec),
+ entry_point_name=ep_name)
+
+ def find_egg_entry_point(self, object_type, name=None):
+ """
+ Returns the (entry_point, protocol) for the with the given
+ ``name``.
+ """
+ if name is None:
+ name = 'main'
+ possible = []
+ for protocol_options in object_type.egg_protocols:
+ for protocol in protocol_options:
+ pkg_resources.require(self.spec)
+ entry = pkg_resources.get_entry_info(
+ self.spec,
+ protocol,
+ name)
+ if entry is not None:
+ possible.append((entry.load(), protocol, entry.name))
+ break
+ if not possible:
+ # Better exception
+ dist = pkg_resources.get_distribution(self.spec)
+ raise LookupError(
+ "Entry point %r not found in egg %r (dir: %s; protocols: %s; "
+ "entry_points: %s)"
+ % (name, self.spec,
+ dist.location,
+ ', '.join(_flatten(object_type.egg_protocols)),
+ ', '.join(_flatten([
+ list((pkg_resources.get_entry_info(self.spec, prot, name) or {}).keys())
+ for prot in protocol_options] or '(no entry points)'))))
+ if len(possible) > 1:
+ raise LookupError(
+ "Ambiguous entry points for %r in egg %r (protocols: %s)"
+ % (name, self.spec, ', '.join(_flatten(protocol_options))))
+ return possible[0]
+
+
+class FuncLoader(_Loader):
+ """ Loader that supports specifying functions inside modules, without
+ using eggs at all. Configuration should be in the format:
+ use = call:my.module.path:function_name
+
+ Dot notation is supported in both the module and function name, e.g.:
+ use = call:my.module.path:object.method
+ """
+ def __init__(self, spec):
+ self.spec = spec
+ if not ':' in spec:
+ raise LookupError("Configuration not in format module:function")
+
+ def get_context(self, object_type, name=None, global_conf=None):
+ obj = lookup_object(self.spec)
+ return LoaderContext(
+ obj,
+ object_type,
+ None, # determine protocol from section type
+ global_conf or {},
+ {},
+ self,
+ )
+
+
+class LoaderContext:
+
+ def __init__(self, obj, object_type, protocol,
+ global_conf, local_conf, loader,
+ distribution=None, entry_point_name=None):
+ self.object = obj
+ self.object_type = object_type
+ self.protocol = protocol
+ #assert protocol in _flatten(object_type.egg_protocols), (
+ # "Bad protocol %r; should be one of %s"
+ # % (protocol, ', '.join(map(repr, _flatten(object_type.egg_protocols)))))
+ self.global_conf = global_conf
+ self.local_conf = local_conf
+ self.loader = loader
+ self.distribution = distribution
+ self.entry_point_name = entry_point_name
+
+ def create(self):
+ return self.object_type.invoke(self)
+
+ def config(self):
+ conf = AttrDict(self.global_conf)
+ conf.update(self.local_conf)
+ conf.local_conf = self.local_conf
+ conf.global_conf = self.global_conf
+ conf.context = self
+ return conf
+
+
+class AttrDict(dict):
+ """
+ A dictionary that can be assigned to.
+ """
+ pass
diff --git a/src/paste/deploy/paster_templates.py b/src/paste/deploy/paster_templates.py
new file mode 100644
index 0000000..edfa97a
--- /dev/null
+++ b/src/paste/deploy/paster_templates.py
@@ -0,0 +1,34 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+import os
+
+from paste.script.templates import Template
+
+
+class PasteDeploy(Template):
+
+ _template_dir = 'paster_templates/paste_deploy'
+ summary = "A web application deployed through paste.deploy"
+
+ egg_plugins = ['PasteDeploy']
+
+ required_templates = ['PasteScript#basic_package']
+
+ def post(self, command, output_dir, vars):
+ for prereq in ['PasteDeploy']:
+ command.insert_into_file(
+ os.path.join(output_dir, 'setup.py'),
+ 'Extra requirements',
+ '%r,\n' % prereq,
+ indent=True)
+ command.insert_into_file(
+ os.path.join(output_dir, 'setup.py'),
+ 'Entry points',
+ (' [paste.app_factory]\n'
+ ' main = %(package)s.wsgiapp:make_app\n') % vars,
+ indent=False)
+ if command.verbose:
+ print('*' * 72)
+ print('* Run "paster serve docs/devel_config.ini" to run the sample application')
+ print('* on http://localhost:8080')
+ print('*' * 72)
diff --git a/src/paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl b/src/paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl
new file mode 100644
index 0000000..5514cfc
--- /dev/null
+++ b/src/paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl
@@ -0,0 +1,23 @@
+import cgi
+
+from paste.deploy.config import CONFIG
+
+
+def application(environ, start_response):
+ # Note that usually you wouldn't be writing a pure WSGI
+ # application, you might be using some framework or
+ # environment. But as an example...
+ start_response('200 OK', [('Content-type', 'text/html')])
+ greeting = CONFIG['greeting']
+ content = [
+ b'<html><head><title>%s</title></head>\n' % greeting.encode('utf-8'),
+ b'<body><h1>%s!</h1>\n' % greeting.encode('utf-8'),
+ b'<table border=1>\n',
+ ]
+ items = environ.items()
+ items = sorted(items)
+ for key, value in items:
+ content.append(b'<tr><td>%s</td><td>%s</td></tr>\n'
+ % (key.encode('utf-8'), cgi.escape(repr(value)).encode('utf-8')))
+ content.append(b'</table></body></html>')
+ return content
diff --git a/src/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl b/src/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl
new file mode 100644
index 0000000..5684c31
--- /dev/null
+++ b/src/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl
@@ -0,0 +1,25 @@
+from __future__ import absolute_import
+from paste.deploy.config import ConfigMiddleware
+
+from . import sampleapp
+
+
+def make_app(
+ global_conf,
+ # Optional and required configuration parameters
+ # can go here, or just **kw; greeting is required:
+ greeting,
+ **kw):
+ # This is a WSGI application:
+ app = sampleapp.application
+ # Here we merge all the keys into one configuration
+ # dictionary; you don't have to do this, but this
+ # can be convenient later to add ad hoc configuration:
+ conf = global_conf.copy()
+ conf.update(kw)
+ conf['greeting'] = greeting
+ # ConfigMiddleware means that paste.deploy.CONFIG will,
+ # during this request (threadsafe) represent the
+ # configuration dictionary we set up:
+ app = ConfigMiddleware(app, conf)
+ return app
diff --git a/src/paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl b/src/paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl
new file mode 100644
index 0000000..0c0ae35
--- /dev/null
+++ b/src/paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl
@@ -0,0 +1,22 @@
+[filter-app:main]
+# This puts the interactive debugger in place:
+use = egg:Paste#evalerror
+next = devel
+
+[app:devel]
+# This application is meant for interactive development
+use = egg:${project}
+debug = true
+# You can add other configuration values:
+greeting = Aloha!
+
+[app:test]
+# While this version of the configuration is for non-iteractive
+# tests (unit tests)
+use = devel
+
+[server:main]
+use = egg:Paste#http
+# Change to 0.0.0.0 to make public:
+host = 127.0.0.1
+port = 8080
diff --git a/src/paste/deploy/util.py b/src/paste/deploy/util.py
new file mode 100644
index 0000000..d30466a
--- /dev/null
+++ b/src/paste/deploy/util.py
@@ -0,0 +1,71 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+import inspect
+import sys
+
+
+def fix_type_error(exc_info, callable, varargs, kwargs):
+ """
+ Given an exception, this will test if the exception was due to a
+ signature error, and annotate the error with better information if
+ so.
+
+ Usage::
+
+ try:
+ val = callable(*args, **kw)
+ except TypeError:
+ exc_info = fix_type_error(None, callable, args, kw)
+ raise exc_info[0], exc_info[1], exc_info[2]
+ """
+ if exc_info is None:
+ exc_info = sys.exc_info()
+ if (exc_info[0] != TypeError
+ or str(exc_info[1]).find('arguments') == -1
+ or getattr(exc_info[1], '_type_error_fixed', False)):
+ return exc_info
+ exc_info[1]._type_error_fixed = True
+ argspec = inspect.formatargspec(*inspect.getargspec(callable))
+ args = ', '.join(map(_short_repr, varargs))
+ if kwargs and args:
+ args += ', '
+ if kwargs:
+ kwargs = sorted(kwargs.items())
+ args += ', '.join(['%s=...' % n for n, v in kwargs])
+ gotspec = '(%s)' % args
+ msg = f'{exc_info[1]}; got {gotspec}, wanted {argspec}'
+ exc_info[1].args = (msg,)
+ return exc_info
+
+
+def _short_repr(v):
+ v = repr(v)
+ if len(v) > 12:
+ v = v[:8] + '...' + v[-4:]
+ return v
+
+
+def fix_call(callable, *args, **kw):
+ """
+ Call ``callable(*args, **kw)`` fixing any type errors that come out.
+ """
+ try:
+ val = callable(*args, **kw)
+ except TypeError:
+ exc_info = fix_type_error(None, callable, args, kw)
+ raise exc_info[1] from None
+ return val
+
+
+def lookup_object(spec):
+ """
+ Looks up a module or object from a some.module:func_name specification.
+ To just look up a module, omit the colon and everything after it.
+ """
+ parts, target = spec.split(':') if ':' in spec else (spec, None)
+ module = __import__(parts)
+
+ for part in parts.split('.')[1:] + ([target] if target else []):
+ module = getattr(module, part)
+
+ return module