This demonstrates how the Request object works, and tests it.

You can instantiate a request using Request.blank(), to create a fresh environment dictionary with all the basic keys such a dictionary should have.

>>> import sys
>>> if sys.version >= '2.7':
...     from io import BytesIO as InputType
... else:
...     from cStringIO import InputType
>>> from doctest import ELLIPSIS, NORMALIZE_WHITESPACE
>>> from webob import Request, UTC
>>> req = Request.blank('/')
>>> req 
<Request at ... GET http://localhost/>
>>> print req
GET / HTTP/1.0
Host: localhost:80
>>> req.environ 
{...}
>>> isinstance(req.body_file, InputType)
True
>>> req.scheme
'http'
>>> req.method
'GET'
>>> req.script_name
''
>>> req.path_info
'/'
>>> req.upath_info
u'/'
>>> req.content_type
''
>>> print req.remote_user
None
>>> req.host_url
'http://localhost'
>>> req.script_name = '/foo'
>>> req.path_info = '/bar/'
>>> req.environ['QUERY_STRING'] = 'a=b'
>>> req.application_url
'http://localhost/foo'
>>> req.path_url
'http://localhost/foo/bar/'
>>> req.url
'http://localhost/foo/bar/?a=b'
>>> req.relative_url('baz')
'http://localhost/foo/bar/baz'
>>> req.relative_url('baz', to_application=True)
'http://localhost/foo/baz'
>>> req.relative_url('http://example.org')
'http://example.org'
>>> req.path_info_peek()
'bar'
>>> req.path_info_pop()
'bar'
>>> req.script_name, req.path_info
('/foo/bar', '/')
>>> print req.environ.get('wsgiorg.routing_args')
None
>>> req.urlvars
{}
>>> req.environ['wsgiorg.routing_args']
((), {})
>>> req.urlvars = dict(x='y')
>>> req.environ['wsgiorg.routing_args']
((), {'x': 'y'})
>>> req.urlargs
()
>>> req.urlargs = (1, 2, 3)
>>> req.environ['wsgiorg.routing_args']
((1, 2, 3), {'x': 'y'})
>>> del req.urlvars
>>> req.environ['wsgiorg.routing_args']
((1, 2, 3), {})
>>> req.urlvars = {'test': 'value'}
>>> del req.urlargs
>>> req.environ['wsgiorg.routing_args']
((), {'test': 'value'})
>>> req.is_xhr
False
>>> req.environ['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
>>> req.is_xhr
True
>>> req.host
'localhost:80'

There are also variables to access the variables and body:

>>> from cStringIO import StringIO
>>> body = 'var1=value1&var2=value2&rep=1&rep=2'
>>> req = Request.blank('/')
>>> req.method = 'POST'
>>> req.body_file = StringIO(body)
>>> req.environ['CONTENT_LENGTH'] = str(len(body))
>>> vars = req.str_POST
>>> vars
MultiDict([('var1', 'value1'), ('var2', 'value2'), ('rep', '1'), ('rep', '2')])
>>> vars is req.POST
False
>>> req.POST
UnicodeMultiDict([(u'var1', u'value1'), (u'var2', u'value2'), (u'rep', u'1'), (u'rep', u'2')])
>>> req.decode_param_names
True

Note that the variables are there for GET requests and non-form requests, but they are empty and read-only:

>>> req = Request.blank('/')
>>> req.str_POST
<NoVars: Not a form request>
>>> req.str_POST.items()
[]
>>> req.str_POST['x'] = 'y'
Traceback (most recent call last):
    ...
KeyError: 'Cannot add variables: Not a form request'
>>> req.method = 'POST'
>>> req.str_POST
MultiDict([])
>>> req.content_type = 'text/xml'
>>> req.body_file = StringIO('<xml></xml>')
>>> req.str_POST
<NoVars: Not an HTML form submission (Content-Type: text/xml)>
>>> req.body
'<xml></xml>'

You can also get access to the query string variables, of course:

>>> req = Request.blank('/?a=b&d=e&d=f')
>>> req.str_GET
GET([('a', 'b'), ('d', 'e'), ('d', 'f')])
>>> req.GET['d']
u'f'
>>> req.GET.getall('d')
[u'e', u'f']
>>> req.method = 'POST'
>>> req.body = 'x=y&d=g'
>>> req.environ['CONTENT_LENGTH']
'7'
>>> req.params
UnicodeMultiDict([(u'a', u'b'), (u'd', u'e'), (u'd', u'f'), (u'x', u'y'), (u'd', u'g')])
>>> req.params['d']
u'f'
>>> req.params.getall('d')
[u'e', u'f', u'g']

Cookies are viewed as a dictionary (view only):

>>> req = Request.blank('/')
>>> req.environ['HTTP_COOKIE'] = 'var1=value1; var2=value2'
>>> sorted(req.str_cookies.items())
[('var1', 'value1'), ('var2', 'value2')]
>>> sorted(req.cookies.items())
[(u'var1', u'value1'), (u'var2', u'value2')]
>>> req.charset = 'utf8'
>>> sorted(req.cookies.items())
[(u'var1', u'value1'), (u'var2', u'value2')]

Sometimes conditional headers are problematic. You can remove them:

>>> from datetime import datetime
>>> req = Request.blank('/')
>>> req.if_none_match = 'some-etag'
>>> req.if_modified_since = datetime(2005, 1, 1, 12, 0)
>>> req.environ['HTTP_ACCEPT_ENCODING'] = 'gzip'
>>> print sorted(req.headers.items())
[('Accept-Encoding', 'gzip'), ('Host', 'localhost:80'), ('If-Modified-Since', 'Sat, 01 Jan 2005 12:00:00 GMT'), ('If-None-Match', 'some-etag')]
>>> req.remove_conditional_headers()
>>> print req.headers
{'Host': 'localhost:80'}

Some headers are handled specifically (more should be added):

>>> req = Request.blank('/')
>>> req.if_none_match = 'xxx'
>>> 'xxx' in req.if_none_match
True
>>> 'yyy' in req.if_none_match
False
>>> req.if_modified_since = datetime(2005, 1, 1, 12, 0)
>>> req.if_modified_since < datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
True
>>> req.user_agent is None
True
>>> req.user_agent = 'MSIE-Win'
>>> req.user_agent
'MSIE-Win'
>>> req.cache_control
<CacheControl ''>
>>> req.cache_control.no_cache = True
>>> req.cache_control.max_age = 0
>>> req.cache_control
<CacheControl 'max-age=0, no-cache'>

.cache_control is a view:

>>> 'cache-control' in req.headers
True
>>> req.headers['cache-control']
'max-age=0, no-cache'
>>> req.cache_control = {'no-transform': None, 'max-age': 100}
>>> req.headers['cache-control']
'max-age=100, no-transform'

Accept-* headers are parsed into read-only objects that support containment tests, and some useful methods. Note that parameters on mime types are not supported.

>>> req = Request.blank('/')
>>> req.environ['HTTP_ACCEPT'] = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.1"
>>> req.accept 
<MIMEAccept('text/*;q=0.3, text/html;q=0.7, text/html, text/html;q=0.4, */*;q=0.1')>
>>> for item, quality in req.accept._parsed:
...     print '%s: %0.1f' % (item, quality)
text/*: 0.3
text/html: 0.7
text/html: 1.0
text/html: 0.4
*/*: 0.1
>>> '%0.1f' % req.accept.quality('text/plain')
'0.3'
>>> '%0.1f' % req.accept.quality('text/html')
'1.0'
>>> req.accept.first_match(['text/plain', 'text/html', 'image/png'])
'text/plain'
>>> 'image/png' in req.accept
True
>>> req.environ['HTTP_ACCEPT'] = "text/html, application/xml; q=0.7, text/*; q=0.5, */*; q=0.1"
>>> req.accept 
<MIMEAccept('text/html, application/xml;q=0.7, text/*;q=0.5, */*;q=0.1')>
>>> req.accept.best_match(['text/plain', 'application/xml'])
'application/xml'
>>> req.accept.first_match(['application/xml', 'text/html'])
'application/xml'
>>> req.accept = "text/html, application/xml, text/*; q=0.5"
>>> 'image/png' in req.accept
False
>>> 'text/plain' in req.accept
True
>>> req.accept_charset = 'utf8'
>>> 'UTF8' in req.accept_charset
True
>>> 'gzip' in req.accept_encoding
False
>>> req.accept_encoding = 'gzip'
>>> 'GZIP' in req.accept_encoding
True
>>> req.accept_language = {'en-US': 0.5, 'es': 0.7}
>>> str(req.accept_language)
'es;q=0.7, en-US;q=0.5'
>>> req.headers['Accept-Language']
'es;q=0.7, en-US;q=0.5'
>>> req.accept_language.best_matches('en-GB')
['es', 'en-US', 'en-GB']
>>> req.accept_language.best_matches('es')
['es']
>>> req.accept_language.best_matches('ES')
['ES']
>>> req = Request.blank('/', accept_language='en;q=0.5')
>>> req.accept_language.best_match(['en-gb'])
'en-gb'
>>> req = Request.blank('/', accept_charset='utf-8;q=0.5')
>>> req.accept_charset.best_match(['iso-8859-1', 'utf-8'])
'iso-8859-1'

The If-Range header is a combination of a possible conditional date or etag match:

>>> req = Request.blank('/')
>>> req.if_range = 'asdf'
>>> req.if_range
<IfRange etag="asdf", date=*>
>>> from webob import Response
>>> res = Response()
>>> res.etag = 'asdf'
>>> req.if_range.match_response(res)
True
>>> res.etag = None
>>> req.if_range.match_response(res)
False
>>> res.last_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC)
>>> req.if_range = datetime(2006, 1, 1, 12, 0, tzinfo=UTC)
>>> req.if_range
<IfRange etag=*, date=Sun, 01 Jan 2006 12:00:00 GMT>
>>> req.if_range.match_response(res)
True
>>> res.last_modified = datetime(2007, 1, 1, 12, 0, tzinfo=UTC)
>>> req.if_range.match_response(res)
False
>>> req = Request.blank('/')
>>> req.if_range
<Empty If-Range>
>>> req.if_range.match_response(res)
True

Ranges work like so:

>>> req = Request.blank('/')
>>> req.range = (0, 100)
>>> req.range
<Range ranges=(0, 100)>
>>> str(req.range)
'bytes=0-99'

You can use them with responses:

>>> res = Response()
>>> res.content_range = req.range.content_range(1000)
>>> res.content_range
<ContentRange bytes 0-99/1000>
>>> str(res.content_range)
'bytes 0-99/1000'
>>> start, end, length = res.content_range
>>> start, end, length
(0, 100, 1000)

A quick test of caching the request body:

>>> from cStringIO import StringIO
>>> length = Request.request_body_tempfile_limit+10
>>> data = StringIO('x'*length)
>>> req = Request.blank('/')
>>> req.content_length = length
>>> req.method = 'PUT'
>>> req.body_file = data
>>> req.body_file_raw
<...IO... object at ...>
>>> len(req.body)
10250
>>> req.body_file
<open file ..., mode 'w+b' at ...>
>>> int(req.body_file.tell())
0
>>> req.POST
UnicodeMultiDict([])
>>> int(req.body_file.tell())
0

Some query tests:

>>> req = Request.blank('/')
>>> req.GET.get('unknown')
>>> req.GET.get('unknown', '?')
'?'
>>> req.POST.get('unknown')
>>> req.POST.get('unknown', '?')
'?'
>>> req.params.get('unknown')
>>> req.params.get('unknown', '?')
'?'

Some updating of the query string:

>>> req = Request.blank('http://localhost/foo?a=b')
>>> req.str_GET
GET([('a', 'b')])
>>> req.str_GET['c'] = 'd'
>>> req.query_string
'a=b&c=d'

And for dealing with file uploads:

>>> req = Request.blank('/posty')
>>> req.method = 'POST'
>>> req.content_type = 'multipart/form-data; boundary="foobar"'
>>> req.body = '''\
... --foobar
... Content-Disposition: form-data; name="a"
...
... b
... --foobar
... Content-Disposition: form-data; name="upload"; filename="test.html"
... Content-Type: text/html
...
... <html>Some text...</html>
... --foobar--
... '''
>>> req.str_POST
MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html'))])
>>> print req.body.replace('\r', '') 
--foobar
Content-Disposition: form-data; name="a"
<BLANKLINE>
b
--foobar
Content-Disposition: form-data; name="upload"; filename="test.html"
Content-type: text/html
<BLANKLINE>
<html>Some text...</html>
--foobar--
>>> req.POST['c'] = 'd'
>>> req.str_POST
MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html')), ('c', 'd')])
>>> req.body_file_raw
<FakeCGIBody at ... viewing MultiDict([('a'...d')])>
>>> sorted(req.str_POST.keys())
['a', 'c', 'upload']
>>> print req.body.replace('\r', '') # doctestx: +REPORT_UDIFF
--foobar
Content-Disposition: form-data; name="a"
<BLANKLINE>
b
--foobar
Content-Disposition: form-data; name="upload"; filename="test.html"
Content-type: text/html
<BLANKLINE>
<html>Some text...</html>
--foobar
Content-Disposition: form-data; name="c"
<BLANKLINE>
d
--foobar--

FakeCGIBody have both readline and readlines methods:

>>> req_ = Request.blank('/posty')
>>> req_.method = 'POST'
>>> req_.content_type = 'multipart/form-data; boundary="foobar"'
>>> req_.body = '''\
... --foobar
... Content-Disposition: form-data; name="a"
...
... b
... --foobar
... Content-Disposition: form-data; name="upload"; filename="test.html"
... Content-Type: text/html
...
... <html>Some text...</html>
... --foobar--
... '''
>>> req_.str_POST
MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html'))])
>>> print req_.body.replace('\r', '') 
--foobar
Content-Disposition: form-data; name="a"
<BLANKLINE>
b
--foobar
Content-Disposition: form-data; name="upload"; filename="test.html"
Content-type: text/html
<BLANKLINE>
<html>Some text...</html>
--foobar--
>>> req_.POST['c'] = 'd'
>>> req_.str_POST
MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html')), ('c', 'd')])
>>> req_.body_file_raw.readline()
'--foobar\r\n'
>>> [n.replace('\r', '') for n in req_.body_file.readlines()]
['Content-Disposition: form-data; name="a"\n', '\n', 'b\n', '--foobar\n', 'Content-Disposition: form-data; name="upload"; filename="test.html"\n', 'Content-type: text/html\n', '\n', '<html>Some text...</html>\n', '--foobar\n', 'Content-Disposition: form-data; name="c"\n', '\n', 'd\n', '--foobar--']

Also reparsing works through the fake body:

>>> del req.environ['webob._parsed_post_vars']
>>> req.str_POST
MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html')), ('c', 'd')])

A BaseRequest class exists for the purpose of usage by web frameworks that want a less featureful Request.

For example, the Request class mutates the environ['webob.adhoc_attrs'] attribute when its __getattr__, __setattr__, and __delattr__ are invoked.

The BaseRequest class omits the mutation annotation behavior provided by the default Request implementation. Instead, the of the BaseRequest class actually mutates the __dict__ of the request instance itself.

>>> from webob import BaseRequest
>>> req = BaseRequest.blank('/')
>>> req.foo = 1
>>> req.environ['webob.adhoc_attrs']
Traceback (most recent call last):
    ...
KeyError: 'webob.adhoc_attrs'
>>> req.foo
1
>>> del req.foo
>>> req.foo
Traceback (most recent call last):
    ...
AttributeError: 'BaseRequest' object has no attribute 'foo'
>>> req = BaseRequest.blank('//foo')
>>> print req.path_info_pop('x')
None
>>> req.script_name
''
>>> print BaseRequest.blank('/foo').path_info_pop('/')
None
>>> BaseRequest.blank('/foo').path_info_pop('foo')
'foo'
>>> BaseRequest.blank('/foo').path_info_pop('fo+')
'foo'
>>> BaseRequest.blank('//1000').path_info_pop('\d+')
'1000'
>>> BaseRequest.blank('/1000/x').path_info_pop('\d+')
'1000'
>>> req = Request.blank('/', method='PUT', body='x'*10)

str(req) returns the request as HTTP request string

>>> print req
PUT / HTTP/1.0
Content-Length: 10
Host: localhost:80
<BLANKLINE>
xxxxxxxxxx

req.as_string() does the same but also can take additional argument skip_body skip_body=True excludes the body from the result

>>> print req.as_string(skip_body=True)
PUT / HTTP/1.0
Content-Length: 10
Host: localhost:80

skip_body=<int> excludes the body from the result if it’s longer than that number

>>> print req.as_string(skip_body=5)
PUT / HTTP/1.0
Content-Length: 10
Host: localhost:80
<BLANKLINE>
<body skipped (len=10)>

but not if it’s shorter

>>> print req.as_string(skip_body=100)
PUT / HTTP/1.0
Content-Length: 10
Host: localhost:80
<BLANKLINE>
xxxxxxxxxx

This Page