summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhao huang <huang1hao@gmail.com>2015-03-28 08:10:39 +0000
committerhao huang <huang1hao@gmail.com>2015-03-28 08:10:39 +0000
commitcf7e560f89016a302342821f5071941a09c04283 (patch)
treef8b50954525cf4c063ac09597f9ac576bf92b16e
downloadpastedeploy-git-huang1hao/fix-typo-in-docsindextxt-1427530235567.tar.gz
-rw-r--r--.hgignore13
-rw-r--r--MANIFEST.in2
-rw-r--r--README11
-rw-r--r--docs/_static/paste.css15
-rw-r--r--docs/_templates/layout.html20
-rw-r--r--docs/conf.py132
-rw-r--r--docs/index.txt672
-rw-r--r--docs/license.txt20
-rw-r--r--docs/modules/config.txt13
-rw-r--r--docs/modules/converters.txt11
-rw-r--r--docs/modules/loadwsgi.txt13
-rw-r--r--docs/news.txt179
-rw-r--r--paste/__init__.py18
-rw-r--r--paste/deploy/__init__.py3
-rw-r--r--paste/deploy/compat.py32
-rw-r--r--paste/deploy/config.py305
-rw-r--r--paste/deploy/converters.py40
-rw-r--r--paste/deploy/loadwsgi.py725
-rw-r--r--paste/deploy/paster_templates.py36
-rw-r--r--paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl23
-rw-r--r--paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl24
-rw-r--r--paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl22
-rw-r--r--paste/deploy/util.py73
-rwxr-xr-xregen-docs9
-rw-r--r--setup.cfg2
-rw-r--r--setup.py59
-rw-r--r--tests/__init__.py13
-rw-r--r--tests/fake_packages/FakeApp.egg/FakeApp.egg-info/PKG-INFO10
-rw-r--r--tests/fake_packages/FakeApp.egg/FakeApp.egg-info/entry_points.txt22
-rw-r--r--tests/fake_packages/FakeApp.egg/FakeApp.egg-info/top_level.txt1
-rw-r--r--tests/fake_packages/FakeApp.egg/fakeapp/__init__.py1
-rw-r--r--tests/fake_packages/FakeApp.egg/fakeapp/apps.py69
-rw-r--r--tests/fake_packages/FakeApp.egg/fakeapp/configapps.py14
-rw-r--r--tests/fake_packages/FakeApp.egg/setup.py23
-rw-r--r--tests/fixture.py20
-rw-r--r--tests/sample_configs/basic_app.ini14
-rwxr-xr-xtests/sample_configs/executable.ini10
-rw-r--r--tests/sample_configs/test_config.ini38
-rw-r--r--tests/sample_configs/test_config_included.ini10
-rw-r--r--tests/sample_configs/test_error.ini8
-rw-r--r--tests/sample_configs/test_filter.ini22
-rw-r--r--tests/sample_configs/test_filter_with.ini12
-rw-r--r--tests/sample_configs/test_func.ini13
-rw-r--r--tests/test_basic_app.py36
-rw-r--r--tests/test_config.py173
-rw-r--r--tests/test_config_middleware.py28
-rw-r--r--tests/test_converters.py17
-rw-r--r--tests/test_filter.py53
-rw-r--r--tests/test_load_package.py12
-rw-r--r--tox.ini14
50 files changed, 3105 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..44abbb2
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,13 @@
+syntax: glob
+*.egg-info/
+*.egg/
+*.pyc
+*.class
+dist/
+build/
+docs/_build/
+.tox
+.project
+.pydevproject
+.settings
+__pycache__
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..7a2ffb9
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include docs/*.txt
+recursive-include paste/deploy/paster_templates *
diff --git a/README b/README
new file mode 100644
index 0000000..291a026
--- /dev/null
+++ b/README
@@ -0,0 +1,11 @@
+This tool provides code to load WSGI applications and servers from
+URIs; these URIs can refer to Python Eggs for INI-style configuration
+files. `Paste Script <http://pythonpaste.org/script>`_ provides
+commands to serve applications based on this configuration file.
+
+The latest version is available in a `Mercurial repository
+<http://bitbucket.org/ianb/pastedeploy>`_ (or a `tarball
+<http://bitbucket.org/ianb/pastedeploy/get/tip.gz#egg=PasteDeploy-dev>`_).
+
+For the latest changes see the `news file
+<http://pythonpaste.org/deploy/news.html>`_. \ No newline at end of file
diff --git a/docs/_static/paste.css b/docs/_static/paste.css
new file mode 100644
index 0000000..6705e5d
--- /dev/null
+++ b/docs/_static/paste.css
@@ -0,0 +1,15 @@
+a.invisible-link {
+ color: #fff;
+ text-decoration: none;
+}
+
+a.invisible-link:visited {
+ color: #fff;
+ text-decoration: none;
+}
+
+a.invisible:link {
+ color: #fff;
+ text-decoration: none;
+}
+
diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html
new file mode 100644
index 0000000..a6d2a97
--- /dev/null
+++ b/docs/_templates/layout.html
@@ -0,0 +1,20 @@
+{% extends "!layout.html" %}
+
+{% block extrahead %}
+{{ super() }}
+<link rel="stylesheet" type="text/css"
+ href="{{ pathto('_static/paste.css') }}">
+{% endblock %}
+
+{% block sidebartoc %}
+<h3><a href="http://pythonpaste.org/" class="invisible-link">Python Paste</a></h3>
+
+<ul>
+<li><a href="http://trac.pythonpaste.org">Issue tracker</a></li>
+<li><a href="http://pythonpaste.org/">Paste core</a></li>
+<li><a href="http://pythonpaste.org/deploy/">Paste Deploy</a></li>
+<li><a href="http://pythonpaste.org/script/">Paste Script</a></li>
+</ul>
+
+{{ super() }}
+{% endblock %}
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..af16cab
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+#
+# Paste documentation build configuration file, created by
+# sphinx-quickstart on Tue Apr 22 22:08:49 2008.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# All configuration values have a default value; values that are commented out
+# serve to show the default value.
+
+import sys
+
+# If your extensions are in another directory, add it here.
+#sys.path.append('some/directory')
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.txt'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General substitutions.
+project = 'Paste Deploy'
+copyright = '2011, Ian Bicking and contributors'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '1.5'
+# The full version, including alpha/beta/rc tags.
+release = '1.5.2'
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = ['include/contact.txt', 'include/reference_header.txt']
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Content template for the index page.
+#html_index = ''
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'PasteDeploydoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+#latex_documents = []
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/docs/index.txt b/docs/index.txt
new file mode 100644
index 0000000..c085d27
--- /dev/null
+++ b/docs/index.txt
@@ -0,0 +1,672 @@
+Paste Deployment
+================
+
+:author: Ian Bicking <ianb@colorstudy.com>
+
+.. contents::
+
+Documents:
+
+.. toctree::
+ :maxdepth: 1
+
+ news
+ modules/loadwsgi
+ modules/config
+ modules/converters
+ license
+
+.. comment:
+ The names used in sections should be more concrete, and it should
+ be clearer that they are just arbitrary names.
+
+Introduction
+------------
+
+Paste Deployment is a system for finding and configuring WSGI
+applications and servers. For WSGI application consumers it provides
+a single, simple function (``loadapp``) for loading a WSGI application
+from a configuration file or a Python Egg. For WSGI application
+providers it only asks for a single, simple entry point to your
+application, so that application users don't need to be exposed to the
+implementation details of your application.
+
+The result is something a system administrator can install and manage
+without knowing any Python, or the details of the WSGI application or
+its container.
+
+Paste Deployment currently does not require other parts of `Paste
+<http://pythonpaste.org>`_, and is distributed as a separate package.
+
+To see updates that have been made to Paste Deploy see the `news file
+<news.html>`_.
+
+Paste Deploy is released under the `MIT license
+<http://www.opensource.org/licenses/mit-license.php>`_.
+
+Status
+------
+
+Paste Deploy has passed version 1.0. Paste Deploy is an actively
+maintained project. As of 1.0, we'll make a strong effort to maintain
+backward compatibility (this actually started happening long before
+1.0, but now it is explicit). This will include deprecation warnings
+when necessary. Major changes will take place under new functions or
+with new entry points.
+
+Note that the most key aspect of Paste Deploy is the entry points it
+defines (such as ``paste.app_factory``). Paste Deploy is not the only
+consumer of these entry points, and many extensions can best take
+place by utilizing the entry points instead of using Paste Deploy
+directly. The entry points will not change; if changes are necessary,
+new entry points will be defined.
+
+Installation
+------------
+
+First make sure you have either
+`setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ or its
+modern replacement
+`distribute <http://pypi.python.org/pypi/distribute>`_ installed.
+For Python 3.x you need distribute as setuptools does not work on it.
+
+Then you can install Paste Deployment using `pip
+<http://www.pip-installer.org/en/latest/installing.html>`_ by running::
+
+ $ sudo pip install PasteDeploy
+
+If you want to track development, do::
+
+ $ hg clone http://bitbucket.org/ianb/pastedeploy
+ $ cd pastedeploy
+ $ sudo python setup.py develop
+
+This will install the package globally, but will load the files in the
+checkout. You can also simply install ``PasteDeploy==dev``.
+
+For downloads and other information see the `Cheese Shop PasteDeploy
+page <http://cheeseshop.python.org/pypi/PasteDeploy>`_.
+
+A complimentary package is `Paste Script </script/>`_. To install
+that, use ``pip install PasteScript`` (or ``pip install
+PasteScript==dev``).
+
+From the User Perspective
+-------------------------
+
+In the following sections, the Python API for using Paste Deploy is
+given. This isn't what users will be using (but it is useful for
+Python developers and useful for setting up tests fixtures).
+
+The primary interaction with Paste Deploy is through its configuration
+files. The primary thing you want to do with a configuration file is
+serve it. To learn about serving configuration files, see `the
+``paster serve`` command
+<http://pythonpaste.org/script/#paster-serve>`_.
+
+The Config File
+~~~~~~~~~~~~~~~
+
+A config file has different sections. The only sections Paste Deploy
+cares about have prefixes, like ``app:main`` or ``filter:errors`` --
+the part after the ``:`` is the "name" of the section, and the part
+before gives the "type". Other sections are ignored.
+
+The format is a simple `INI format
+<http://en.wikipedia.org/wiki/INI_file>`_: ``name = value``. You can
+extend the value by indenting subsequent lines. ``#`` is a comment.
+
+Typically you have one or two sections, named "main": an application
+section (``[app:main]``) and a server section (``[server:main]``).
+``[composite:...]`` signifies something that dispatches to multiple
+applications (example below).
+
+Here's a typical configuration file that also shows off mounting
+multiple applications using `paste.urlmap
+<http://pythonpaste.org/module-paste.urlmap.html>`_::
+
+ [composite:main]
+ use = egg:Paste#urlmap
+ / = home
+ /blog = blog
+ /wiki = wiki
+ /cms = config:cms.ini
+
+ [app:home]
+ use = egg:Paste#static
+ document_root = %(here)s/htdocs
+
+ [filter-app:blog]
+ use = egg:Authentication#auth
+ next = blogapp
+ roles = admin
+ htpasswd = /home/me/users.htpasswd
+
+ [app:blogapp]
+ use = egg:BlogApp
+ database = sqlite:/home/me/blog.db
+
+ [app:wiki]
+ use = call:mywiki.main:application
+ database = sqlite:/home/me/wiki.db
+
+I'll explain each section in detail now::
+
+ [composite:main]
+ use = egg:Paste#urlmap
+ / = home
+ /blog = blog
+ /cms = config:cms.ini
+
+That this is a ``composite`` section means it dispatches the request
+to other applications. ``use = egg:Paste#urlmap`` means to use the
+composite application named ``urlmap`` from the ``Paste`` package.
+``urlmap`` is a particularly common composite application -- it uses a
+path prefix to map your request to another application. These are
+the applications like "home", "blog", "wiki" and "config:cms.ini". The last
+one just refers to another file ``cms.ini`` in the same directory.
+
+Next up::
+
+ [app:home]
+ use = egg:Paste#static
+ document_root = %(here)s/htdocs
+
+``egg:Paste#static`` is another simple application, in this case it
+just serves up non-dynamic files. It takes one bit of configuration:
+``document_root``. You can use variable substitution, which will pull
+variables from the section ``[DEFAULT]`` (case sensitive!) with
+markers like ``%(var_name)s``. The special variable ``%(here)s`` is
+the directory containing the configuration file; you should use that
+in lieu of relative filenames (which depend on the current directory,
+which can change depending how the server is run).
+
+Then::
+
+ [filter-app:blog]
+ use = egg:Authentication#auth
+ next = blogapp
+ roles = admin
+ htpasswd = /home/me/users.htpasswd
+
+ [app:blogapp]
+ use = egg:BlogApp
+ database = sqlite:/home/me/blog.db
+
+The ``[filter-app:blog]`` section means that you want an application
+with a filter applied. The application being filtered is indicated
+with ``next`` (which refers to the next section). The
+``egg:Authentication#auth`` filter doesn't actually exist, but one
+could imagine it logs people in and checks permissions.
+
+That last section is just a reference to an application that you
+probably installed with ``pip install BlogApp``, and one bit of
+configuration you passed to it (``database``).
+
+Lastly::
+
+ [app:wiki]
+ use = call:mywiki.main:application
+ database = sqlite:/home/me/wiki.db
+
+This section is similar to the previous one, with one important difference.
+Instead of an entry point in an egg, it refers directly to the ``application``
+variable in the ``mywiki.main`` module. The reference consist of two parts,
+separated by a colon. The left part is the full name of the module and the
+right part is the path to the variable, as a Python expression relative to the
+containing module.
+
+So, that's most of the features you'll use.
+
+Basic Usage
+-----------
+
+The basic way you'll use Paste Deployment is to load `WSGI
+<http://www.python.org/peps/pep-3333.html>`_ applications. Many
+Python frameworks now support WSGI, so applications written for these
+frameworks should be usable.
+
+The primary function is ``paste.deploy.loadapp``. This loads an
+application given a URI. You can use it like::
+
+ from paste.deploy import loadapp
+ wsgi_app = loadapp('config:/path/to/config.ini')
+
+There's two URI formats currently supported: ``config:`` and ``egg:``.
+
+``config:`` URIs
+----------------
+
+URIs that being with ``config:`` refer to configuration files. These
+filenames can be relative if you pass the ``relative_to`` keyword
+argument to ``loadapp()``.
+
+.. note::
+
+ Filenames are never considered relative to the current working
+ directory, as that is a unpredictable location. Generally when
+ a URI has a context it will be seen as relative to that context;
+ for example, if you have a ``config:`` URI inside another
+ configuration file, the path is considered relative to the
+ directory that contains that configuration file.
+
+Config Format
+~~~~~~~~~~~~~
+
+Configuration files are in the INI format. This is a simple format
+that looks like::
+
+ [section_name]
+ key = value
+ another key = a long value
+ that extends over multiple lines
+
+All values are strings (no quoting is necessary). The keys and
+section names are case-sensitive, and may contain punctuation and
+spaces (though both keys and values are stripped of leading and
+trailing whitespace). Lines can be continued with leading whitespace.
+
+Lines beginning with ``#`` (preferred) or ``;`` are considered
+comments.
+
+Applications
+~~~~~~~~~~~~
+
+You can define multiple applications in a single file; each
+application goes in its own section. Even if you have just one
+application, you must put it in a section.
+
+Each section name defining an application should be prefixed with
+``app:``. The "main" section (when just defining one application)
+would go in ``[app:main]`` or just ``[app]``.
+
+There's two ways to indicate the Python code for the application. The
+first is to refer to another URI or name::
+
+ [app:myapp]
+ use = config:another_config_file.ini#app_name
+
+ # or any URI:
+ [app:myotherapp]
+ use = egg:MyApp
+
+ # or a callable from a module:
+ [app:mythirdapp]
+ use = call:my.project:myapplication
+
+ # or even another section:
+ [app:mylastapp]
+ use = myotherapp
+
+It would seem at first that this was pointless; just a way to point to
+another location. However, in addition to loading the application
+from that location, you can also add or change the configuration.
+
+The other way to define an application is to point exactly to some
+Python code::
+
+ [app:myapp]
+ paste.app_factory = myapp.modulename:app_factory
+
+You must give an explicit *protocol* (in this case
+``paste.app_factory``), and the value is something to import. In
+this case the module ``myapp.modulename`` is loaded, and the
+``app_factory`` object retrieved from it.
+
+See `Defining Factories`_ for more about the protocols.
+
+Configuration
+~~~~~~~~~~~~~
+
+Configuration is done through keys besides ``use`` (or the protocol
+names). Any other keys found in the section will be passed as keyword
+arguments to the factory. This might look like::
+
+ [app:blog]
+ use = egg:MyBlog
+ database = mysql://localhost/blogdb
+ blogname = This Is My Blog!
+
+You can override these in other sections, like::
+
+ [app:otherblog]
+ use = blog
+ blogname = The other face of my blog
+
+This way some settings could be defined in a generic configuration
+file (if you have ``use = config:other_config_file``) or you can
+publish multiple (more specialized) applications just by adding a
+section.
+
+Global Configuration
+~~~~~~~~~~~~~~~~~~~~
+
+Often many applications share the same configuration. While you can
+do that a bit by using other config sections and overriding values,
+often you want that done for a bunch of disparate configuration
+values. And typically applications can't take "extra" configuration
+parameters; with global configuration you do something equivalent to
+"if this application wants to know the admin email, this is it".
+
+Applications are passed the global configuration separately, so they
+must specifically pull values out of it; typically the global
+configuration serves as the basis for defaults when no local
+configuration is passed in.
+
+Global configuration to apply to every application defined in a file
+should go in a special section named ``[DEFAULT]``. You can override
+global configuration locally like::
+
+ [DEFAULT]
+ admin_email = webmaster@example.com
+
+ [app:main]
+ use = ...
+ set admin_email = bob@example.com
+
+That is, by using ``set`` in front of the key.
+
+Composite Applications
+~~~~~~~~~~~~~~~~~~~~~~
+
+"Composite" applications are things that act like applications, but
+are made up of other applications. One example would be a URL mapper,
+where you mount applications at different URL paths. This might look
+like::
+
+ [composite:main]
+ use = egg:Paste#urlmap
+ / = mainapp
+ /files = staticapp
+
+ [app:mainapp]
+ use = egg:MyApp
+
+ [app:staticapp]
+ use = egg:Paste#static
+ document_root = /path/to/docroot
+
+The composite application "main" is just like any other application
+from the outside (you load it with ``loadapp`` for instance), but it
+has access to other applications defined in the configuration file.
+
+Other Objects
+~~~~~~~~~~~~~
+
+In addition to sections with ``app:``, you can define filters and
+servers in a configuration file, with ``server:`` and ``filter:``
+prefixes. You load these with ``loadserver`` and ``loadfilter``. The
+configuration works just the same; you just get back different kinds
+of objects.
+
+Filter Composition
+~~~~~~~~~~~~~~~~~~
+
+There are several ways to apply filters to applications. It mostly
+depends on how many filters, and in what order you want to apply them.
+
+The first way is to use the ``filter-with`` setting, like::
+
+ [app:main]
+ use = egg:MyEgg
+ filter-with = printdebug
+
+ [filter:printdebug]
+ use = egg:Paste#printdebug
+ # and you could have another filter-with here, and so on...
+
+Also, two special section types exist to apply filters to your
+applications: ``[filter-app:...]`` and ``[pipeline:...]``. Both of
+these sections define applications, and so can be used wherever an
+application is needed.
+
+``filter-app`` defines a filter (just like you would in a
+``[filter:...]`` section), and then a special key ``next`` which
+points to the application to apply the filter to.
+
+``pipeline:`` is used when you need apply a number of filters. It
+takes *one* configuration key ``pipeline`` (plus any global
+configuration overrides you want). ``pipeline`` is a list of filters
+ended by an application, like::
+
+ [pipeline:main]
+ pipeline = filter1 egg:FilterEgg#filter2 filter3 app
+
+ [filter:filter1]
+ ...
+
+Getting Configuration
+~~~~~~~~~~~~~~~~~~~~~
+
+If you want to get the configuration without creating the application,
+you can use the ``appconfig(uri)`` function, which is just like the
+``loadapp()`` function except it returns the configuration that would
+be used, as a dictionary. Both global and local configuration is
+combined into a single dictionary, but you can look at just one or the
+other with the attributes ``.local_conf`` and ``.global_conf``.
+
+``egg:`` URIs
+-------------
+
+`Python Eggs <http://peak.telecommunity.com/DevCenter/PythonEggs>`_
+are a distribution and installation format produced by `setuptools
+<http://peak.telecommunity.com/DevCenter/setuptools>`_ and
+`distribute <http://packages.python.org/distribute/>`_ that adds metadata to a
+normal Python package (among other things).
+
+You don't need to understand a whole lot about Eggs to use them. If
+you have a `distutils
+<http://python.org/doc/current/lib/module-distutils.html>`_
+``setup.py`` script, just change::
+
+ from distutils.core import setup
+
+to::
+
+ from setuptools import setup
+
+Now when you install the package it will be installed as an egg.
+
+The first important part about an Egg is that it has a
+*specification*. This is formed from the name of your distribution
+(the ``name`` keyword argument to ``setup()``), and you can specify a
+specific version. So you can have an egg named ``MyApp``, or
+``MyApp==0.1`` to specify a specific version.
+
+The second is *entry points*. These are references to Python objects
+in your packages that are named and have a specific protocol.
+"Protocol" here is just a way of saying that we will call them with
+certain arguments, and expect a specific return value. We'll talk
+more about the protocols later_.
+
+.. _later: `Defining Factories`_
+
+The important part here is how we define entry points. You'll add an
+argument to ``setup()`` like::
+
+ setup(
+ name='MyApp',
+ ...
+ entry_points={
+ 'paste.app_factory': [
+ 'main=myapp.mymodule:app_factory',
+ 'ob2=myapp.mymodule:ob_factory'],
+ },
+ )
+
+This defines two applications named ``main`` and ``ob2``. You can
+then refer to these by ``egg:MyApp#main`` (or just ``egg:MyApp``,
+since ``main`` is the default) and ``egg:MyApp#ob2``.
+
+The values are instructions for importing the objects. ``main`` is
+located in the ``myapp.mymodule`` module, in an object named
+``app_factory``.
+
+There's no way to add configuration to objects imported as Eggs.
+
+Defining Factories
+------------------
+
+This lets you point to factories (that obey the specific protocols we
+mentioned). But that's not much use unless you can create factories
+for your applications.
+
+There's a few protocols: ``paste.app_factory``,
+``paste.composite_factory``, ``paste.filter_factory``, and lastly
+``paste.server_factory``. Each of these expects a callable (like a
+function, method, or class).
+
+``paste.app_factory``
+~~~~~~~~~~~~~~~~~~~~~~
+
+The application is the most common. You define one like::
+
+ def app_factory(global_config, **local_conf):
+ return wsgi_app
+
+The ``global_config`` is a dictionary, and local configuration is
+passed as keyword arguments. The function returns a WSGI application.
+
+``paste.composite_factory``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Composites are just slightly more complex::
+
+ def composite_factory(loader, global_config, **local_conf):
+ return wsgi_app
+
+The ``loader`` argument is an object that has a couple interesting
+methods. ``get_app(name_or_uri, global_conf=None)`` return a WSGI
+application with the given name. ``get_filter`` and ``get_server``
+work the same way.
+
+A more interesting example might be a composite factory that does
+something. For instance, consider a "pipeline" application::
+
+ def pipeline_factory(loader, global_config, pipeline):
+ # space-separated list of filter and app names:
+ pipeline = pipeline.split()
+ filters = [loader.get_filter(n) for n in pipeline[:-1]]
+ app = loader.get_app(pipeline[-1])
+ filters.reverse() # apply in reverse order!
+ for filter in filters:
+ app = filter(app)
+ return app
+
+Then we use it like::
+
+ [composite:main]
+ use = <pipeline_factory_uri>
+ pipeline = egg:Paste#printdebug session myapp
+
+ [filter:session]
+ use = egg:Paste#session
+ store = memory
+
+ [app:myapp]
+ use = egg:MyApp
+
+``paste.filter_factory``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Filter factories are just like app factories (same signature), except
+they return filters. Filters are callables that take a WSGI
+application as the only argument, and return a "filtered" version of
+that application.
+
+Here's an example of a filter that checks that the ``REMOTE_USER`` CGI
+variable is set, creating a really simple authentication filter::
+
+ def auth_filter_factory(global_conf, req_usernames):
+ # space-separated list of usernames:
+ req_usernames = req_usernames.split()
+ def filter(app):
+ return AuthFilter(app, req_usernames)
+ return filter
+
+ class AuthFilter(object):
+ def __init__(self, app, req_usernames):
+ self.app = app
+ self.req_usernames = req_usernames
+
+ def __call__(self, environ, start_response):
+ if environ.get('REMOTE_USER') in self.req_usernames:
+ return self.app(environ, start_response)
+ start_response(
+ '403 Forbidden', [('Content-type', 'text/html')])
+ return ['You are forbidden to view this resource']
+
+``paste.filter_app_factory``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is very similar to ``paste.filter_factory``, except that it also
+takes a ``wsgi_app`` argument, and returns a WSGI application. So if
+you changed the above example to::
+
+ class AuthFilter(object):
+ def __init__(self, app, global_conf, req_usernames):
+ ....
+
+Then ``AuthFilter`` would serve as a filter_app_factory
+(``req_usernames`` is a required local configuration key in this
+case).
+
+``paste.server_factory``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This takes the same signature as applications and filters, but returns
+a server.
+
+A server is a callable that takes a single argument, a WSGI
+application. It then serves the application.
+
+An example might look like::
+
+ def server_factory(global_conf, host, port):
+ port = int(port)
+ def serve(app):
+ s = Server(app, host=host, port=port)
+ s.serve_forever()
+ return serve
+
+The implementation of ``Server`` is left to the user.
+
+``paste.server_runner``
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Like ``paste.server_factory``, except ``wsgi_app`` is passed as the
+first argument, and the server should run immediately.
+
+Outstanding Issues
+------------------
+
+* Should there be a "default" protocol for each type of object? Since
+ there's currently only one protocol, it seems like it makes sense
+ (in the future there could be multiple). Except that
+ ``paste.app_factory`` and ``paste.composite_factory`` overlap
+ considerably.
+
+* ConfigParser's INI parsing is kind of annoying. I'd like it both
+ more constrained and less constrained. Some parts are sloppy (like
+ the way it interprets ``[DEFAULT]``).
+
+* ``config:`` URLs should be potentially relative to other locations,
+ e.g., ``config:$docroot/...``. Maybe using variables from
+ ``global_conf``?
+
+* Should other variables have access to ``global_conf``?
+
+* Should objects be Python-syntax, instead of always strings? Lots of
+ code isn't usable with Python strings without a thin wrapper to
+ translate objects into their proper types.
+
+* Some short-form for a filter/app, where the filter refers to the
+ "next app". Maybe like::
+
+ [app-filter:app_name]
+ use = egg:...
+ next = next_app
+
+ [app:next_app]
+ ...
+
diff --git a/docs/license.txt b/docs/license.txt
new file mode 100644
index 0000000..c810dec
--- /dev/null
+++ b/docs/license.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2006-2007 Ian Bicking and Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/docs/modules/config.txt b/docs/modules/config.txt
new file mode 100644
index 0000000..9d8f894
--- /dev/null
+++ b/docs/modules/config.txt
@@ -0,0 +1,13 @@
+:mod:`paste.deploy.config` -- Configuration and Environment middleware
+======================================================================
+
+.. automodule:: paste.deploy.config
+
+Module Contents
+---------------
+
+.. autoclass:: DispatchingConfig
+.. autoclass:: ConfigMiddleware
+.. autoclass:: PrefixMiddleware
+
+.. comment: FIXME: do something about CONFIG (manual docs?)
diff --git a/docs/modules/converters.txt b/docs/modules/converters.txt
new file mode 100644
index 0000000..4948104
--- /dev/null
+++ b/docs/modules/converters.txt
@@ -0,0 +1,11 @@
+:mod:`paste.deploy.converters` -- Conversion helpers for String Configuration
+=============================================================================
+
+.. automodule:: paste.deploy.converters
+
+Module Contents
+---------------
+
+.. autofunction:: asbool
+.. autofunction:: asint
+.. autofunction:: aslist
diff --git a/docs/modules/loadwsgi.txt b/docs/modules/loadwsgi.txt
new file mode 100644
index 0000000..feebf28
--- /dev/null
+++ b/docs/modules/loadwsgi.txt
@@ -0,0 +1,13 @@
+:mod:`paste.deploy.loadwsgi` -- Load WSGI applications from config files
+========================================================================
+
+.. automodule:: paste.deploy.loadwsgi
+
+Module Contents
+---------------
+
+.. autofunction:: loadapp
+.. autofunction:: loadserver
+.. autofunction:: loadfilter
+.. autofunction:: appconfig
+
diff --git a/docs/news.txt b/docs/news.txt
new file mode 100644
index 0000000..bc1e987
--- /dev/null
+++ b/docs/news.txt
@@ -0,0 +1,179 @@
+Paste Deployment News
+=====================
+
+1.5.2
+-----
+
+* Fixed Python 3 issue in paste.deploy.util.fix_type_error()
+
+1.5.1
+-----
+
+* Fixed use of the wrong variable when determining the context protocol
+
+* Fixed invalid import of paste.deploy.Config to paste.deploy.config.Config
+
+* Fixed multi proxy IPs bug in X-Forwarded-For header in PrefixMiddleware
+
+* Fixed TypeError when trying to raise LookupError on Python 3
+
+* Fixed exception reraise on Python 3
+
+Thanks to Alexandre Conrad, Atsushi Odagiri, Pior Bastida and Tres Seaver for their contributions.
+
+1.5.0
+-----
+
+* Project is now maintained by Alex Grönholm <alex.gronholm@nextday.fi>
+
+* Was printing extraneous data when calling setup.py
+
+* Fixed missing paster template files (fixes "paster create -t paste.deploy")
+
+* Excluded tests from release distributions
+
+* Added support for the "call:" protocol for loading apps directly as
+ functions (contributed by Jason Stitt)
+
+* Added Python 3.x support
+
+* Dropped Python 2.4 support
+
+* Removed the ``paste.deploy.epdesc`` and ``paste.deploy.interfaces`` modules
+ -- contact the maintainer if you actually needed them
+
+1.3.4
+-----
+
+* Fix loadconfig path handling on Jython on Windows.
+
+1.3.3
+-----
+
+* In :class:`paste.deploy.config.PrefixMiddleware` the headers
+ ``X-Forwarded-Scheme`` and ``X-Forwarded-Proto`` are now translated
+ to the key ``environ['wsgi.url_scheme']``. Also ``X-Forwarded-For``
+ is translated to ``environ['REMOTE_ADDR']``
+
+* Also in PrefixMiddleware, if X-Forwarded-Host has multiple
+ (comma-separated) values, use only the first value.
+
+1.3.2
+-----
+
+* Added ``paste.deploy.converters.asint()``.
+* fixed use sections overwriting the config's __file__ value with the
+ use'd filename.
+* ``paste.deploy.loadwsgi`` now supports variable expansion in the
+ DEFAULT section of config files (unlike plain ConfigParser).
+
+1.3.1
+-----
+
+* Fix ``appconfig`` config loading when using a config file with
+ ``filter-with`` in it (previously you'd get TypeError: iteration
+ over non-sequence)
+
+1.3
+---
+
+* Added ``scheme`` option to ``PrefixMiddleware``, so you can force a
+ scheme (E.g., when proxying an HTTPS connection over HTTP).
+
+* Pop proper values into ``environ['paste.config']`` in
+ ``ConfigMiddleware``.
+
+1.1
+---
+
+* Any ``global_conf`` extra keys you pass to ``loadapp`` (or the other
+ loaders) will show up as though they were in ``[DEFAULT]``, so they
+ can be used in variable interpolation. Note: this won't overwrite
+ any existing values in ``[DEFAULT]``.
+
+* Added ``force_port`` option to
+ ``paste.deploy.config.PrefixMiddleware``. Also the ``prefix``
+ argument is stripped of any trailing ``/``, which can't be valid in
+ that position.
+
+1.0
+---
+
+* Added some documentation for the different kinds of entry points
+ Paste Deploy uses.
+
+* Added a feature to ``PrefixMiddleware`` that translates the
+ ``X-Forwarded-Server`` header to ``Host``.
+
+0.9.6
+-----
+
+* Added ``PrefixMiddleware`` which compensates for cases where the
+ wsgi app is behind a proxy of some sort that isn't moving the prefix
+ into the SCRIPT_NAME in advance.
+
+* Changed _loadconfig() so that it works with Windows absolute paths.
+
+* Make the error messages prettier when you call a function and fail
+ to give an argument, like a required function argument.
+
+0.5
+---
+
+* Made the ``paste_deploy`` template (used with ``paster create
+ --template=paste_deploy``) more useful, with an example application
+ and entry point.
+
+0.4
+---
+
+* Allow filters to have ``filter-with`` values, just like
+ applications.
+
+* Renamed ``composit`` to ``composite`` (old names still work, but
+ aren't documented).
+
+* Added ``appconfig()`` to load along with ``loadapp()``, but return
+ the configuration without invoking the application.
+
+0.3
+---
+
+* Allow variable setting like::
+
+ get local_var = global_var_name
+
+ To bring in global variables to the local scope.
+
+* Allow interpolation in files, like ``%(here)s``. Anything in the
+ ``[DEFAULTS]`` section will be available to substitute into a value,
+ as will variables in the same section. Also, the special value
+ ``here`` will be the directory the configuration file is located in.
+
+0.2
+---
+
+Released 26 August 2004
+
+* Added a ``filter-with`` setting to applications.
+
+* Removed the ``1`` from all the protocol names (e.g.,
+ ``paste.app_factory1`` is not ``paste.app_factory``).
+
+* Added ``filter-app:`` and ``pipeline:`` sections. `Docs
+ <paste-deploy.html#filter-composition>`__.
+
+* Added ``paste.filter_app_factory1`` (`doc
+ <paste-deploy.html#paste-filter-app-factory1>`__) and
+ ``paste.server_runner1`` (`doc
+ <paste-deploy.html#paste-server-runner1>`__) protocols.
+
+* Added ``paste.deploy.converters`` module for handling the
+ string values that are common with this system.
+
+0.1
+---
+
+Released 22 August 2004
+
+Initial version released. It's all new.
diff --git a/paste/__init__.py b/paste/__init__.py
new file mode 100644
index 0000000..cdb6121
--- /dev/null
+++ b/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/paste/deploy/__init__.py b/paste/deploy/__init__.py
new file mode 100644
index 0000000..94c63a8
--- /dev/null
+++ b/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/paste/deploy/compat.py b/paste/deploy/compat.py
new file mode 100644
index 0000000..05047db
--- /dev/null
+++ b/paste/deploy/compat.py
@@ -0,0 +1,32 @@
+# (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
+"""Python 2<->3 compatibility module"""
+import sys
+
+
+def print_(template, *args, **kwargs):
+ template = str(template)
+ if args:
+ template = template % args
+ elif kwargs:
+ template = template % kwargs
+ sys.stdout.writelines(template)
+
+if sys.version_info < (3, 0):
+ basestring = basestring
+ from ConfigParser import ConfigParser
+ from urllib import unquote
+ iteritems = lambda d: d.iteritems()
+ dictkeys = lambda d: d.keys()
+
+ def reraise(t, e, tb):
+ exec('raise t, e, tb', dict(t=t, e=e, tb=tb))
+else:
+ basestring = str
+ from configparser import ConfigParser
+ from urllib.parse import unquote
+ iteritems = lambda d: d.items()
+ dictkeys = lambda d: list(d.keys())
+
+ def reraise(t, e, tb):
+ raise e.with_traceback(tb)
diff --git a/paste/deploy/config.py b/paste/deploy/config.py
new file mode 100644
index 0000000..a503007
--- /dev/null
+++ b/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(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.
+ """
+
+ # @@: 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(object):
+
+ """
+ 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(object):
+ """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 = '%s:%s' % (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/paste/deploy/converters.py b/paste/deploy/converters.py
new file mode 100644
index 0000000..c9d87de
--- /dev/null
+++ b/paste/deploy/converters.py
@@ -0,0 +1,40 @@
+# (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.compat import basestring
+
+
+truthy = frozenset(['true', 'yes', 'on', 'y', 't', '1'])
+falsy = frozenset(['false', 'no', 'off', 'n', 'f', '0'])
+
+
+def asbool(obj):
+ if isinstance(obj, basestring):
+ 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, basestring):
+ 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/paste/deploy/loadwsgi.py b/paste/deploy/loadwsgi.py
new file mode 100644
index 0000000..8b2849d
--- /dev/null
+++ b/paste/deploy/loadwsgi.py
@@ -0,0 +1,725 @@
+# (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 __future__ import with_statement
+import os
+import sys
+import re
+
+import pkg_resources
+
+from paste.deploy.compat import ConfigParser, unquote, iteritems, dictkeys
+from paste.deploy.util import fix_call, lookup_object
+
+__all__ = ['loadapp', 'loadserver', 'loadfilter', 'appconfig']
+
+
+############################################################
+## Utility functions
+############################################################
+
+
+def import_string(s):
+ return pkg_resources.EntryPoint.parse("x=" + s).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
+ if hasattr(self, '_interpolation'):
+ self._interpolation = self.InterpolateWrapper(self._interpolation)
+
+ read_file = getattr(ConfigParser, 'read_file', ConfigParser.readfp)
+
+ 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 iteritems(defaults):
+ defaults[key] = self.get('DEFAULT', key) or val
+ return defaults
+
+ def _interpolate(self, section, option, rawval, vars):
+ # Python < 3.2
+ try:
+ return ConfigParser._interpolate(
+ self, section, option, rawval, vars)
+ except Exception:
+ e = sys.exc_info()[1]
+ args = list(e.args)
+ args[0] = 'Error in file %s: %s' % (self.filename, e)
+ e.args = tuple(args)
+ e.message = args[0]
+ raise
+
+ class InterpolateWrapper(object):
+ # Python >= 3.2
+ 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] = 'Error in file %s: %s' % (parser.filename, e)
+ e.args = tuple(args)
+ e.message = args[0]
+ raise
+
+
+############################################################
+## Object types
+############################################################
+
+
+class _ObjectType(object):
+
+ 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 '<%s protocols=%r prefixes=%r>' % (
+ 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(object):
+
+ 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 iteritems(new_defaults):
+ 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)
+ if global_conf is None:
+ global_conf = {}
+ else:
+ global_conf = global_conf.copy()
+ defaults = self.parser.defaults()
+ global_conf.update(defaults)
+ 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"
+ % (', '.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([
+ dictkeys(pkg_resources.get_entry_info(self.spec, prot, name) or {})
+ 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(object):
+
+ 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/paste/deploy/paster_templates.py b/paste/deploy/paster_templates.py
new file mode 100644
index 0000000..9c5f942
--- /dev/null
+++ b/paste/deploy/paster_templates.py
@@ -0,0 +1,36 @@
+# (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
+
+from paste.deploy.compat import print_
+
+
+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/paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl b/paste/deploy/paster_templates/paste_deploy/+package+/sampleapp.py_tmpl
new file mode 100644
index 0000000..cb49352
--- /dev/null
+++ b/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 = [
+ '<html><head><title>%s</title></head>\n' % greeting,
+ '<body><h1>%s!</h1>\n' % greeting,
+ '<table border=1>\n',
+ ]
+ items = environ.items()
+ items.sort()
+ for key, value in items:
+ content.append('<tr><td>%s</td><td>%s</td></tr>\n'
+ % (key, cgi.escape(repr(value))))
+ content.append('</table></body></html>')
+ return content
diff --git a/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl b/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl
new file mode 100644
index 0000000..97decb8
--- /dev/null
+++ b/paste/deploy/paster_templates/paste_deploy/+package+/wsgiapp.py_tmpl
@@ -0,0 +1,24 @@
+from paste.deploy.config import ConfigMiddleware
+
+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/paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl b/paste/deploy/paster_templates/paste_deploy/docs/devel_config.ini_tmpl
new file mode 100644
index 0000000..0c0ae35
--- /dev/null
+++ b/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/paste/deploy/util.py b/paste/deploy/util.py
new file mode 100644
index 0000000..b6f766a
--- /dev/null
+++ b/paste/deploy/util.py
@@ -0,0 +1,73 @@
+# (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
+
+from paste.deploy.compat import reraise
+
+
+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 = '%s; got %s, wanted %s' % (exc_info[1], gotspec, 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)
+ reraise(*exc_info)
+ 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
diff --git a/regen-docs b/regen-docs
new file mode 100755
index 0000000..f8dad75
--- /dev/null
+++ b/regen-docs
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+mkdir -p docs/_static docs/_build
+sphinx-build -E -b html docs/ docs/_build || exit 1
+if [ "$1" = "publish" ] ; then
+ cd docs/
+ echo "Uploading files..."
+ scp -r _build/* ianb@webwareforpython.org:/home/paste/htdocs/deploy/
+fi
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..f15c017
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[wheel]
+universal = true
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..79f9d70
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,59 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.dirname(__file__)
+readme_path = os.path.join(here, 'README')
+readme = open(readme_path).read()
+
+
+setup(
+ name='PasteDeploy',
+ version='1.5.2',
+ description='Load, configure, and compose WSGI applications and servers',
+ long_description=readme,
+ classifiers=[
+ 'Development Status :: 6 - Mature',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.5',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.1',
+ 'Programming Language :: Python :: 3.2',
+ 'Programming Language :: Python :: 3.3',
+ 'Topic :: Internet :: WWW/HTTP',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Framework :: Paste',
+ ],
+ keywords='web wsgi application server',
+ author='Ian Bicking',
+ author_email='ianb@colorstudy.com',
+ maintainer='Alex Gronholm',
+ maintainer_email='alex.gronholm@nextday.fi',
+ url='http://pythonpaste.org/deploy/',
+ license='MIT',
+ namespace_packages=['paste'],
+ packages=find_packages(exclude=['tests']),
+ include_package_data=True,
+ zip_safe=False,
+ test_suite='nose.collector',
+ tests_require=['nose>=0.11'],
+ extras_require={
+ 'Config': [],
+ 'Paste': ['Paste'],
+ },
+ entry_points="""
+ [paste.filter_app_factory]
+ config = paste.deploy.config:make_config_filter [Config]
+ prefix = paste.deploy.config:make_prefix_middleware
+
+ [paste.paster_create_template]
+ paste_deploy=paste.deploy.paster_templates:PasteDeploy
+ """
+)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..cffe526
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,13 @@
+import os
+import sys
+
+here = os.path.dirname(__file__)
+base = os.path.dirname(here)
+sys.path.insert(0, base)
+
+# We can only import this after we adjust the paths
+import pkg_resources
+
+# Make absolutely sure we're testing *this* package, not
+# some other installed package
+pkg_resources.require('PasteDeploy')
diff --git a/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/PKG-INFO b/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/PKG-INFO
new file mode 100644
index 0000000..a2a1137
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/PKG-INFO
@@ -0,0 +1,10 @@
+Metadata-Version: 1.0
+Name: FakeApp
+Version: 1.0
+Summary: UNKNOWN
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/entry_points.txt b/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/entry_points.txt
new file mode 100644
index 0000000..9bfc986
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/entry_points.txt
@@ -0,0 +1,22 @@
+[paste.app_factory]
+
+ basic_app=fakeapp.apps:make_basic_app
+ other=fakeapp.apps:make_basic_app2
+ configed=fakeapp.configapps:SimpleApp.make_app
+
+
+[paste.composit_factory]
+
+ remote_addr=fakeapp.apps:make_remote_addr
+
+
+[paste.filter_app_factory]
+
+ caps2=fakeapp.apps:CapFilter
+
+
+[paste.filter_factory]
+
+ caps=fakeapp.apps:make_cap_filter
+
+
diff --git a/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/top_level.txt b/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/top_level.txt
new file mode 100644
index 0000000..79ed67a
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/FakeApp.egg-info/top_level.txt
@@ -0,0 +1 @@
+fakeapp
diff --git a/tests/fake_packages/FakeApp.egg/fakeapp/__init__.py b/tests/fake_packages/FakeApp.egg/fakeapp/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/fakeapp/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/tests/fake_packages/FakeApp.egg/fakeapp/apps.py b/tests/fake_packages/FakeApp.egg/fakeapp/apps.py
new file mode 100644
index 0000000..cae7eba
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/fakeapp/apps.py
@@ -0,0 +1,69 @@
+############################################################
+## Apps
+############################################################
+
+def simple_app(response, environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/html')])
+ return ['This is ', response]
+
+def basic_app(environ, start_response):
+ return simple_app('basic app', environ, start_response)
+
+def make_basic_app(global_conf, **conf):
+ return basic_app
+
+def basic_app2(environ, start_response):
+ return simple_app('basic app2', environ, start_response)
+
+def make_basic_app2(global_conf, **conf):
+ return basic_app2
+
+############################################################
+## Composits
+############################################################
+
+def make_remote_addr(loader, global_conf, **conf):
+ apps = {}
+ addrs = {}
+ for name, value in conf.items():
+ if name.startswith('app.'):
+ apps[name[4:]] = loader.get_app(value, global_conf)
+ elif name.startswith('addr.'):
+ addrs[name[5:]] = value
+ dispatcher = RemoteAddrDispatch()
+ for name in apps:
+ dispatcher.map[addrs[name]] = apps[name]
+ return dispatcher
+
+class RemoteAddrDispatch(object):
+ def __init__(self, map=None):
+ self.map = map or {}
+
+ def __call__(self, environ, start_response):
+ addr = environ['REMOTE_ADDR']
+ app = self.map.get(addr) or self.map['0.0.0.0']
+ return app(environ, start_response)
+
+############################################################
+## Filters
+############################################################
+
+def make_cap_filter(global_conf, method_to_call='upper'):
+ def cap_filter(app):
+ return CapFilter(app, global_conf, method_to_call)
+ return cap_filter
+
+class CapFilter(object):
+
+ def __init__(self, app, global_conf, method_to_call='upper'):
+ self.app = app
+ self.method_to_call = method_to_call
+ self.global_conf = global_conf
+
+ def __call__(self, environ, start_response):
+ app_iter = self.app(environ, start_response)
+ for item in app_iter:
+ yield getattr(item, self.method_to_call)()
+ if hasattr(app_iter, 'close'):
+ app_iter.close()
+
diff --git a/tests/fake_packages/FakeApp.egg/fakeapp/configapps.py b/tests/fake_packages/FakeApp.egg/fakeapp/configapps.py
new file mode 100644
index 0000000..ef13182
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/fakeapp/configapps.py
@@ -0,0 +1,14 @@
+class SimpleApp(object):
+ def __init__(self, global_conf, local_conf, name):
+ self.global_conf = global_conf
+ self.local_conf = local_conf
+ self.name = name
+
+ def __call__(self, environ, start_response):
+ start_response('200 OK', [('Content-type', 'text/html')])
+ return ['I am: ', name]
+
+ def make_app(cls, global_conf, **conf):
+ return cls(global_conf, conf, 'basic')
+ make_app = classmethod(make_app)
+
diff --git a/tests/fake_packages/FakeApp.egg/setup.py b/tests/fake_packages/FakeApp.egg/setup.py
new file mode 100644
index 0000000..854483e
--- /dev/null
+++ b/tests/fake_packages/FakeApp.egg/setup.py
@@ -0,0 +1,23 @@
+from setuptools import setup, find_packages
+
+setup(
+ name="FakeApp",
+ version="1.0",
+ packages=find_packages(),
+ entry_points={
+ 'paste.app_factory': """
+ basic_app=fakeapp.apps:make_basic_app
+ other=fakeapp.apps:make_basic_app2
+ configed=fakeapp.configapps:SimpleApp.make_app
+ """,
+ 'paste.composit_factory': """
+ remote_addr=fakeapp.apps:make_remote_addr
+ """,
+ 'paste.filter_factory': """
+ caps=fakeapp.apps:make_cap_filter
+ """,
+ 'paste.filter_app_factory': """
+ caps2=fakeapp.apps:CapFilter
+ """,
+ },
+ )
diff --git a/tests/fixture.py b/tests/fixture.py
new file mode 100644
index 0000000..751659d
--- /dev/null
+++ b/tests/fixture.py
@@ -0,0 +1,20 @@
+import os
+import sys
+import shutil
+
+test_dir = os.path.dirname(__file__)
+egg_info_dir = os.path.join(test_dir, 'fake_packages', 'FakeApp.egg',
+ 'EGG-INFO')
+info_dir = os.path.join(test_dir, 'fake_packages', 'FakeApp.egg',
+ 'FakeApp.egg-info')
+if not os.path.exists(egg_info_dir):
+ try:
+ os.symlink(info_dir, egg_info_dir)
+ except:
+ shutil.copytree(info_dir, egg_info_dir)
+
+sys.path.append(os.path.dirname(egg_info_dir))
+
+from pkg_resources import *
+working_set.add_entry(os.path.dirname(egg_info_dir))
+require('FakeApp')
diff --git a/tests/sample_configs/basic_app.ini b/tests/sample_configs/basic_app.ini
new file mode 100644
index 0000000..f1d931c
--- /dev/null
+++ b/tests/sample_configs/basic_app.ini
@@ -0,0 +1,14 @@
+[application:main]
+use = egg:FakeApp#basic_app
+
+[application:other]
+use = egg:FakeApp#other
+
+[composit:remote_addr]
+use = egg:FakeApp#remote_addr
+app.1 = main
+addr.1 = 127.0.0.1
+
+app.2 = other
+addr.2 = 0.0.0.0
+
diff --git a/tests/sample_configs/executable.ini b/tests/sample_configs/executable.ini
new file mode 100755
index 0000000..3b75fe9
--- /dev/null
+++ b/tests/sample_configs/executable.ini
@@ -0,0 +1,10 @@
+#!/usr/bin/env paster
+[exe]
+sys.path = /fake/path/
+ /another/fake/path ../fake_packages/
+
+[server]
+use = egg:PasteScript#cgi
+
+[app]
+use = egg:FakeApp#basic_app
diff --git a/tests/sample_configs/test_config.ini b/tests/sample_configs/test_config.ini
new file mode 100644
index 0000000..d614829
--- /dev/null
+++ b/tests/sample_configs/test_config.ini
@@ -0,0 +1,38 @@
+[DEFAULT]
+def1 = a
+def2 = b
+basepath = %(here)s
+
+[app:test1]
+use = egg:FakeApp#configed
+setting1 = foo
+setting2 = bar
+apppath = %(basepath)s/app
+
+[app:test2]
+use = egg:FakeApp#configed
+set def1 = test2
+set another = TEST
+local conf = something
+
+[app:test3]
+use = test2
+set def1 = test3
+another = something more
+ across several
+ lines
+
+[app:test_foreign_config]
+use = config:test_config_included.ini
+set glob = override
+another = FOO
+
+[app:test_get]
+use = egg:FakeApp#configed
+set def2 = TEST
+get def1 = def1
+get foo = def2
+
+[app:test_global_conf]
+use = egg:FakeApp#configed
+test_interp = this:%(inherit)s
diff --git a/tests/sample_configs/test_config_included.ini b/tests/sample_configs/test_config_included.ini
new file mode 100644
index 0000000..cc0da7a
--- /dev/null
+++ b/tests/sample_configs/test_config_included.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+def2 = from include
+def3 = c
+
+[app:main]
+# Equivalent to the egg reference, but just for kicks...
+paste.app_factory = fakeapp.configapps:SimpleApp.make_app
+set glob = orig
+bob = your uncle
+another = BAR
diff --git a/tests/sample_configs/test_error.ini b/tests/sample_configs/test_error.ini
new file mode 100644
index 0000000..b6ad5b2
--- /dev/null
+++ b/tests/sample_configs/test_error.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+def1 = a
+def2 = b
+
+[app:main]
+use = egg:FakeApp#configed
+setting1 = foo
+setting2 = %(does_not_exist)s/bar
diff --git a/tests/sample_configs/test_filter.ini b/tests/sample_configs/test_filter.ini
new file mode 100644
index 0000000..bfad8dc
--- /dev/null
+++ b/tests/sample_configs/test_filter.ini
@@ -0,0 +1,22 @@
+[app:normal]
+use = egg:FakeApp#basic_app
+
+[pipeline:piped]
+pipeline = egg:FakeApp#caps normal
+
+[filter-app:filt]
+use = egg:FakeApp#caps
+method_to_call = lower
+next = normal
+
+[pipeline:piped2]
+pipeline = egg:FakeApp#caps2 normal
+
+[filter-app:filt2]
+use = egg:FakeApp#caps2
+method_to_call = lower
+next = normal
+
+[app:inv]
+use = egg:FakeApp#basic_app
+filter-with = egg:FakeApp#caps
diff --git a/tests/sample_configs/test_filter_with.ini b/tests/sample_configs/test_filter_with.ini
new file mode 100644
index 0000000..118804f
--- /dev/null
+++ b/tests/sample_configs/test_filter_with.ini
@@ -0,0 +1,12 @@
+[app:main]
+use = egg:FakeApp#basic_app
+example = test
+filter-with = filter1
+
+[filter:filter1]
+use = egg:FakeApp#caps
+filter-with = filter2
+
+[filter:filter2]
+use = egg:FakeApp#caps
+
diff --git a/tests/sample_configs/test_func.ini b/tests/sample_configs/test_func.ini
new file mode 100644
index 0000000..a0d28c4
--- /dev/null
+++ b/tests/sample_configs/test_func.ini
@@ -0,0 +1,13 @@
+[application:main]
+use = call:fakeapp.apps:make_basic_app
+
+[application:other]
+use = call:fakeapp.apps:make_basic_app2
+
+[composit:remote_addr]
+use = call:fakeapp.apps:make_remote_addr
+app.1 = main
+addr.1 = 127.0.0.1
+
+app.2 = other
+addr.2 = 0.0.0.0 \ No newline at end of file
diff --git a/tests/test_basic_app.py b/tests/test_basic_app.py
new file mode 100644
index 0000000..1ddb52b
--- /dev/null
+++ b/tests/test_basic_app.py
@@ -0,0 +1,36 @@
+from paste.deploy import loadapp
+
+from tests.fixture import *
+import fakeapp.apps
+
+
+here = os.path.dirname(__file__)
+
+
+def test_main():
+ app = loadapp('config:sample_configs/basic_app.ini',
+ relative_to=here)
+ assert app is fakeapp.apps.basic_app
+ app = loadapp('config:sample_configs/basic_app.ini#main',
+ relative_to=here)
+ assert app is fakeapp.apps.basic_app
+ app = loadapp('config:sample_configs/basic_app.ini',
+ relative_to=here, name='main')
+ assert app is fakeapp.apps.basic_app
+ app = loadapp('config:sample_configs/basic_app.ini#ignored',
+ relative_to=here, name='main')
+ assert app is fakeapp.apps.basic_app
+
+
+def test_other():
+ app = loadapp('config:sample_configs/basic_app.ini#other',
+ relative_to=here)
+ assert app is fakeapp.apps.basic_app2
+
+
+def test_composit():
+ app = loadapp('config:sample_configs/basic_app.ini#remote_addr',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.RemoteAddrDispatch)
+ assert app.map['127.0.0.1'] is fakeapp.apps.basic_app
+ assert app.map['0.0.0.0'] is fakeapp.apps.basic_app2
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..de40a2a
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,173 @@
+from nose.tools import eq_
+
+from paste.deploy import loadapp, appconfig
+from tests.fixture import *
+import fakeapp.configapps as fc
+import fakeapp.apps
+
+
+ini_file = 'config:sample_configs/test_config.ini'
+here = os.path.dirname(__file__)
+config_path = os.path.join(here, 'sample_configs')
+config_filename = os.path.join(config_path, 'test_config.ini')
+
+
+def test_config_egg():
+ app = loadapp('egg:FakeApp#configed')
+ assert isinstance(app, fc.SimpleApp)
+
+
+def test_config1():
+ app = loadapp(ini_file, relative_to=here, name='test1')
+ eq_(app.local_conf, {
+ 'setting1': 'foo',
+ 'setting2': 'bar',
+ 'apppath': os.path.join(config_path, 'app')})
+ eq_(app.global_conf, {
+ 'def1': 'a',
+ 'def2': 'b',
+ 'basepath': config_path,
+ 'here': config_path,
+ '__file__': config_filename})
+
+
+def test_config2():
+ app = loadapp(ini_file, relative_to=here, name='test2')
+ eq_(app.local_conf, {
+ 'local conf': 'something'})
+ eq_(app.global_conf, {
+ 'def1': 'test2',
+ 'def2': 'b',
+ 'basepath': config_path,
+ 'another': 'TEST',
+ 'here': config_path,
+ '__file__': config_filename})
+ # Run this to make sure the global-conf-modified test2
+ # didn't mess up the general global conf
+ test_config1()
+
+
+def test_config3():
+ app = loadapp(ini_file, relative_to=here, name='test3')
+ assert isinstance(app, fc.SimpleApp)
+ eq_(app.local_conf, {
+ 'local conf': 'something',
+ 'another': 'something more\nacross several\nlines'})
+ eq_(app.global_conf, {
+ 'def1': 'test3',
+ 'def2': 'b',
+ 'basepath': config_path,
+ 'another': 'TEST',
+ 'here': config_path,
+ '__file__': config_filename})
+ test_config2()
+
+
+def test_main():
+ app = loadapp('config:test_func.ini',
+ relative_to=config_path)
+ assert app is fakeapp.apps.basic_app
+ app = loadapp('config:test_func.ini#main',
+ relative_to=config_path)
+ assert app is fakeapp.apps.basic_app
+ app = loadapp('config:test_func.ini',
+ relative_to=config_path, name='main')
+ assert app is fakeapp.apps.basic_app
+ app = loadapp('config:test_func.ini#ignored',
+ relative_to=config_path, name='main')
+ assert app is fakeapp.apps.basic_app
+
+
+def test_other():
+ app = loadapp('config:test_func.ini#other', relative_to=config_path)
+ assert app is fakeapp.apps.basic_app2
+
+
+def test_composit():
+ app = loadapp('config:test_func.ini#remote_addr', relative_to=config_path)
+ assert isinstance(app, fakeapp.apps.RemoteAddrDispatch)
+ assert app.map['127.0.0.1'] is fakeapp.apps.basic_app
+ assert app.map['0.0.0.0'] is fakeapp.apps.basic_app2
+
+
+def test_foreign_config():
+ app = loadapp(ini_file, relative_to=here, name='test_foreign_config')
+ assert isinstance(app, fc.SimpleApp)
+ eq_(app.local_conf, {
+ 'another': 'FOO',
+ 'bob': 'your uncle'})
+ eq_(app.global_conf, {
+ 'def1': 'a',
+ 'def2': 'from include',
+ 'def3': 'c',
+ 'basepath': config_path,
+ 'glob': 'override',
+ 'here': config_path,
+ '__file__': os.path.join(config_path, 'test_config.ini')})
+
+
+def test_config_get():
+ app = loadapp(ini_file, relative_to=here, name='test_get')
+ assert isinstance(app, fc.SimpleApp)
+ eq_(app.local_conf, {
+ 'def1': 'a',
+ 'foo': 'TEST'})
+ eq_(app.global_conf, {
+ 'def1': 'a',
+ 'def2': 'TEST',
+ 'basepath': os.path.join(here, 'sample_configs'),
+ 'here': config_path,
+ '__file__': config_filename})
+
+
+def test_appconfig():
+ conf = appconfig(ini_file, relative_to=here, name='test_get')
+ eq_(conf, {
+ 'def1': 'a',
+ 'def2': 'TEST',
+ 'basepath': os.path.join(here, 'sample_configs'),
+ 'here': config_path,
+ '__file__': config_filename,
+ 'foo': 'TEST'})
+ eq_(conf.local_conf, {
+ 'def1': 'a',
+ 'foo': 'TEST'})
+ eq_(conf.global_conf, {
+ 'def1': 'a',
+ 'def2': 'TEST',
+ 'basepath': os.path.join(here, 'sample_configs'),
+ 'here': config_path,
+ '__file__': config_filename})
+
+
+def test_appconfig_filter_with():
+ conf = appconfig('config:test_filter_with.ini', relative_to=config_path)
+ eq_(conf['example'], 'test')
+
+
+def test_global_conf():
+ conf = appconfig(ini_file, relative_to=here, name='test_global_conf',
+ global_conf={'def2': 'TEST DEF 2', 'inherit': 'bazbar'})
+ eq_(conf, {
+ 'def1': 'a',
+ # Note that this gets overwritten:
+ 'def2': 'b',
+ 'basepath': os.path.join(here, 'sample_configs'),
+ 'here': config_path,
+ 'inherit': 'bazbar',
+ '__file__': config_filename,
+ 'test_interp': 'this:bazbar',
+ })
+ eq_(conf.local_conf, {
+ 'test_interp': 'this:bazbar'})
+
+
+def test_interpolate_exception():
+ try:
+ appconfig('config:test_error.ini', relative_to=config_path)
+ except Exception:
+ e = sys.exc_info()[1]
+ expected = "Error in file %s" % os.path.join(config_path, 'test_error.ini')
+ eq_(str(e).split(':')[0], expected)
+ else:
+ assert False, 'Should have raised an exception'
diff --git a/tests/test_config_middleware.py b/tests/test_config_middleware.py
new file mode 100644
index 0000000..cc315e3
--- /dev/null
+++ b/tests/test_config_middleware.py
@@ -0,0 +1,28 @@
+from nose.tools import assert_raises
+from nose.plugins.skip import SkipTest
+
+from paste.deploy.config import ConfigMiddleware
+
+
+class Bug(Exception):
+ pass
+
+
+def app_with_exception(environ, start_response):
+ def cont():
+ yield "something"
+ raise Bug
+ start_response('200 OK', [('Content-type', 'text/html')])
+ return cont()
+
+
+def test_error():
+ # This import is conditional due to Paste not yet working on py3k
+ try:
+ from paste.fixture import TestApp
+ except ImportError:
+ raise SkipTest
+
+ wrapped = ConfigMiddleware(app_with_exception, {'test': 1})
+ test_app = TestApp(wrapped)
+ assert_raises(Bug, test_app.get, '/')
diff --git a/tests/test_converters.py b/tests/test_converters.py
new file mode 100644
index 0000000..5361310
--- /dev/null
+++ b/tests/test_converters.py
@@ -0,0 +1,17 @@
+def test_asbool_truthy():
+ from paste.deploy.converters import asbool
+ assert asbool('true')
+ assert asbool('yes')
+ assert asbool('on')
+ assert asbool('y')
+ assert asbool('t')
+ assert asbool('1')
+
+def test_asbool_falsy():
+ from paste.deploy.converters import asbool
+ assert not asbool('false')
+ assert not asbool('no')
+ assert not asbool('off')
+ assert not asbool('n')
+ assert not asbool('f')
+ assert not asbool('0')
diff --git a/tests/test_filter.py b/tests/test_filter.py
new file mode 100644
index 0000000..a76af7c
--- /dev/null
+++ b/tests/test_filter.py
@@ -0,0 +1,53 @@
+from paste.deploy import loadapp
+from tests.fixture import *
+import fakeapp.apps
+
+
+here = os.path.dirname(__file__)
+
+
+def test_filter_app():
+ app = loadapp('config:sample_configs/test_filter.ini#filt',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.CapFilter)
+ assert app.app is fakeapp.apps.basic_app
+ assert app.method_to_call == 'lower'
+
+
+def test_pipeline():
+ app = loadapp('config:sample_configs/test_filter.ini#piped',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.CapFilter)
+ assert app.app is fakeapp.apps.basic_app
+ assert app.method_to_call == 'upper'
+
+
+def test_filter_app2():
+ app = loadapp('config:sample_configs/test_filter.ini#filt2',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.CapFilter)
+ assert app.app is fakeapp.apps.basic_app
+ assert app.method_to_call == 'lower'
+
+
+def test_pipeline2():
+ app = loadapp('config:sample_configs/test_filter.ini#piped2',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.CapFilter)
+ assert app.app is fakeapp.apps.basic_app
+ assert app.method_to_call == 'upper'
+
+
+def test_filter_app_inverted():
+ app = loadapp('config:sample_configs/test_filter.ini#inv',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.CapFilter)
+ assert app.app is fakeapp.apps.basic_app
+
+
+def test_filter_with_filter_with():
+ app = loadapp('config:sample_configs/test_filter_with.ini',
+ relative_to=here)
+ assert isinstance(app, fakeapp.apps.CapFilter)
+ assert isinstance(app.app, fakeapp.apps.CapFilter)
+ assert app.app.app is fakeapp.apps.basic_app
diff --git a/tests/test_load_package.py b/tests/test_load_package.py
new file mode 100644
index 0000000..b3fea55
--- /dev/null
+++ b/tests/test_load_package.py
@@ -0,0 +1,12 @@
+from pprint import pprint
+import sys
+
+import pkg_resources
+
+from paste.deploy.compat import print_
+
+
+def test_load_package():
+ print_('Path:')
+ pprint(sys.path)
+ print_(pkg_resources.require('FakeApp'))
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..4ac34fd
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,14 @@
+[tox]
+envlist = py26,py27,py32,py33,pypy
+
+[testenv]
+deps=nose
+ Paste
+commands={envpython} setup.py test
+
+# Keep it this way until Paste has been ported to py3k
+[testenv:py32]
+deps=nose
+
+[testenv:py33]
+deps=nose