diff options
author | Alex Gr?nholm <alex.gronholm@nextday.fi> | 2011-05-24 02:08:17 +0300 |
---|---|---|
committer | Alex Gr?nholm <alex.gronholm@nextday.fi> | 2011-05-24 02:08:17 +0300 |
commit | 8c28df8cabf091366a145e16570e9d9c3617becd (patch) | |
tree | 914b24f48ed37ee33ce17394e5c08d72ecb052f8 | |
parent | f972b92de5ceab675c1c82b49c0c51ec02bba431 (diff) | |
parent | bce90c1b63ab7bc33de374a6c6a72fffe2af91b2 (diff) | |
download | pastedeploy-git-8c28df8cabf091366a145e16570e9d9c3617becd.tar.gz |
Added support for the call: protocol, contributed by Jason Stitt
-rw-r--r-- | docs/conf.py | 6 | ||||
-rw-r--r-- | docs/index.txt | 31 | ||||
-rw-r--r-- | docs/news.txt | 5 | ||||
-rw-r--r-- | paste/deploy/loadwsgi.py | 49 | ||||
-rw-r--r-- | paste/deploy/util.py | 14 | ||||
-rw-r--r-- | tests/sample_configs/test_func.ini | 13 | ||||
-rw-r--r-- | tests/test_config.py | 28 |
7 files changed, 135 insertions, 11 deletions
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 63a52d2..337347c 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -12,10 +12,13 @@ hg tip * Excluded tests from release distributions -* Dropped Python 2.4 support +* 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/loadwsgi.py b/paste/deploy/loadwsgi.py index 2da8213..fe24add 100644 --- a/paste/deploy/loadwsgi.py +++ b/paste/deploy/loadwsgi.py @@ -8,7 +8,7 @@ import re import pkg_resources from paste.deploy.compat import ConfigParser, unquote, iteritems -from paste.deploy.util import fix_call +from paste.deploy.util import fix_call, lookup_object __all__ = ['loadapp', 'loadserver', 'loadfilter', 'appconfig'] @@ -332,6 +332,14 @@ 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 ############################################################ @@ -475,6 +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'): + context.protocol = 'paste.app_factory' + elif section_protocol in ('composit', 'composite'): + context.protocol = 'paste.composit_factory' + else: + # This will work with 'server' and 'filter', otherwise it + # could fail but there is an error message already for + # bad protocols + context.protocol = 'paste.%s_factory' % context_protocol + return context def _context_from_explicit(self, object_type, local_conf, global_conf, @@ -644,6 +666,31 @@ class EggLoader(_Loader): return possible[0] +class FuncLoader(_Loader): + """ Loader that supports specifying functions inside modules, without + using eggs at all. Configuration should be in the format: + use = call:my.module.path:function_name + + Dot notation is supported in both the module and function name, e.g.: + use = call:my.module.path:object.method + """ + def __init__(self, spec): + self.spec = spec + if not ':' in spec: + raise LookupError("Configuration not in format module:function") + + def get_context(self, object_type, name=None, global_conf=None): + obj = lookup_object(self.spec) + return LoaderContext( + obj, + object_type, + None, # determine protocol from section type + global_conf or {}, + {}, + self, + ) + + class LoaderContext(object): def __init__(self, obj, object_type, protocol, diff --git a/paste/deploy/util.py b/paste/deploy/util.py index c5056b2..02d3fa2 100644 --- a/paste/deploy/util.py +++ b/paste/deploy/util.py @@ -58,3 +58,17 @@ def fix_call(callable, *args, **kw): exc_info = fix_type_error(None, callable, args, kw) reraise(*exc_info) return val + + +def lookup_object(spec): + """ + Looks up a module or object from a some.module:func_name specification. + To just look up a module, omit the colon and everything after it. + """ + parts, target = spec.split(':') if ':' in spec else (spec, None) + module = __import__(parts) + + for part in parts.split('.')[1:] + ([target] if target else []): + module = getattr(module, part) + + return module diff --git a/tests/sample_configs/test_func.ini b/tests/sample_configs/test_func.ini new file mode 100644 index 0000000..a0d28c4 --- /dev/null +++ b/tests/sample_configs/test_func.ini @@ -0,0 +1,13 @@ +[application:main] +use = call:fakeapp.apps:make_basic_app + +[application:other] +use = call:fakeapp.apps:make_basic_app2 + +[composit:remote_addr] +use = call:fakeapp.apps:make_remote_addr +app.1 = main +addr.1 = 127.0.0.1 + +app.2 = other +addr.2 = 0.0.0.0
\ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py index b6cd158..6dee066 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,6 +3,7 @@ from nose.tools import eq_ from paste.deploy import loadapp, appconfig from tests.fixture import * import fakeapp.configapps as fc +import fakeapp.apps ini_file = 'config:sample_configs/test_config.ini' @@ -62,6 +63,33 @@ def test_config3(): 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) |