diff options
Diffstat (limited to 'docs/test_request.txt')
-rw-r--r-- | docs/test_request.txt | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/docs/test_request.txt b/docs/test_request.txt new file mode 100644 index 0000000..66d26dd --- /dev/null +++ b/docs/test_request.txt @@ -0,0 +1,553 @@ +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 + <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)) + >>> 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 + <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([('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 + <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.5" + >>> req.accept # doctest: +ELLIPSIS + <MIMEAccept at ... Accept: text/*;q=0.3, text/html;q=0.7, text/html, text/html;q=0.4, */*;q=0.5> + >>> 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 + <MIMEAccept at ... Accept: 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.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', '') # 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.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', '') # 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_.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 + |