Only show key_fields in error message
This caused a crash when GET, POST, and context were added to
the django_args by the decorators.
Patch by: Sverre Rabbelier
This demonstrates how the Response object works, and tests it at the
same time.
>>> from dtopt import ELLIPSIS
>>> from webob import Response, UTC
>>> from datetime import datetime
>>> res = Response('Test', status='200 OK')
This is a minimal response object. We can do things like get and set
the body:
>>> res.body
'Test'
>>> res.body = 'Another test'
>>> res.body
'Another test'
>>> res.body = 'Another'
>>> res.write(' test')
>>> res.app_iter
['Another test']
>>> res.content_length
12
>>> res.headers['content-length']
'12'
Content-Length is only applied when setting the body to a string; you
have to set it manually otherwise. There are also getters and setters
for the various pieces:
>>> res.app_iter = ['test']
>>> print res.content_length
None
>>> res.content_length = 4
>>> res.status
'200 OK'
>>> res.status_int
200
>>> res.headers
HeaderDict([('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')])
>>> res.headerlist
[('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')]
Content-type and charset are handled separately as properties, though
they are both in the ``res.headers['content-type']`` header:
>>> res.content_type
'text/html'
>>> res.content_type = 'text/html'
>>> res.content_type
'text/html'
>>> res.charset
'utf8'
>>> res.charset = 'iso-8859-1'
>>> res.charset
'iso-8859-1'
>>> res.content_type
'text/html'
>>> res.headers['content-type']
'text/html; charset=iso-8859-1'
Cookie handling is done through methods:
>>> res.set_cookie('test', 'value')
>>> res.headers['set-cookie']
'test=value; Path=/'
>>> res.set_cookie('test2', 'value2', max_age=10000)
>>> res.headers['set-cookie'] # We only see the last header
'test2=value2; Max-Age=10000; Path=/'
>>> res.headers.getall('set-cookie')
['test=value; Path=/', 'test2=value2; Max-Age=10000; Path=/']
>>> res.unset_cookie('test')
>>> res.headers.getall('set-cookie')
['test2=value2; Max-Age=10000; Path=/']
Most headers are available in a parsed getter/setter form through
properties:
>>> res.age = 10
>>> res.age, res.headers['age']
(10, '10')
>>> res.allow = ['GET', 'PUT']
>>> res.allow, res.headers['allow']
(['GET', 'PUT'], 'GET, PUT')
>>> res.cache_control
<CacheControl ''>
>>> print res.cache_control.max_age
None
>>> res.cache_control.properties['max-age'] = None
>>> print res.cache_control.max_age
-1
>>> res.cache_control.max_age = 10
>>> res.cache_control
<CacheControl 'max-age=10'>
>>> res.headers['cache-control']
'max-age=10'
>>> res.cache_control.max_stale = 10
Traceback (most recent call last):
...
AttributeError: The property max-stale only applies to request Cache-Control
>>> res.cache_control = {}
>>> res.cache_control
<CacheControl ''>
>>> res.content_encoding = 'gzip'
>>> (res.content_encoding, res.headers['content-encoding'])
('gzip', 'gzip')
>>> res.content_language = 'en'
>>> (res.content_language, res.headers['content-language'])
(['en'], 'en')
>>> res.content_location = 'http://localhost:8080'
>>> res.headers['content-location']
'http://localhost:8080'
>>> res.content_range = (0, 100, 1000)
>>> (res.content_range, res.headers['content-range'])
(<ContentRange bytes 0-101/1000>, 'bytes 0-101/1000')
>>> res.date = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
>>> (res.date, res.headers['date'])
(datetime.datetime(2005, 1, 1, 12, 0, tzinfo=UTC), 'Sat, 01 Jan 2005 12:00:00 GMT')
>>> print res.etag
None
>>> res.etag = 'foo'
>>> (res.etag, res.headers['etag'])
('foo', 'foo')
>>> res.expires = res.date
>>> res.retry_after = 120 # two minutes
>>> res.retry_after
datetime.datetime(...)
>>> res.server = 'Python/foo'
>>> res.headers['server']
'Python/foo'
>>> res.vary = ['Cookie']
>>> (res.vary, res.headers['vary'])
(['Cookie'], 'Cookie')
The location header will try to absolutify itself if you have a
request object attached.
>>> res.location = '/test.html'
>>> from webob import Request
>>> res.request = Request.blank('/')
>>> res.location
'http://localhost/test.html'
>>> res.request = None
>>> res.location
'/test.html'
>>> res.request = Request.blank('/')
>>> res.location = '/test2.html'
>>> res.request = None
>>> res.location
'http://localhost/test2.html'
There's some conditional response handling too (you have to turn on
conditioanl_response)::
>>> res = Response(conditional_response=True)
>>> req = Request.blank('/')
>>> res.etag = 'tag'
>>> req.if_none_match = 'tag'
>>> req.get_response(res)
<Response ... 304 Not Modified>
>>> res.etag = 'other-tag'
>>> req.get_response(res)
<Response ... 200 OK>
>>> del req.if_none_match
>>> req.if_modified_since = datetime(2005, 1, 1, 12, 1, tzinfo=UTC)
>>> res.last_modified = datetime(2005, 1, 1, 12, 1, tzinfo=UTC)
>>> req.get_response(res)
<Response ... 304 Not Modified>
>>> res.last_modified = datetime(2006, 1, 1, 12, 1, tzinfo=UTC)
>>> req.get_response(res)
<Response ... 200 OK>
>>> res.last_modified = None
>>> req.get_response(res)
<Response ... 200 OK>
Also range response::
>>> res = Response(conditional_response=True)
>>> req = Request.blank('/')
>>> res.body = '0123456789'
>>> req.range = (1, 5)
>>> result = req.get_response(res)
>>> result.body
'1234'
>>> result.content_range
<ContentRange bytes 1-6/10>
>>> tuple(result.content_range)
(1, 5, 10)
>>> # Now an invalid range:
>>> req.range = (0, 20)
>>> str(req.range)
'bytes=0-21'
>>> result = req.get_response(res)
>>> result.body
'0123456789'
>>> print result.content_range
None
That was easier; we'll try it with a iterator for the body::
>>> res = Response(conditional_response=True)
>>> res.app_iter = ['01234', '567', '89']
>>> req = Request.blank('/')
>>> req.range = (1, 5)
>>> result = req.get_response(res)
>>> # Because we don't know the length of the app_iter, this
>>> # doesn't work:
>>> result.body
'0123456789'
>>> print result.content_range
None
>>> req.range = (5, None)
>>> result = req.get_response(res)
>>> result.body
'56789'
>>> result.content_range
<ContentRange bytes 5-*/10>
>>> # If we set Content-Length then we can use it with an app_iter
>>> res.content_length = 10
>>> req.range = (1, 5)
>>> result = req.get_response(res)
>>> result.body
'1234'
>>> result.content_range
<ContentRange bytes 1-6/10>
>>> # And trying If-modified-since
>>> res.etag = 'foobar'
>>> req.if_range = 'foobar'
>>> req.if_range
<IfRange etag=foobar, date=*>
>>> result = req.get_response(res)
>>> result.content_range
<ContentRange bytes 1-6/10>
>>> req.if_range = 'blah'
>>> result = req.get_response(res)
>>> result.content_range
>>> req.if_range = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
>>> res.last_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
>>> result = req.get_response(res)
>>> result.content_range
<ContentRange bytes 1-6/10>
>>> res.last_modified = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
>>> result = req.get_response(res)
>>> result.content_range
Some tests of exceptions::
>>> from webob import exc
>>> res = exc.HTTPNotFound('Not found!')
>>> res.exception.content_type = 'text/plain'
>>> res.content_type
'text/plain'
>>> res = exc.HTTPNotModified()
>>> res.headers
HeaderDict([])