pesto.wsgiutils API documention

pesto.wsgiutils

Utility functions for WSGI applications

pesto.wsgiutils.use_x_forwarded(trusted=('127.0.0.1', 'localhost'))

Return a middleware application that modifies the WSGI environment so that the X_FORWARDED_* headers are observed and generated URIs are correct in a proxied environment.

Use this whenever the WSGI application server is sitting behind Apache or another proxy server.

HTTP_X_FORWARDED_FOR is substituted for REMOTE_ADDR and HTTP_X_FORWARDED_HOST for SERVER_NAME. If HTTP_X_FORWARDED_SSL is set, then the wsgi.url_scheme is modified to https and HTTPS is set to on.

Example:

>>> from pesto.core import to_wsgi
>>> from pesto.testing import TestApp
>>> def app(request):
...     return Response(["URL is ", request.request_uri, "; REMOTE_ADDR is ", request.remote_addr])
...
>>> app = TestApp(use_x_forwarded()(to_wsgi(app)))
>>> response = app.get('/',
...     SERVER_NAME='wsgiserver-name',
...     SERVER_PORT='8080',
...     HTTP_HOST='wsgiserver-name:8080',
...     REMOTE_ADDR='127.0.0.1',
...     HTTP_X_FORWARDED_HOST='real-name:81',
...     HTTP_X_FORWARDED_FOR='1.2.3.4'
... )
>>> response.body
'URL is http://real-name:81/; REMOTE_ADDR is 1.2.3.4'
>>> response = app.get('/',
...     SERVER_NAME='wsgiserver-name',
...     SERVER_PORT='8080',
...     HTTP_HOST='wsgiserver-name:8080',
...     REMOTE_ADDR='127.0.0.1',
...     HTTP_X_FORWARDED_HOST='real-name:443',
...     HTTP_X_FORWARDED_FOR='1.2.3.4',
...     HTTP_X_FORWARDED_SSL='on'
... )
>>> response.body
'URL is https://real-name/; REMOTE_ADDR is 1.2.3.4'

In a non-forwarded environment, the environ dictionary will not be changed:

>>> response = app.get('/',
...     SERVER_NAME='wsgiserver-name',
...     SERVER_PORT='8080',
...     HTTP_HOST='wsgiserver-name:8080',
...     REMOTE_ADDR='127.0.0.1',
... )
>>> response.body
'URL is http://wsgiserver-name:8080/; REMOTE_ADDR is 127.0.0.1'
pesto.wsgiutils.mount_app(appmap)

Create a composite application with different mount points.

Synopsis:

>>> def app1(e, sr):
...     return [1]
...
>>> def app2(e, sr):
...     return [2]
...
>>> app = mount_app({
...     '/path/one' : app1,
...     '/path/two' : app2,
... })
pesto.wsgiutils.use_redirect_url(use_redirect_querystring=True)

Replace the SCRIPT_NAME and QUERY_STRING WSGI environment variables with ones taken from Apache’s REDIRECT_URL and REDIRECT_QUERY_STRING environment variable, if set.

If an application is mounted as CGI and Apache RewriteRules are used to route requests, the SCRIPT_NAME and QUERY_STRING parts of the environment may not be meaningful for reconstructing URLs.

In this case Apache puts an extra key, REDIRECT_URL into the path which contains the full path as requested.

See also:

Example: assume a handler similar to the below has been made available at the address /cgi-bin/myhandler.cgi:

>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response(["My URL is " + request.request_uri])
...

Apache has been configured to redirect requests using the following RewriteRules in a .htaccess file in the server’s document root, or the equivalents in the apache configuration file:

RewriteEngine On
RewriteBase /
RewriteRule ^pineapple(.*)$ /cgi-bin/myhandler.cgi [PT]

The following code creates a simulation of the request headers Apache will pass to the application with the above rewrite rules. Without the middleware, the output will be as follows:

>>> from pesto.testing import TestApp
>>> TestApp(app).get(
...     SERVER_NAME = 'example.com',
...     REDIRECT_URL = '/pineapple/cake',
...     SCRIPT_NAME = '/myhandler.cgi',
...     PATH_INFO = '/cake',
... ).body
'My URL is http://example.com/myhandler.cgi/cake'

The use_redirect_url middleware will correctly set the SCRIPT_NAME and QUERY_STRING values:

>>> app = use_redirect_url()(app)

With this change the application will now output the correct values:

>>> TestApp(app).get(
...     SERVER_NAME = 'example.com',
...     REDIRECT_URL = '/pineapple/cake',
...     SCRIPT_NAME = '/myhandler.cgi',
...     PATH_INFO = '/cake',
... ).body
'My URL is http://example.com/pineapple/cake'
pesto.wsgiutils.make_absolute_url(wsgi_environ, url)

Return an absolute url from url, based on the current url.

Synopsis:

>>> from pesto.testing import make_environ
>>> environ = make_environ(wsgi_url_scheme='https', SERVER_NAME='example.com', SERVER_PORT='443', PATH_INFO='/foo')
>>> make_absolute_url(environ, '/bar')
'https://example.com/bar'
>>> make_absolute_url(environ, 'baz')
'https://example.com/foo/baz'
>>> make_absolute_url(environ, 'http://anotherhost/bar')
'http://anotherhost/bar'

Note that the URL is constructed using the PEP-333 URL reconstruction method (http://www.python.org/dev/peps/pep-0333/#url-reconstruction) and the returned URL is normalized:

>>> environ = make_environ(
...     wsgi_url_scheme='https',
...     SERVER_NAME='example.com',
...     SERVER_PORT='443',
...     SCRIPT_NAME='/colors',
...     PATH_INFO='/red',
... )

>>> make_absolute_url(environ, 'blue')
'https://example.com/colors/red/blue'

>>> make_absolute_url(environ, '../blue')
'https://example.com/colors/blue'

>>> make_absolute_url(environ, 'blue/')
'https://example.com/colors/red/blue/'
pesto.wsgiutils.run_with_cgi(application, environ=None)

Run application application as a CGI script

pesto.wsgiutils.make_uri_component(s, separator='-')

Turn a string into something suitable for a URI component.

Synopsis:

>>> import pesto.wsgiutils
>>> pesto.wsgiutils.make_uri_component(u"How now brown cow")
'how-now-brown-cow'

Unicode characters are mapped to ASCII equivalents where appropriate, and characters which would normally have to be escaped are translated into hyphens to ease readability of the generated URI.

s
The (unicode) string to translate.
separator
A single ASCII character that will be used to replace spaces and other characters that are inadvisable in URIs.
returns
A lowercase ASCII string, suitable for inclusion as part of a URI.
pesto.wsgiutils.static_server(document_root, default_charset='ISO-8859-1', bufsize=8192)

Create a simple WSGI static file server application

Synopsis:

>>> from pesto.dispatch import dispatcher_app
>>> dispatcher = dispatcher_app()
>>> dispatcher.match('/static/<path:path>',
...     GET=static_server('/docroot'),
...     HEAD=static_server('/docroot')
... )
pesto.wsgiutils.serve_static_file(request, path, default_charset='ISO-8859-1', bufsize=8192)

Serve a static file located at path. It is the responsibility of the caller to check that the path is valid and allowed.

Synopsis:

>>> from pesto.dispatch import dispatcher_app
>>> def view_important_document(request):
...     return serve_static_file(request, '/path/to/very_important_document.pdf')
...
>>> def download_important_document(request):
...     return serve_static_file(request, '/path/to/very_important_document.pdf').add_headers(
...         content_disposition='attachment; filename=very_important_document.pdf'
...     )
...
pesto.wsgiutils.uri_join(base, link)

Example:

>>> uri_join('http://example.org/', 'http://example.com/')
'http://example.com/'

>>> uri_join('http://example.com/', '../styles/main.css')
'http://example.com/styles/main.css'

>>> uri_join('http://example.com/subdir/', '../styles/main.css')
'http://example.com/styles/main.css'

>>> uri_join('http://example.com/login', '?error=failed+auth')
'http://example.com/login?error=failed+auth'

>>> uri_join('http://example.com/login', 'register')
'http://example.com/register'
pesto.wsgiutils.make_query(data=None, separator=';', charset=None, **kwargs)

Return a query string formed from the given dictionary data.

Note that the pairs are separated using a semicolon, in accordance with the W3C recommendation

If no encoding is given, unicode values are encoded using the character set specified by pesto.DEFAULT_CHARSET.

Synopsis:

>>> # Basic usage
>>> make_query({ 'eat' : u'more cake', 'drink' : u'more tea' })
'drink=more+tea;eat=more+cake'

>>> # Use an ampersand as the separator
>>> make_query({ 'eat' : u'more cake', 'drink' : u'more tea' }, separator='&')
'drink=more+tea&eat=more+cake'

>>> # Can also be called using ``**kwargs`` style
>>> make_query(eat=u'more cake', drink=u'more tea')
'drink=more+tea;eat=more+cake'

>>> # Non-string values
>>> make_query(eat=[1, 2], drink=3.4)
'drink=3.4;eat=1;eat=2'

>>> # Multiple values per key
>>> make_query(eat=[u'more', u'cake'], drink=u'more tea')
'drink=more+tea;eat=more;eat=cake'

>>> # Anything with a value of ``None`` is excluded from the query
>>> make_query(x='1', y=None)
'x=1'
pesto.wsgiutils.with_request_args(**argspec)

Function decorator to map request query/form arguments to function arguments.

Synopsis:

>>> from pesto.dispatch import dispatcher_app
>>> from pesto import to_wsgi
>>> from pesto.testing import TestApp
>>>
>>> dispatcher = dispatcher_app()
>>>
>>> @dispatcher.match('/recipes/<category:unicode>/view', 'GET')
... @with_request_args(id=int)
... def my_handler(request, category, id):
...     return Response([
...         u'Recipe #%d in category "%s".' % (id, category)
...     ])
... 
>>> print TestApp(dispatcher).get('/recipes/rat-stew/view', QUERY_STRING='id=2')
200 OK
Content-Type: text/html; charset=UTF-8

Recipe #2 in category "rat-stew".

If specified arguments are not present in the request (and no default value is given in the function signature), or a ValueError is thrown during type conversion an appropriate error will be raised:

>>> print TestApp(dispatcher).get('/recipes/rat-stew/view') 
Traceback (most recent call last):
    ...
RequestArgsKeyError: 'id'
>>> print TestApp(dispatcher).get('/recipes/rat-stew/view?id=elephant') 
Traceback (most recent call last):
    ...
RequestArgsConversionError: Could not convert parameter 'id' to requested type (invalid literal for int() with base 10: 'elephant')

A default argument value in the handler function will protect against this:

>>> dispatcher = dispatcher_app()
>>> @dispatcher.match('/recipes/<category:unicode>/view', 'GET')
... @with_request_args(category=unicode, id=int)
... def my_handler(request, category, id=1):
...     return Response([
...         u'Recipe #%d in category "%s".' % (id, category)
...     ])
... 
>>> print TestApp(dispatcher).get('/recipes/mouse-pie/view')
200 OK
Content-Type: text/html; charset=UTF-8

Recipe #1 in category "mouse-pie".

Sometimes it is necessary to map multiple request values to a single argument, for example in a form where two or more input fields have the same name. To do this, put the type-casting function into a list when calling with_request_args:

>>> @to_wsgi
... @with_request_args(actions=[unicode])
... def app(request, actions):
...     return Response([
...         u', '.join(actions)
...     ])
... 
>>> print TestApp(app).get('/', QUERY_STRING='actions=up;actions=up;actions=and+away%21')
200 OK
Content-Type: text/html; charset=UTF-8

up, up, and away!

Table Of Contents

Previous topic

pesto.httputils API documention

Next topic

pesto.utils API documention

This Page