summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gr?nholm <alex.gronholm@nextday.fi>2011-05-24 02:08:17 +0300
committerAlex Gr?nholm <alex.gronholm@nextday.fi>2011-05-24 02:08:17 +0300
commit8c28df8cabf091366a145e16570e9d9c3617becd (patch)
tree914b24f48ed37ee33ce17394e5c08d72ecb052f8
parentf972b92de5ceab675c1c82b49c0c51ec02bba431 (diff)
parentbce90c1b63ab7bc33de374a6c6a72fffe2af91b2 (diff)
downloadpastedeploy-git-8c28df8cabf091366a145e16570e9d9c3617becd.tar.gz
Added support for the call: protocol, contributed by Jason Stitt
-rw-r--r--docs/conf.py6
-rw-r--r--docs/index.txt31
-rw-r--r--docs/news.txt5
-rw-r--r--paste/deploy/loadwsgi.py49
-rw-r--r--paste/deploy/util.py14
-rw-r--r--tests/sample_configs/test_func.ini13
-rw-r--r--tests/test_config.py28
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)