diff options
29 files changed, 440 insertions, 351 deletions
@@ -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())) - @@ -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')) @@ -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 |