The response object

The response object allows you to set headers and provides shortcuts for common handler responses, such as redirection.

Constructing response objects

At the minimum the pesto.response.Response constructor needs the response content. If this is all you specify, a successful response of type text/html will be generated with your specified content.

def app(request):
        return Response(['<html><body><h1>Hello World</body></html>'])

This will output:

200 OK
Content-Type: text/html; charset=UTF-8

<html><body><h1>Hello World</body></html>

The content argument must be an iterable object – eg a list, a generator expression or any python object that implements the iteration interface)

HTTP headers and a status code can also be supplied. Here’s a longer example, showing more options:

from pesto.response import Response

def app(request):
        return Response(
                status=405, # method not allowed
                content_type='text/html',
                allow=['GET', 'POST'],
                content=['<html><body>Sorry, that method is not allowed</body></html>']
        )

This will output:

405 Method Not Allowed
Allow: GET
Allow: POST
Content-Type: text/html

<html><body>Sorry, that method is not allowed</body></html>

Headers can be supplied as a list of tuples (the same way the WSGI start_response function expects them), or as keyword arguments, or any mixture of the two:

Response(
        ['<html><body>Sorry, that method is not allowed</body></html>'],
        status=405,
        headers=[('Content-Type', 'text/html'), ('Allow', 'GET'), ('Allow', 'POST')],
)

Response(
        ['<html><body>Sorry, that method is not allowed</body></html>'],
        status=405,
        content_type='text/html',
        allow=['GET', 'POST'],
)

Changing response objects

Response objects have a range of methods allowing you to add, remove and replace the headers and content. This makes it easy to chain handler functions together, each operating on the output of the last:

def handler1(request):
        return Response(["Ten green bottles, hanging on the wall"], content_type='text/plain')

def handler2(request):
        response = handler1(request)
        return response.replace(content=[chunk.replace('Ten', 'Nine') for chunk in response.content])

def handler3(request):
        response = handler2(request)
        return response.replace(content_type='text/html')
>>> from pesto.testing import TestApp
>>> print TestApp(to_wsgi(handler1)).get('/').text()
200 OK
Content-Type: text/plain

Ten green bottles, hanging on the wall

>>> print TestApp(to_wsgi(handler2)).get('/').text()
200 OK
Content-Type: text/plain

Nine green bottles, hanging on the wall

>>> print TestApp(to_wsgi(handler3)).get('/').text()
200 OK
Content-Type: text/html

Nine green bottles, hanging on the wall

Headers may be added, either singly:

>>> r = Response(content = ['Whoa nelly!'])
>>> r.headers
[('Content-Type', 'text/html; charset=UTF-8')]
>>> r = r.add_header('Cache-Control', 'private')
>>> r.headers
[('Cache-Control', 'private'), ('Content-Type', 'text/html; charset=UTF-8')]

or in groups:

>>> r = Response(content = ['Whoa nelly!'])
>>> r.headers
[('Content-Type', 'text/html; charset=UTF-8')]
>>> r = r.add_headers([('Content-Length', '11'), ('Cache-Control', 'Private')])
>>> r.headers
[('Cache-Control', 'Private'), ('Content-Length', '11'), ('Content-Type', 'text/html; charset=UTF-8')]
>>> r = r.add_headers(x_powered_by='pesto')
>>> r.headers
[('Cache-Control', 'Private'), ('Content-Length', '11'), ('Content-Type', 'text/html; charset=UTF-8'), ('X-Powered-By', 'pesto')]

Removing and replacing headers is the same. See the API documentation for pesto.response.Response for details.

Integrating with WSGI

It’s often useful to be able to switch between Pesto handler functions and WSGI application functions – for example, when writing WSGI middleware.

To aid this, Response objects are fully compliant WSGI applications:

>>> def mywsgi_app(environ, start_response):
...     r = Response(content = ['Whoa nelly!'])
...     return r(environ, start_response)
...
>>> print TestApp(mywsgi_app).get('/').text()
200 OK
Content-Type: text/html; charset=UTF-8
<BLANKLINE>
Whoa nelly!

Secondly, it is possible to proxy a WSGI application through a response object, capturing its output to allow further inspection and modification:

>>> def basic_wsgi_app(environ, start_response):
...     start_response('200 OK', [('Content-Type', 'text/html')])
...     return [ "<html>"
...          "<body>"
...          "<h1>Hello World!</h1>"
...          "</body>"
...          "</html>"
...     ]
...
>>> def altered_wsgi_app(environ, start_response):
...     response = Response.from_wsgi(wsgi_app1, environ, start_response)
...     return response.add_headers(x_powered_by='pesto')(environ, start_response)
...
>>> print TestApp(altered_wsgi_app).get('/').text()
200 OK
Content-Type: text/html
X-Powered-By: pesto
<BLANKLINE>
<html><body><h1>Hello World!</h1></body></html>

Common responses

Many canned error responses are available as Response classmethods:

>>> from pesto.response import Response
>>> def handler(request):
...     if not somecondition():
...         return Response.not_found()
...     return Response(['ok'])
...

>>> def handler2(request):
...     if not somecondition():
...         return Response.forbidden()
...     return Response(['ok'])
...

Redirect responses

A temporary or permanent redirect may be achieved by returning pesto.response.Response.redirect(). For example:

>>> def redirect(request):
...     return Response.redirect("http://www.example.com/")
...

pesto.response API documention

pesto.response

Response object for WSGI applications

class pesto.response.Response(content=None, status='200 OK', headers=None, onclose=None, add_default_content_type=True, **kwargs)

WSGI Response object.

Parameters:
  • content – An iterator over the response content
  • status – The WSGI status line, eg 200 OK or 404 Not Found.
  • headers – A list of headers, eg [('Content-Type', 'text/plain'), ('Content-Length', 193)]
  • add_default_content_type – If true (and the status is not 204 or 304) a default Content-Type header will be added if one is not provided, using the value of pesto.response.Response.default_content_type.
  • **kwargs – Arbitrary headers, provided as keyword arguments. Replace hyphens with underscores where necessary (eg content_length instead of Content-Length).

Example usage:

>>> # Construct a response
>>> response = Response(
...     content=['hello world\n'],
...     status='200 OK',
...     headers=[('Content-Type', 'text/plain')]
... )
>>> 
>>> # We can manipulate the response before outputting it
>>> response = response.add_header('X-Header', 'hello!')
>>>
>>> # To output the response, we call it as a WSGI application
>>> from pesto.testing import TestApp
>>> print TestApp(response).get('/').text()
200 OK
Content-Type: text/plain
X-Header: hello!
<BLANKLINE>
hello world
<BLANKLINE>
>>>

Note that response objects are themselves callable WSGI applications:

def wsgiapp(environ, start_response):
    response = Response(['hello world'], content_type='text/plain')
    return response(environ, start_response)

Return a new response object with the given cookie added.

Synopsis:

>>> r = Response(content_type = 'text/plain', cache_control='no-cache')
>>> r.headers
[('Cache-Control', 'no-cache'), ('Content-Type', 'text/plain')]
>>> r.add_cookie('foo', 'bar').headers
[('Cache-Control', 'no-cache'), ('Content-Type', 'text/plain'), ('Set-Cookie', 'foo=bar;Version=1')]
add_header(name, value)

Return a new response object with the given additional header.

Synopsis:

>>> r = Response(content_type = 'text/plain')
>>> r.headers
[('Content-Type', 'text/plain')]
>>> r.add_header('Cache-Control', 'no-cache').headers
[('Cache-Control', 'no-cache'), ('Content-Type', 'text/plain')]
add_headers(headers=[], **kwheaders)

Return a new response object with the given additional headers.

Synopsis:

>>> r = Response(content_type = 'text/plain')
>>> r.headers
[('Content-Type', 'text/plain')]
>>> r.add_headers(
...     cache_control='no-cache',
...     expires='Mon, 26 Jul 1997 05:00:00 GMT'
... ).headers
[('Cache-Control', 'no-cache'), ('Content-Type', 'text/plain'), ('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')]
add_onclose(*funcs)

Add functions to be called as part of the response iterators close method.

classmethod bad_request(request=None)

Returns an HTTP bad request response.

Example usage:

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.bad_request()
...
>>> print TestApp(app).get('/')
400 Bad Request
Content-Type: text/html; charset=UTF-8

<html><body><h1>The server could not understand your request</h1></body></html>
buffered()

Return a new response object with the content buffered into a list. This will also add a content-length header.

Example usage:

>>> def generate_content():
...     yield "one two "
...     yield "three four five"
...
>>> Response(content=generate_content()).content 
<generator object ...>
>>> Response(content=generate_content()).buffered().content
['one two ', 'three four five']
content

Iterator over the response content part

content_type

Return the value of the Content-Type header if set, otherwise None.

classmethod forbidden(message='Sorry, access is denied')

Return an HTTP forbidden response (403):

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.forbidden()
...
>>> print TestApp(app).get('/')
403 Forbidden
Content-Type: text/html; charset=UTF-8

<html>
<body>
    <h1>Sorry, access is denied</h1>
</body>
</html>
classmethod from_wsgi(wsgi_callable, environ, start_response)

Return a Response object constructed from the result of calling wsgi_callable with the given environ and start_response arguments.

get_header(name, default='')

Return the concatenated values of the named header(s) or default if the header has not been set.

As specified in RFC2616 (section 4.2), multiple headers will be combined using a single comma.

Example usage:

>>> r = Response(set_cookie = ['cookie1', 'cookie2'])
>>> r.get_header('set-cookie')
'cookie1,cookie2'
get_headers(name)

Return the list of headers set with the given name.

Synopsis:

>>> r = Response(set_cookie = ['cookie1', 'cookie2'])
>>> r.get_headers('set-cookie')
['cookie1', 'cookie2']
headers

Return a list of response headers in the format [(<header-name>, <value>), ...]

classmethod internal_server_error()

Return an HTTP internal server error response (500):

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.internal_server_error()
...
>>> print TestApp(app).get('/')
500 Internal Server Error
Content-Type: text/html; charset=UTF-8

<html><body><h1>Internal Server Error</h1></body></html>
returns
A pesto.response.Response object
classmethod length_required(request=None)

Returns an HTTP 411 Length Required request response.

Example usage:

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.length_required()
...
>>> print TestApp(app).get('/')
411 Length Required
Content-Type: text/html; charset=UTF-8

<html><body><h1>The Content-Length header was missing from your request</h1></body></html>
classmethod make_headers(header_list, header_dict)

Return a list of header (name, value) tuples from the combination of the header_list and header_dict.

Example usage:

>>> Response.make_headers(
...     [('Content-Type', 'text/html')],
...     {'content_length' : 54}
... )
[('Content-Type', 'text/html'), ('Content-Length', '54')]

>>> Response.make_headers(
...     [('Content-Type', 'text/html')],
...     {'x_foo' : ['a1', 'b2']}
... )
[('Content-Type', 'text/html'), ('X-Foo', 'a1'), ('X-Foo', 'b2')]
classmethod make_status(status)

Return a status line from the given status, which may be a simple integer.

Example usage:

>>> Response.make_status(200)
'200 OK'
classmethod method_not_allowed(valid_methods)

Returns an HTTP method not allowed response (404):

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.method_not_allowed(valid_methods=("GET", "HEAD"))
...
>>> print TestApp(app).get('/')
405 Method Not Allowed
Allow: GET,HEAD
Content-Type: text/html; charset=UTF-8

<html><body><h1>Method not allowed</h1></body></html>
valid_methods
A list of HTTP methods valid for the URI requested. If None, the dispatcher_app mechanism will be used to autogenerate a list of methods. This expects a dispatcher_app object to be stored in the wsgi environ dictionary at pesto.dispatcher_app.
returns
A pesto.response.Response object
classmethod not_found(request=None)

Returns an HTTP not found response (404). This method also outputs the necessary HTML to be used as the return value for a pesto handler.

Synopsis:

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.not_found()
...
>>> print TestApp(app).get('/')
404 Not Found
Content-Type: text/html; charset=UTF-8

<html>
<body>
    <h1>Not found</h1>
    <p>The requested resource could not be found.</p>
</body>
</html>
classmethod redirect(location, request=None, status=302)

Return an HTTP redirect reponse.

location
The URI of the new location. If this is relative it will be converted to an absolute URL based on the current request.
status
HTTP status code for the redirect, defaulting to STATUS_FOUND (a temporary redirect)

Synopsis:

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...   return Response.redirect("/new-location", request)
...
>>> print TestApp(app).get('/')
302 Found
Content-Type: text/html; charset=UTF-8
Location: http://localhost/new-location

<html><head></head><body>
<h1>Page has moved</h1>
<p><a href='http://localhost/new-location'>http://localhost/new-location</a></p>
</body></html>

Note that we can also do the following:

>>> from functools import partial
>>> from pesto.testing import TestApp
>>> from pesto.dispatch import dispatcher_app
>>> d = dispatcher_app()
>>> d.match('/animals', GET=partial(Response.redirect, '/new-location'))
>>> print TestApp(d).get('/animals')
302 Found
Content-Type: text/html; charset=UTF-8
Location: http://localhost/new-location

<html><head></head><body>
<h1>Page has moved</h1>
<p><a href='http://localhost/new-location'>http://localhost/new-location</a></p>
</body></html>
remove_headers(*headers)

Return a new response object with the given headers removed.

Synopsis:

>>> r = Response(content_type = 'text/plain', cache_control='no-cache')
>>> r.headers
[('Cache-Control', 'no-cache'), ('Content-Type', 'text/plain')]
>>> r.remove_headers('Cache-Control').headers
[('Content-Type', 'text/plain')]
replace(content=None, status=None, headers=None, **kwheaders)

Return a new response object with any of content, status or headers changed.

Synopsis:

>>> Response(allow='GET', foo='bar', add_default_content_type=False).replace(allow='POST').headers
[('Allow', 'POST'), ('Foo', 'bar')]

>>> Response(allow='GET', add_default_content_type=False).replace(headers=[('allow', 'POST')]).headers
[('Allow', 'POST')]

>>> Response(location='http://www.google.com').replace(status=301).status
'301 Moved Permanently'

>>> Response(content=['donald']).replace(content=['pluto']).content
['pluto']
classmethod request_entity_too_large(request=None)

Returns an HTTP 413 Request Entity Too Large response.

Example usage:

>>> from pesto.testing import TestApp
>>> from pesto import to_wsgi
>>> @to_wsgi
... def app(request):
...     return Response.request_entity_too_large()
...
>>> print TestApp(app).get('/')
413 Request Entity Too Large
Content-Type: text/html; charset=UTF-8

<html><body><h1>Request Entity Too Large</h1></body></html>
status

HTTP status message for the response, eg 200 OK

status_code

Return the numeric status code for the response as an integer:

>>> Response(status='404 Not Found').status_code
404
>>> Response(status=200).status_code
200