summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore9
-rw-r--r--MANIFEST.in3
-rw-r--r--docs/conf.py6
-rw-r--r--docs/index.txt31
-rw-r--r--docs/news.txt13
-rw-r--r--paste/deploy/__init__.py9
-rw-r--r--paste/deploy/compat.py30
-rw-r--r--paste/deploy/config.py37
-rw-r--r--paste/deploy/converters.py17
-rw-r--r--paste/deploy/epdesc.py5
-rw-r--r--paste/deploy/interfaces.py22
-rw-r--r--paste/deploy/loadwsgi.py132
-rw-r--r--paste/deploy/paster_templates.py17
-rw-r--r--paste/deploy/util.py (renamed from paste/deploy/util/fixtypeerror.py)33
-rw-r--r--paste/deploy/util/__init__.py3
-rw-r--r--paste/deploy/util/threadinglocal.py39
-rw-r--r--setup.py26
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/finddata.py95
-rw-r--r--tests/fixture.py3
-rw-r--r--tests/sample_configs/test_config.ini2
-rw-r--r--tests/sample_configs/test_error.ini8
-rw-r--r--tests/test_basic_app.py12
-rw-r--r--tests/test_config.py145
-rw-r--r--tests/test_config_middleware.py16
-rw-r--r--tests/test_filter.py11
-rw-r--r--tests/test_func_loader.py32
-rw-r--r--tests/test_load_package.py14
-rw-r--r--tox.ini20
29 files changed, 440 insertions, 351 deletions
diff --git a/.hgignore b/.hgignore
index 5345d54..ff958ef 100644
--- a/.hgignore
+++ b/.hgignore
@@ -3,3 +3,12 @@ syntax: glob
dist/
build/
docs/_build/
+
+syntax: glob
+.tox
+syntax: glob
+.project
+syntax: glob
+.pydevproject
+syntax: regexp
+^tests/fake_packages/FakeApp\.egg$ \ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index e5deda2..7a2ffb9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,2 @@
-include docs/*.html
include docs/*.txt
-exclude docs/rebuild
+recursive-include paste/deploy/paster_templates *
diff --git a/docs/conf.py b/docs/conf.py
index 1b256af..76beb2f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -34,15 +34,15 @@ master_doc = 'index'
# General substitutions.
project = 'Paste Deploy'
-copyright = '2010, Ian Bicking and contributors'
+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.3'
+version = '1.5'
# The full version, including alpha/beta/rc tags.
-release = '1.3.4'
+release = '1.5.0dev'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
diff --git a/docs/index.txt b/docs/index.txt
index c23e270..3150e59 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -126,6 +126,7 @@ multiple applications using `paste.urlmap
use = egg:Paste#urlmap
/ = home
/blog = blog
+ /wiki = wiki
/cms = config:cms.ini
[app:home]
@@ -142,6 +143,10 @@ multiple applications using `paste.urlmap
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]
@@ -155,7 +160,7 @@ 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" and "config:cms.ini". The last
+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::
@@ -173,7 +178,7 @@ 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).
-Lastly::
+Then::
[filter-app:blog]
use = egg:Authentication#auth
@@ -195,6 +200,19 @@ That last section is just a reference to an application that you
probably installed with ``easy_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
@@ -269,6 +287,10 @@ first is to refer to another URI or name::
[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
@@ -603,7 +625,7 @@ An example might look like::
s.serve_forever()
return serve
-An implementation of ``Server`` is left to the user.
+The implementation of ``Server`` is left to the user.
``paste.server_runner``
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -614,9 +636,6 @@ first argument, and the server should run immediately.
Outstanding Issues
------------------
-* Should add a ``python:`` scheme for loading objects out of modules
- directly. It has to include the protocol somehow...?
-
* 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
diff --git a/docs/news.txt b/docs/news.txt
index a48214a..337347c 100644
--- a/docs/news.txt
+++ b/docs/news.txt
@@ -4,8 +4,21 @@ Paste Deployment News
hg tip
------
+* 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
+
1.3.4
-----
diff --git a/paste/deploy/__init__.py b/paste/deploy/__init__.py
index f89218f..94c63a8 100644
--- a/paste/deploy/__init__.py
+++ b/paste/deploy/__init__.py
@@ -1,10 +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 loadwsgi import *
-try:
- from config import CONFIG
-except ImportError:
- # @@: Or should we require Paste? Or should we put threadlocal
- # into this package too?
- pass
-
+from paste.deploy.loadwsgi import *
diff --git a/paste/deploy/compat.py b/paste/deploy/compat.py
new file mode 100644
index 0000000..f7b93f5
--- /dev/null
+++ b/paste/deploy/compat.py
@@ -0,0 +1,30 @@
+# (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()
+
+ 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()
+
+ def reraise(t, e, tb):
+ exec('raise e from tb', dict(e=e, tb=tb))
diff --git a/paste/deploy/config.py b/paste/deploy/config.py
index d690ff3..1db5680 100644
--- a/paste/deploy/config.py
+++ b/paste/deploy/config.py
@@ -3,25 +3,27 @@
"""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:
- from paste.deploy.util.threadinglocal import local
- config_local = local()
+ config_local = threading.local()
config_local.wsgi_dict = result = {}
return result
except AttributeError:
config_local.wsgi_dict = result = {}
return result
+
class DispatchingConfig(object):
"""
@@ -45,7 +47,7 @@ class DispatchingConfig(object):
self.dispatching_id = 0
while 1:
self._local_key = 'paste.processconfig_%i' % self.dispatching_id
- if not local_dict().has_key(self._local_key):
+ if not self._local_key in local_dict():
break
self.dispatching_id += 1
finally:
@@ -94,7 +96,7 @@ class DispatchingConfig(object):
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:
@@ -123,7 +125,7 @@ class DispatchingConfig(object):
def __contains__(self, key):
# I thought __getattr__ would catch this, but apparently not
- return self.has_key(key)
+ return key in self
def __setitem__(self, key, value):
# I thought __getattr__ would catch this, but apparently not
@@ -132,6 +134,7 @@ class DispatchingConfig(object):
CONFIG = DispatchingConfig()
+
class ConfigMiddleware(object):
"""
@@ -156,7 +159,7 @@ class ConfigMiddleware(object):
from paste import wsgilib
popped_config = None
if 'paste.config' in environ:
- popped_config = environ['paste.config']
+ popped_config = environ['paste.config']
conf = environ['paste.config'] = self.config.copy()
app_iter = None
CONFIG.push_thread_config(conf)
@@ -182,6 +185,7 @@ class ConfigMiddleware(object):
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)
@@ -189,16 +193,17 @@ def make_config_filter(app, global_conf, **local_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
+
+ 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
+ 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
+ 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
@@ -214,9 +219,9 @@ class PrefixMiddleware(object):
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]
@@ -228,7 +233,7 @@ class PrefixMiddleware(object):
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``,
+ 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
@@ -250,11 +255,12 @@ class PrefixMiddleware(object):
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 = '/'
+ if not url:
+ url = '/'
environ['PATH_INFO'] = url
environ['SCRIPT_NAME'] = self.prefix
if self.translate_forwarded_server:
@@ -284,6 +290,7 @@ class PrefixMiddleware(object):
environ['wsgi.url_scheme'] = self.scheme
return self.app(environ, start_response)
+
def make_prefix_middleware(
app, global_conf, prefix='/',
translate_forwarded_server=True,
diff --git a/paste/deploy/converters.py b/paste/deploy/converters.py
index 98edd8f..f37d267 100644
--- a/paste/deploy/converters.py
+++ b/paste/deploy/converters.py
@@ -1,26 +1,29 @@
# (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
+
+
def asbool(obj):
- if isinstance(obj, (str, unicode)):
+ if isinstance(obj, basestring):
obj = obj.strip().lower()
if obj in ['true', 'yes', 'on', 'y', 't', '1']:
return True
elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
return False
else:
- raise ValueError(
- "String is not true/false: %r" % obj)
+ raise ValueError("String is not true/false: %r" % obj)
return bool(obj)
+
def asint(obj):
try:
return int(obj)
- except (TypeError, ValueError), e:
- raise ValueError(
- "Bad integer value: %r" % obj)
+ except (TypeError, ValueError):
+ raise ValueError("Bad integer value: %r" % obj)
+
def aslist(obj, sep=None, strip=True):
- if isinstance(obj, (str, unicode)):
+ if isinstance(obj, basestring):
lst = obj.split(sep)
if strip:
lst = [v.strip() for v in lst]
diff --git a/paste/deploy/epdesc.py b/paste/deploy/epdesc.py
index 7c7deae..5f05175 100644
--- a/paste/deploy/epdesc.py
+++ b/paste/deploy/epdesc.py
@@ -3,6 +3,7 @@ class AppFactoryDescription(object):
This gives a factory/function that can create WSGI apps
"""
+
class CompositeFactoryDescription(object):
description = """
This gives a factory/function that can create WSGI apps, and has
@@ -10,12 +11,14 @@ class CompositeFactoryDescription(object):
apps based on name.
"""
+
class FilterAppFactoryDescription(object):
description = """
This gives a factory/function that wraps a WSGI application to
create another WSGI application (typically applying middleware)
"""
+
class FilterFactoryDescription(object):
description = """
This gives a factory/function that return a function that can wrap
@@ -23,6 +26,7 @@ class FilterFactoryDescription(object):
paste.filter_app_factory is the same thing with less layers.
"""
+
class ServerFactoryDescription(object):
description = """
This gives a factory/function that creates a server, that can be
@@ -30,6 +34,7 @@ class ServerFactoryDescription(object):
paste.server_runner is the same thing with less layers.
"""
+
class ServerRunnerDescription(object):
description = """
This gives a factory/function that, given a WSGI application and
diff --git a/paste/deploy/interfaces.py b/paste/deploy/interfaces.py
index 29163a5..3dbc44e 100644
--- a/paste/deploy/interfaces.py
+++ b/paste/deploy/interfaces.py
@@ -1,13 +1,15 @@
# (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
+
############################################################
## Functions
############################################################
+
def loadapp(uri, name=None, relative_to=None, global_conf=None):
"""
Provided by ``paste.deploy.loadapp``.
-
+
Load the specified URI as a WSGI application (returning IWSGIApp).
The ``name`` can be in the URI (typically as ``#name``). If it is
and ``name`` is given, the keyword argument overrides the URI.
@@ -19,6 +21,7 @@ def loadapp(uri, name=None, relative_to=None, global_conf=None):
override the values). ``global_conf`` is copied before modifying.
"""
+
def loadfilter(uri, name=None, relative_to=None, global_conf=None):
"""
Provided by ``paste.deploy.loadfilter``.
@@ -26,6 +29,7 @@ def loadfilter(uri, name=None, relative_to=None, global_conf=None):
Like ``loadapp()``, except returns in IFilter object.
"""
+
def loadserver(uri, name=None, relative_to=None, global_conf=None):
"""
Provided by ``paste.deploy.loadserver``.
@@ -33,10 +37,12 @@ def loadserver(uri, name=None, relative_to=None, global_conf=None):
Like ``loadapp()``, except returns in IServer object.
"""
+
############################################################
## Factories
############################################################
+
class IPasteAppFactory(object):
"""
@@ -55,6 +61,7 @@ class IPasteAppFactory(object):
capture these values).
"""
+
class IPasteCompositFactory(object):
"""
@@ -73,6 +80,7 @@ class IPasteCompositFactory(object):
applications.
"""
+
class IPasteFilterFactory(object):
"""
@@ -85,13 +93,14 @@ class IPasteFilterFactory(object):
Returns a IFilter object.
"""
+
class IPasteFilterAppFactory(object):
"""
This is the spec for the ``paste.filter_app_factory``
protocol/entry_point.
"""
-
+
def __call__(wsgi_app, global_conf, **local_conf):
"""
Returns a WSGI application that wraps ``wsgi_app``.
@@ -100,6 +109,7 @@ class IPasteFilterAppFactory(object):
objects that implement the IFilter interface.
"""
+
class IPasteServerFactory(object):
"""
@@ -112,6 +122,7 @@ class IPasteServerFactory(object):
Returns a IServer object.
"""
+
class IPasteServerRunner(object):
"""
@@ -129,6 +140,7 @@ class IPasteServerRunner(object):
objects that implement the IServer interface.
"""
+
class ILoader(object):
"""
@@ -151,16 +163,18 @@ class ILoader(object):
"""
Return an IFilter object, like ``get_app``.
"""
-
+
def get_server(name_or_uri, global_conf=None):
"""
Return an IServer object, like ``get_app``.
"""
+
############################################################
## Objects
############################################################
+
class IWSGIApp(object):
"""
@@ -175,6 +189,7 @@ class IWSGIApp(object):
an iterator for the body of the response.
"""
+
class IFilter(object):
"""
@@ -188,6 +203,7 @@ class IFilter(object):
``wsgi_app`` passed in.
"""
+
class IServer(object):
"""
diff --git a/paste/deploy/loadwsgi.py b/paste/deploy/loadwsgi.py
index 6b373d0..fe24add 100644
--- a/paste/deploy/loadwsgi.py
+++ b/paste/deploy/loadwsgi.py
@@ -1,20 +1,26 @@
# (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 urllib
-from ConfigParser import ConfigParser
+
import pkg_resources
-from paste.deploy.util.fixtypeerror import fix_call
+
+from paste.deploy.compat import ConfigParser, unquote, iteritems
+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)
+ return pkg_resources.EntryPoint.parse("x=" + s).load(False)
+
def _aslist(obj):
"""
@@ -28,6 +34,7 @@ def _aslist(obj):
else:
return [obj]
+
def _flatten(lst):
"""
Flatten a nested list.
@@ -39,11 +46,16 @@ def _flatten(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
@@ -52,25 +64,51 @@ class NicerConfigParser(ConfigParser):
Mainly to support defaults using values such as %(here)s
"""
defaults = ConfigParser.defaults(self).copy()
- for key, val in defaults.iteritems():
- defaults[key] = self._interpolate('DEFAULT', key, val, defaults)
+ 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:
+ except Exception:
+ e = sys.exc_info()[1]
args = list(e.args)
args[0] = 'Error in file %s, [%s] %s=%r: %s' % (
self.filename, section, option, rawval, 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] %s=%r: %s' % (
+ parser.filename, section, option, value, e)
+ e.args = tuple(args)
+ e.message = args[0]
+ raise
+
+
############################################################
## Object types
############################################################
+
class _ObjectType(object):
name = None
@@ -79,8 +117,8 @@ class _ObjectType(object):
def __init__(self):
# Normalize these variables:
- self.egg_protocols = map(_aslist, _aslist(self.egg_protocols))
- self.config_prefixes = map(_aslist, _aslist(self.config_prefixes))
+ 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>' % (
@@ -91,6 +129,7 @@ class _ObjectType(object):
return fix_call(context.object,
context.global_conf, **context.local_conf)
+
class _App(_ObjectType):
name = 'application'
@@ -112,6 +151,7 @@ class _App(_ObjectType):
APP = _App()
+
class _Filter(_ObjectType):
name = 'filter'
egg_protocols = [['paste.filter_factory', 'paste.filter_app_factory']]
@@ -133,6 +173,7 @@ class _Filter(_ObjectType):
FILTER = _Filter()
+
class _Server(_ObjectType):
name = 'server'
egg_protocols = [['paste.server_factory', 'paste.server_runner']]
@@ -154,6 +195,7 @@ class _Server(_ObjectType):
SERVER = _Server()
+
# Virtual type: (@@: There's clearly something crufty here;
# this probably could be more elegant)
class _PipeLine(_ObjectType):
@@ -169,6 +211,7 @@ class _PipeLine(_ObjectType):
PIPELINE = _PipeLine()
+
class _FilterApp(_ObjectType):
name = 'filter_app'
@@ -179,6 +222,7 @@ class _FilterApp(_ObjectType):
FILTER_APP = _FilterApp()
+
class _FilterWith(_App):
name = 'filtered_with'
@@ -195,19 +239,24 @@ class _FilterWith(_App):
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,
@@ -216,6 +265,7 @@ def appconfig(uri, name=None, relative_to=None, global_conf=None):
_loaders = {}
+
def loadobj(object_type, uri, name=None, relative_to=None,
global_conf=None):
context = loadcontext(
@@ -223,6 +273,7 @@ def loadobj(object_type, uri, name=None, relative_to=None,
global_conf=global_conf)
return context.create()
+
def loadcontext(object_type, uri, name=None, relative_to=None,
global_conf=None):
if '#' in uri:
@@ -246,6 +297,7 @@ def loadcontext(object_type, uri, name=None, relative_to=None,
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)
@@ -263,7 +315,7 @@ def _loadconfig(object_type, uri, path, name, relative_to,
path = relative_to + '/' + path
if path.startswith('///'):
path = path[2:]
- path = urllib.unquote(path)
+ path = unquote(path)
loader = ConfigLoader(path)
if global_conf:
loader.update_defaults(global_conf, overwrite=False)
@@ -271,6 +323,7 @@ def _loadconfig(object_type, uri, path, name, relative_to,
_loaders['config'] = _loadconfig
+
def _loadegg(object_type, uri, spec, name, relative_to,
global_conf):
loader = EggLoader(spec)
@@ -278,18 +331,20 @@ def _loadegg(object_type, uri, spec, name, relative_to,
_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):
@@ -317,6 +372,7 @@ class _Loader(object):
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
@@ -325,31 +381,22 @@ class _Loader(object):
return False
return self._absolute_re.search(name)
+
class ConfigLoader(_Loader):
def __init__(self, filename):
self.filename = filename = filename.strip()
- self.parser = NicerConfigParser(self.filename)
- # Don't lower-case keys:
- self.parser.optionxform = str
- # Stupid ConfigParser ignores files that aren't found, so
- # we have to add an extra check:
- if not os.path.exists(filename):
- if filename.strip() != filename:
- raise IOError(
- "File %r not found; trailing whitespace: "
- "did you try to use a # on the same line as a filename? "
- "(comments must be on their own line)" % filename)
- raise IOError(
- "File %r not found" % filename)
- self.parser.read(filename)
- self.parser._defaults.setdefault(
- 'here', os.path.dirname(os.path.abspath(filename)))
- self.parser._defaults.setdefault(
- '__file__', os.path.abspath(filename))
+ defaults = {
+ 'here': os.path.dirname(os.path.abspath(filename)),
+ '__file__': os.path.abspath(filename)
+ }
+ self.parser = NicerConfigParser(filename, defaults=defaults)
+ self.parser.optionxform = str # Don't lower-case keys
+ with open(filename) as f:
+ self.parser.read_file(f)
def update_defaults(self, new_defaults, overwrite=True):
- for key, value in new_defaults.items():
+ for key, value in iteritems(new_defaults):
if not overwrite and key in self.parser._defaults:
continue
self.parser._defaults[key] = value
@@ -436,20 +483,20 @@ class ConfigLoader(_Loader):
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'):
+ if section_protocol in ('application', 'app'):
context.protocol = 'paste.app_factory'
- elif section_protocol in ('composit','composite'):
+ 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' % context_protocol
-
+
return context
def _context_from_explicit(self, object_type, local_conf, global_conf,
@@ -556,8 +603,8 @@ class ConfigLoader(_Loader):
found.append(name_prefix)
name = 'main'
for section in sections:
- if section.startswith(name_prefix+':'):
- if section[len(name_prefix)+1:].strip() == name:
+ if section.startswith(name_prefix + ':'):
+ if section[len(name_prefix) + 1:].strip() == name:
found.append(section)
return found
@@ -629,17 +676,11 @@ class FuncLoader(_Loader):
"""
def __init__(self, spec):
self.spec = spec
- try:
- self.module_path, self.func_name = self.spec.split(':')
- except ValueError:
+ if not ':' in spec:
raise LookupError("Configuration not in format module:function")
- self.module_name = self.module_path.split('.', 1)[-1]
def get_context(self, object_type, name=None, global_conf=None):
- module = __import__(self.module_path, {}, {}, [self.module_name], 0)
- obj = module
- for part in self.func_name.split('.'):
- obj = getattr(obj, part)
+ obj = lookup_object(self.spec)
return LoaderContext(
obj,
object_type,
@@ -678,6 +719,7 @@ class LoaderContext(object):
conf.context = self
return conf
+
class AttrDict(dict):
"""
A dictionary that can be assigned to.
diff --git a/paste/deploy/paster_templates.py b/paste/deploy/paster_templates.py
index 4434898..9c5f942 100644
--- a/paste/deploy/paster_templates.py
+++ b/paste/deploy/paster_templates.py
@@ -1,15 +1,19 @@
# (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):
@@ -26,8 +30,7 @@ class PasteDeploy(Template):
' 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
-
+ 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/util/fixtypeerror.py b/paste/deploy/util.py
index b1fa0ec..02d3fa2 100644
--- a/paste/deploy/util/fixtypeerror.py
+++ b/paste/deploy/util.py
@@ -1,18 +1,17 @@
# (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
-"""
-Fixes the vague error message that you get when calling a function
-with the wrong arguments.
-"""
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:
@@ -28,7 +27,6 @@ def fix_type_error(exc_info, callable, varargs, kwargs):
or getattr(exc_info[1], '_type_error_fixed', False)):
return exc_info
exc_info[1]._type_error_fixed = True
- import inspect
argspec = inspect.formatargspec(*inspect.getargspec(callable))
args = ', '.join(map(_short_repr, varargs))
if kwargs and args:
@@ -42,20 +40,35 @@ def fix_type_error(exc_info, callable, varargs, kwargs):
exc_info[1].args = (msg,)
return exc_info
+
def _short_repr(v):
v = repr(v)
if len(v) > 12:
- v = v[:8]+'...'+v[-4:]
+ v = v[:8] + '...' + v[-4:]
return v
+
def fix_call(callable, *args, **kw):
"""
- Call ``callable(*args, **kw)`` fixing any type errors that come
- out.
+ Call ``callable(*args, **kw)`` fixing any type errors that come out.
"""
try:
val = callable(*args, **kw)
except TypeError:
exc_info = fix_type_error(None, callable, args, kw)
- raise exc_info[0], exc_info[1], exc_info[2]
+ 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/paste/deploy/util/__init__.py b/paste/deploy/util/__init__.py
deleted file mode 100644
index 56bf54c..0000000
--- a/paste/deploy/util/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# (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
-#
diff --git a/paste/deploy/util/threadinglocal.py b/paste/deploy/util/threadinglocal.py
deleted file mode 100644
index 57afa17..0000000
--- a/paste/deploy/util/threadinglocal.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# (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 threading
-except ImportError:
- # No threads, so "thread local" means process-global
- class local(object):
- pass
-else:
- try:
- local = threading.local
- except AttributeError:
- # Added in 2.4, but now we'll have to define it ourselves
- import thread
- class local(object):
-
- def __init__(self):
- self.__dict__['__objs'] = {}
-
- def __getattr__(self, attr, g=thread.get_ident):
- try:
- return self.__dict__['__objs'][g()][attr]
- except KeyError:
- raise AttributeError(
- "No variable %s defined for the thread %s"
- % (attr, g()))
-
- def __setattr__(self, attr, value, g=thread.get_ident):
- self.__dict__['__objs'].setdefault(g(), {})[attr] = value
-
- def __delattr__(self, attr, g=thread.get_ident):
- try:
- del self.__dict__['__objs'][g()][attr]
- except KeyError:
- raise AttributeError(
- "No variable %s defined for thread %s"
- % (attr, g()))
-
diff --git a/setup.py b/setup.py
index 1959175..d3127a7 100644
--- a/setup.py
+++ b/setup.py
@@ -1,16 +1,9 @@
from setuptools import setup, find_packages
-version = '1.3.4'
-
-import os
-
-here = os.path.dirname(os.path.abspath(__file__))
-finddata_py = os.path.join(here, 'tests', 'finddata.py')
-execfile(finddata_py)
setup(
name="PasteDeploy",
- version=version,
+ version='1.5.0dev',
description="Load, configure, and compose WSGI applications and servers",
long_description="""\
This tool provides code to load WSGI applications and servers from
@@ -30,22 +23,29 @@ For the latest changes see the `news file
"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",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
- "Topic :: Software Development :: Libraries :: Python Modules",
"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'),
- package_data=find_package_data(
- exclude_directories=standard_exclude_directories + ('tests',)),
+ packages=find_packages(exclude=['tests']),
+ include_package_data=True,
zip_safe=False,
test_suite='nose.collector',
tests_require=['nose>=0.11'],
@@ -70,4 +70,4 @@ For the latest changes see the `news file
paste.server_factory = paste.deploy.epdesc:ServerFactoryDescription
paste.server_runner = paste.deploy.epdesc:ServerRunnerDescription
""",
- )
+)
diff --git a/tests/__init__.py b/tests/__init__.py
index 0ba8f8a..cffe526 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -11,4 +11,3 @@ import pkg_resources
# Make absolutely sure we're testing *this* package, not
# some other installed package
pkg_resources.require('PasteDeploy')
-
diff --git a/tests/finddata.py b/tests/finddata.py
deleted file mode 100644
index 16057dc..0000000
--- a/tests/finddata.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# (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
-# Note: you may want to copy this into your setup.py file verbatim, as
-# you can't import this from another package, when you don't know if
-# that package is installed yet.
-
-import os
-import sys
-from fnmatch import fnmatchcase
-from distutils.util import convert_path
-
-# Provided as an attribute, so you can append to these instead
-# of replicating them:
-standard_exclude = ('*.py', '*.pyc', '*$py.class', '*~', '.*', '*.bak')
-standard_exclude_directories = ('.*', 'CVS', '_darcs', './build',
- './dist', 'EGG-INFO', '*.egg-info')
-
-def find_package_data(
- where='.', package='',
- exclude=standard_exclude,
- exclude_directories=standard_exclude_directories,
- only_in_packages=True,
- show_ignored=False):
- """
- Return a dictionary suitable for use in ``package_data``
- in a distutils ``setup.py`` file.
-
- The dictionary looks like::
-
- {'package': [files]}
-
- Where ``files`` is a list of all the files in that package that
- don't match anything in ``exclude``.
-
- If ``only_in_packages`` is true, then top-level directories that
- are not packages won't be included (but directories under packages
- will).
-
- Directories matching any pattern in ``exclude_directories`` will
- be ignored; by default directories with leading ``.``, ``CVS``,
- and ``_darcs`` will be ignored.
-
- If ``show_ignored`` is true, then all the files that aren't
- included in package data are shown on stderr (for debugging
- purposes).
-
- Note patterns use wildcards, or can be exact paths (including
- leading ``./``), and all searching is case-insensitive.
- """
-
- out = {}
- stack = [(convert_path(where), '', package, only_in_packages)]
- while stack:
- where, prefix, package, only_in_packages = stack.pop(0)
- for name in os.listdir(where):
- fn = os.path.join(where, name)
- if os.path.isdir(fn):
- bad_name = False
- for pattern in exclude_directories:
- if (fnmatchcase(name, pattern)
- or fn.lower() == pattern.lower()):
- bad_name = True
- if show_ignored:
- print >> sys.stderr, (
- "Directory %s ignored by pattern %s"
- % (fn, pattern))
- break
- if bad_name:
- continue
- if (os.path.isfile(os.path.join(fn, '__init__.py'))
- and not prefix):
- if not package:
- new_package = name
- else:
- new_package = package + '.' + name
- stack.append((fn, '', new_package, False))
- else:
- stack.append((fn, prefix + name + '/', package, only_in_packages))
- elif package or not only_in_packages:
- # is a file
- bad_name = False
- for pattern in exclude:
- if (fnmatchcase(name, pattern)
- or fn.lower() == pattern.lower()):
- bad_name = True
- if show_ignored:
- print >> sys.stderr, (
- "File %s ignored by pattern %s"
- % (fn, pattern))
- break
- if bad_name:
- continue
- out.setdefault(package, []).append(prefix+name)
- return out
-
diff --git a/tests/fixture.py b/tests/fixture.py
index 6c3e99f..751659d 100644
--- a/tests/fixture.py
+++ b/tests/fixture.py
@@ -12,10 +12,9 @@ if not os.path.exists(egg_info_dir):
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/test_config.ini b/tests/sample_configs/test_config.ini
index 69bae5a..d614829 100644
--- a/tests/sample_configs/test_config.ini
+++ b/tests/sample_configs/test_config.ini
@@ -1,11 +1,13 @@
[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
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/test_basic_app.py b/tests/test_basic_app.py
index 11d1f40..1ddb52b 100644
--- a/tests/test_basic_app.py
+++ b/tests/test_basic_app.py
@@ -1,9 +1,12 @@
-from paste.deploy import loadapp, loadfilter, appconfig
-from fixture import *
+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)
@@ -18,11 +21,12 @@ def test_main():
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',
@@ -30,5 +34,3 @@ def test_composit():
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
index 26ba482..6dee066 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,116 +1,173 @@
-import os
-from paste.deploy import loadapp, loadfilter, appconfig
-from fixture import *
+from nose.tools import eq_
+
+from paste.deploy import loadapp, appconfig
+from tests.fixture import *
import fakeapp.configapps as fc
-from pprint import pprint
+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')
- assert app.local_conf == {
- 'setting1': 'foo', 'setting2': 'bar'}
- assert app.global_conf == {
- 'def1': 'a', 'def2': 'b',
+ 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}
+ '__file__': config_filename})
+
def test_config2():
app = loadapp(ini_file, relative_to=here, name='test2')
- assert app.local_conf == {
- 'local conf': 'something'}
- assert app.global_conf == {
+ 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}
+ '__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)
- assert app.local_conf == {
+ eq_(app.local_conf, {
'local conf': 'something',
- 'another': 'something more\nacross several\nlines'}
- assert app.global_conf == {
+ '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}
+ '__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)
- assert app.local_conf == {
+ eq_(app.local_conf, {
'another': 'FOO',
- 'bob': 'your uncle'}
- pprint(app.global_conf)
- assert app.global_conf == {
+ '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')}
-
+ '__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)
- assert app.local_conf == {
+ eq_(app.local_conf, {
'def1': 'a',
- 'foo': 'TEST'}
- assert app.global_conf == {
+ 'foo': 'TEST'})
+ eq_(app.global_conf, {
'def1': 'a',
'def2': 'TEST',
+ 'basepath': os.path.join(here, 'sample_configs'),
'here': config_path,
- '__file__': config_filename}
+ '__file__': config_filename})
+
def test_appconfig():
conf = appconfig(ini_file, relative_to=here, name='test_get')
- assert conf == {
+ eq_(conf, {
'def1': 'a',
'def2': 'TEST',
+ 'basepath': os.path.join(here, 'sample_configs'),
'here': config_path,
'__file__': config_filename,
- 'foo': 'TEST'}
- assert conf.local_conf == {
+ 'foo': 'TEST'})
+ eq_(conf.local_conf, {
'def1': 'a',
- 'foo': 'TEST'}
- assert conf.global_conf == {
+ 'foo': 'TEST'})
+ eq_(conf.global_conf, {
'def1': 'a',
'def2': 'TEST',
+ 'basepath': os.path.join(here, 'sample_configs'),
'here': config_path,
- '__file__': config_filename,}
+ '__file__': config_filename})
+
def test_appconfig_filter_with():
conf = appconfig('config:test_filter_with.ini', relative_to=config_path)
- assert conf['example'] == 'test'
+ 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'})
- pprint(conf)
- assert 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',
- }
- assert conf.local_conf == {
- '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
index 868e75f..cc315e3 100644
--- a/tests/test_config_middleware.py
+++ b/tests/test_config_middleware.py
@@ -1,8 +1,12 @@
from nose.tools import assert_raises
+from nose.plugins.skip import SkipTest
+
from paste.deploy.config import ConfigMiddleware
-from paste.fixture import TestApp
-class Bug(Exception): pass
+
+class Bug(Exception):
+ pass
+
def app_with_exception(environ, start_response):
def cont():
@@ -11,8 +15,14 @@ def app_with_exception(environ, start_response):
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_filter.py b/tests/test_filter.py
index 77ee2ee..a76af7c 100644
--- a/tests/test_filter.py
+++ b/tests/test_filter.py
@@ -1,9 +1,11 @@
-from paste.deploy import loadapp, loadfilter
-from fixture import *
+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)
@@ -11,6 +13,7 @@ def test_filter_app():
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)
@@ -18,6 +21,7 @@ def test_pipeline():
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)
@@ -25,6 +29,7 @@ def test_filter_app2():
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)
@@ -32,12 +37,14 @@ def test_pipeline2():
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)
diff --git a/tests/test_func_loader.py b/tests/test_func_loader.py
deleted file mode 100644
index a04632d..0000000
--- a/tests/test_func_loader.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from paste.deploy import loadapp, loadfilter, appconfig
-from fixture import *
-import fakeapp.apps
-
-here = os.path.abspath(os.path.dirname(__file__))
-
-def test_main():
- app = loadapp('config:sample_configs/test_func.ini',
- relative_to=here)
- assert app is fakeapp.apps.basic_app
- app = loadapp('config:sample_configs/test_func.ini#main',
- relative_to=here)
- assert app is fakeapp.apps.basic_app
- app = loadapp('config:sample_configs/test_func.ini',
- relative_to=here, name='main')
- assert app is fakeapp.apps.basic_app
- app = loadapp('config:sample_configs/test_func.ini#ignored',
- relative_to=here, name='main')
- assert app is fakeapp.apps.basic_app
-
-def test_other():
- app = loadapp('config:sample_configs/test_func.ini#other',
- relative_to=here)
- assert app is fakeapp.apps.basic_app2
-
-
-def test_composit():
- app = loadapp('config:sample_configs/test_func.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_load_package.py b/tests/test_load_package.py
index a66b7d0..b3fea55 100644
--- a/tests/test_load_package.py
+++ b/tests/test_load_package.py
@@ -1,10 +1,12 @@
-import sys, os
-import pkg_resources
-import site
from pprint import pprint
+import sys
+
+import pkg_resources
+
+from paste.deploy.compat import print_
+
def test_load_package():
- print 'Path:'
+ print_('Path:')
pprint(sys.path)
- print pkg_resources.require('FakeApp')
-
+ print_(pkg_resources.require('FakeApp'))
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..07853f5
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,20 @@
+[tox]
+envlist = py25,py26,py27,py31,py32,jython,pypy1.4,pypy1.5
+
+[testenv]
+deps=nose
+ Paste
+commands={envpython} setup.py test
+
+# Keep it this way until Paste has been ported to py3k
+[testenv:py31]
+deps=nose
+
+[testenv:py32]
+deps=nose
+
+[testenv:pypy1.4]
+basepython=pypy1.4
+
+[testenv:pypy1.5]
+basepython=pypy1.5