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 # doctest: +ELLIPSIS <Request at ... GET http://localhost/> >>> print req GET / HTTP/1.0 Host: localhost:80 >>> req.environ # doctest: +ELLIPSIS {...} >>> 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)) >>> req.POST MultiDict([(u'var1', u'value1'), (u'var2', u'value2'), (u'rep', u'1'), (u'rep', u'2')]) Note that the variables are there for GET requests and non-form requests, but they are empty and read-only: >>> req = Request.blank('/') >>> req.POST <NoVars: Not a form request> >>> req.POST.items() [] >>> req.POST['x'] = 'y' Traceback (most recent call last): ... KeyError: 'Cannot add variables: Not a form request' >>> req.method = 'POST' >>> req.POST MultiDict([]) >>> req.content_type = 'text/xml' >>> req.body_file = StringIO('<xml></xml>') >>> req.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.GET MultiDict([(u'a', u'b'), (u'd', u'e'), (u'd', u'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 NestedMultiDict([(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.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 # doctest: +ELLIPSIS <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' >>> '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 # doctest: +ELLIPSIS <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 = "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 <NoVars: Not an HTML form submission (Content-Type: )> >>> 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.GET MultiDict([(u'a', u'b')]) >>> req.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.POST MultiDict([(u'a', u'b'), (u'upload', FieldStorage(u'upload', u'test.html'))]) >>> print req.body.replace('\r', '') # doctest: +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-- >>> req.POST['c'] = 'd' >>> req.POST MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html')), ('c', 'd')]) >>> req.body_file_raw <....BufferedReader> >>> req.body_file_raw.raw <FakeCGIBody at ... viewing MultiDict([('a'...d')])> >>> sorted(req.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_.POST MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html'))]) >>> print req_.body.replace('\r', '') # doctest: +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-- >>> req_.POST['c'] = 'd' >>> req_.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.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(request) returns the request as HTTP request string >>> print req PUT / HTTP/1.0 Content-Length: 10 Host: localhost:80 <BLANKLINE> xxxxxxxxxx req.as_text() does the same thing but also can take additional argument `skip_body` skip_body=True excludes the body from the result >>> print req.as_text(skip_body=True) PUT / HTTP/1.0 Content-Length: 10 Host: localhost:80 req.as_bytes() returns the body as bytes; it does the same thing as as_text except the result is bytes. >>> print req.as_bytes(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_bytes(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_bytes(skip_body=100) PUT / HTTP/1.0 Content-Length: 10 Host: localhost:80 <BLANKLINE> xxxxxxxxxx