This demonstrates how the Request object works, and tests it. To test for absense of PendingDeprecationWarning's we should reset default warning filters >>> import warnings >>> warnings.resetwarnings() 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 >>> 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)) >>> vars = req.str_POST >>> vars MultiDict([('var1', 'value1'), ('var2', 'value2'), ('rep', '1'), ('rep', '2')]) >>> vars is req.POST False >>> req.POST UnicodeMultiDict([('var1', u'value1'), ('var2', u'value2'), ('rep', u'1'), ('rep', u'2')]) >>> req.decode_param_names False 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 >>> 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('') >>> req.str_POST >>> req.body '' 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([('a', u'b'), ('d', u'e'), ('d', u'f'), ('x', u'y'), ('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()) [('var1', u'value1'), ('var2', u'value2')] >>> req.charset = 'utf8' >>> sorted(req.cookies.items()) [('var1', u'value1'), ('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 >>> req.cache_control.no_cache = True >>> req.cache_control.max_age = 0 >>> req.cache_control .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.5" >>> req.accept # doctest: +ELLIPSIS >>> 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.5 >>> '%0.1f' % req.accept.quality('text/html') '0.3' >>> 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 # doctest: +ELLIPSIS >>> 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 >>> 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 >>> 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 >>> req.if_range.match_response(res) True Ranges work like so:: >>> req = Request.blank('/') >>> req.range = (0, 100) >>> req.range >>> 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 >>> 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.body_file = data >>> req.body_file_raw <...IO... object at ...> >>> len(req.body) 10250 >>> req.body_file >>> 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 ... ... Some text... ... --foobar-- ... ''' >>> req.str_POST MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html'))]) >>> print req.body.replace('\r', '') # doctest: +REPORT_UDIFF --foobar Content-Disposition: form-data; name="a" b --foobar Content-Disposition: form-data; name="upload"; filename="test.html" Content-type: text/html Some text... --foobar-- >>> req.POST['c'] = 'd' >>> req.str_POST MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html')), ('c', 'd')]) >>> req.body_file_raw >>> sorted(req.str_POST.keys()) ['a', 'c', 'upload'] >>> print req.body.replace('\r', '') # doctestx: +REPORT_UDIFF --foobar Content-Disposition: form-data; name="a" b --foobar Content-Disposition: form-data; name="upload"; filename="test.html" Content-type: text/html Some text... --foobar Content-Disposition: form-data; name="c" 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 ... ... Some text... ... --foobar-- ... ''' >>> req_.str_POST MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html'))]) >>> print req_.body.replace('\r', '') # doctest: +REPORT_UDIFF --foobar Content-Disposition: form-data; name="a" b --foobar Content-Disposition: form-data; name="upload"; filename="test.html" Content-type: text/html Some text... --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', 'Some text...\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 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= 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 but not if it's shorter >>> print req.as_string(skip_body=100) PUT / HTTP/1.0 Content-Length: 10 Host: localhost:80 xxxxxxxxxx