diff options
Diffstat (limited to 'google_appengine/lib/webob/docs/reference.txt')
-rw-r--r-- | google_appengine/lib/webob/docs/reference.txt | 1001 |
1 files changed, 1001 insertions, 0 deletions
diff --git a/google_appengine/lib/webob/docs/reference.txt b/google_appengine/lib/webob/docs/reference.txt new file mode 100644 index 0000000..9cebd85 --- /dev/null +++ b/google_appengine/lib/webob/docs/reference.txt @@ -0,0 +1,1001 @@ +WebOb Reference ++++++++++++++++ + +.. contents:: + +.. comment: + + >>> from dtopt import ELLIPSIS + +Introduction +============ + +This document covers all the details of the Request and Response +objects. It is written to be testable with `doctest +<http://python.org/doc/current/lib/module-doctest.html>`_ -- this +effects the flavor of the documentation, perhaps to its detriment. +But it also means you can feel confident that the documentation is +correct. + +This is a somewhat different approach to reference documentation +compared to the extracted documentation for the `request +<class-webob.Request.html>`_ and `response +<class-webob.Response.html>`_. + +Request +======= + +The primary object in WebOb is ``webob.Request``, a wrapper around a +`WSGI environment <http://www.python.org/dev/peps/pep-0333/>`_. + +The basic way you create a request object is simple enough: + +.. code-block:: + + >>> from webob import Request + >>> environ = {} + >>> req = Request(environ) + +The request object *wraps* the environment; it has very little +internal state of its own. Instead attributes you access read and +write to the environment dictionary. + +You don't have to understand the details of WSGI to use this library; +this library handles those details for you. You also don't have to +use this exclusively of other libraries. If those other libraries +also keep their state in the environment, multiple wrappers can +coexist. Examples of libraries that can coexist include +`paste.wsgiwrappers.Request +<http://pythonpaste.org/class-paste.wsgiwrappers.WSGIRequest.html>`_ +(used by Pylons) and `yaro.Request +<http://lukearno.com/projects/yaro/>`_. + +The WSGI environment has a number of required variables. To make it +easier to test and play around with, the ``Request`` class has a +constructor that will fill in a minimal environment: + +.. code-block:: + + >>> req = Request.blank('/article?id=1') + >>> from pprint import pprint + >>> pprint(req.environ) + {'HTTP_HOST': 'localhost:80', + 'PATH_INFO': '/article', + 'QUERY_STRING': 'id=1', + 'REQUEST_METHOD': 'GET', + 'SCRIPT_NAME': '', + 'SERVER_NAME': 'localhost', + 'SERVER_PORT': '80', + 'SERVER_PROTOCOL': 'HTTP/1.0', + 'wsgi.errors': <open file '<stderr>', mode 'w' at ...>, + 'wsgi.input': <cStringIO.StringI object at ...>, + 'wsgi.multiprocess': False, + 'wsgi.multithread': False, + 'wsgi.run_once': False, + 'wsgi.url_scheme': 'http', + 'wsgi.version': (1, 0)} + +Request Body +------------ + +``req.body`` is a file-like object that gives the body of the request +(e.g., a POST form, the body of a PUT, etc). It's kind of boring to +start, but you can set it to a string and that will be turned into a +file-like object. You can read the entire body with +``req.body``. + +.. code-block:: + + >>> req.body_file + <cStringIO.StringI object at ...> + >>> req.body + '' + >>> req.body = 'test' + >>> req.body_file + <cStringIO.StringI object at ...> + >>> req.body + 'test' + +Method & URL +------------ + +All the normal parts of a request are also accessible through the +request object: + +.. code-block:: + + >>> req.method + 'GET' + >>> req.scheme + 'http' + >>> req.script_name # The base of the URL + '' + >>> req.script_name = '/blog' # make it more interesting + >>> req.path_info # The yet-to-be-consumed part of the URL + '/article' + >>> req.content_type # Content-Type of the request body + '' + >>> print req.remote_user # The authenticated user (there is none set) + None + >>> print req.remote_addr # The remote IP + None + >>> req.host + 'localhost:80' + >>> req.host_url + 'http://localhost' + >>> req.application_url + 'http://localhost/blog' + >>> req.path_url + 'http://localhost/blog/article' + >>> req.url + 'http://localhost/blog/article?id=1' + >>> req.path + '/blog/article' + >>> req.path_qs + '/blog/article?id=1' + >>> req.query_string + 'id=1' + +You can make new URLs: + +.. code-block:: + + >>> req.relative_url('archive') + 'http://localhost/blog/archive' + +For parsing the URLs, it is often useful to deal with just the next +path segment on PATH_INFO: + +.. code-block:: + + >>> req.path_info_peek() # Doesn't change request + 'article' + >>> req.path_info_pop() # Does change request! + 'article' + >>> req.script_name + '/blog/article' + >>> req.path_info + '' + +Headers +------- + +All request headers are available through a dictionary-like object +``req.headers``. Keys are case-insensitive. + +.. code-block:: + + >>> req.headers['content-type'] = 'application/x-www-urlencoded' + >>> req.headers + {'Content-Length': '4', 'Content-Type': 'application/x-www-urlencoded', 'Host': 'localhost:80'} + >>> req.environ['CONTENT_TYPE'] + 'application/x-www-urlencoded' + +Query & POST variables +---------------------- + +Requests can have variables in one of two locations: the query string +(``?id=1``), or in the body of the request (generally a POST form). +Note that even POST requests can have a query string, so both kinds of +variables can exist at the same time. Also, a variable can show up +more than once, as in ``?check=a&check=b``. + +For these variables WebOb uses a `MultiDict +<class-webob.multidict.MultiDict.html>`_, which is basically a +dictionary wrapper on a list of key/value pairs. It looks like a +single-valued dictionary, but you can access all the values of a key +with ``.getall(key)`` (which always returns a list, possibly an empty +list). You also get all key/value pairs when using ``.items()`` and +all values with ``.values()``. + +Some examples: + +.. code-block:: + + >>> req = Request.blank('/test?check=a&check=b&name=Bob') + >>> req.GET + MultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob')]) + >>> req.GET['check'] + 'b' + >>> req.GET.getall('check') + ['a', 'b'] + >>> req.GET.items() + [('check', 'a'), ('check', 'b'), ('name', 'Bob')] + +We'll have to create a request body and change the method to get +POST. Until we do that, the variables are boring: + +.. code-block:: + + >>> req.POST + <NoVars: Not a POST request> + >>> req.POST.items() # NoVars can be read like a dict, but not written + [] + >>> req.method = 'POST' + >>> req.body = 'name=Joe&email=joe@example.com' + >>> req.POST + MultiDict([('name', 'Joe'), ('email', 'joe@example.com')]) + >>> req.POST['name'] + 'Joe' + +Often you won't care where the variables come from. (Even if you care +about the method, the location of the variables might not be +important.) There is a dictionary called ``req.params`` that +contains variables from both sources: + +.. code-block:: + + >>> req.params + NestedMultiDict([('check', 'a'), ('check', 'b'), ('name', 'Bob'), ('name', 'Joe'), ('email', 'joe@example.com')]) + >>> req.params['name'] + 'Bob' + >>> req.params.getall('name') + ['Bob', 'Joe'] + >>> for name, value in req.params.items(): + ... print '%s: %r' % (name, value) + check: 'a' + check: 'b' + name: 'Bob' + name: 'Joe' + email: 'joe@example.com' + +Unicode Variables +~~~~~~~~~~~~~~~~~ + +Submissions are non-unicode (``str``) strings, unless some character +set is indicated. A client can indicate the character set with +``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but +very few clients actually do this (sometimes XMLHttpRequest requests +will do this, as JSON is always UTF8 even when a page is served with a +different character set). You can force a charset, which will effect +all the variables: + +.. code-block:: + + >>> req.charset = 'utf8' + >>> req.GET + UnicodeMultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]) + +If you always want ``str`` values, you can use ``req.str_GET`` +and ``str_POST``. + +Cookies +------- + +Cookies are presented in a simple dictionary. Like other variables, +they will be decoded into Unicode strings if you set the charset. + +.. code-block:: + + >>> req.headers['Cookie'] = 'test=value' + >>> req.cookies + UnicodeMultiDict([(u'test', u'value')]) + >>> req.charset = None + >>> req.cookies + {'test': 'value'} + +Modifying the request +--------------------- + +The headers are all modifiable, as are other environmental variables +(like ``req.remote_user``, which maps to +``request.environ['REMOTE_USER']``). + +If you want to copy the request you can use ``req.copy()``; this +copies the ``environ`` dictionary, and the request body from +``environ['wsgi.input']``. + +The method ``req.remove_conditional_headers(remove_encoding=True)`` +can be used to remove headers that might result in a ``304 Not +Modified`` response. If you are writing some intermediary it can be +useful to avoid these headers. Also if ``remove_encoding`` is true +(the default) then any ``Accept-Encoding`` header will be removed, +which can result in gzipped responses. + +Header Getters +-------------- + +In addition to ``req.headers``, there are attributes for most of the +request headers defined by the HTTP 1.1 specification. These +attributes often return parsed forms of the headers. + +Accept-* headers +~~~~~~~~~~~~~~~~ + +There are several request headers that tell the server what the client +accepts. These are ``accept`` (the Content-Type that is accepted), +``accept_charset`` (the charset accepted), ``accept_encoding`` +(the Content-Encoding, like gzip, that is accepted), and +``accept_language`` (generally the preferred language of the client). + +The objects returned support containment to test for acceptability. +E.g.: + +.. code-block:: + + >>> 'text/html' in req.accept + True + +Because no header means anything is potentially acceptable, this is +returning True. We can set it to see more interesting behavior (the +example means that ``text/html`` is okay, but +``application/xhtml+xml`` is preferred): + +.. code-block:: + + >>> req.accept = 'text/html;q=0.5, application/xhtml+xml;q=1' + >>> req.accept + <MIMEAccept at ... Accept: text/html;q=0.5, application/xhtml+xml> + >>> 'text/html' in req.accept + True + +There's three methods for different strategies of finding a match. +First, when you trust the server's preference over the client (a good +idea for Accept): + +.. code-block:: + + >>> req.accept.first_match(['text/html', 'application/xhtml+xml']) + 'text/html' + +Because ``text/html`` is at least *somewhat* acceptible, it is +returned, even if the client says it prefers +``application/xhtml+xml``. If we trust the client more: + +.. code-block:: + + >>> req.accept.best_match(['text/html', 'application/xhtml+xml']) + 'application/xhtml+xml' + +If we just want to know everything the client prefers, in the order it +is preferred: + +.. code-block:: + + >>> req.accept.best_matches() + ['application/xhtml+xml', 'text/html'] + +For languages you'll often have a "fallback" language. E.g., if there's +nothing better then use ``en-US`` (and if ``en-US`` is okay, ignore +any less preferrable languages): + +.. code-block:: + + >>> req.accept_language = 'es, pt-BR' + >>> req.accept_language.best_matches('en-US') + ['es', 'pt-BR', 'en-US'] + >>> req.accept_language.best_matches('es') + ['es'] + +Conditional Requests +~~~~~~~~~~~~~~~~~~~~ + +There a number of ways to make a conditional request. A conditional +request is made when the client has a document, but it is not sure if +the document is up to date. If it is not, it wants a new version. If +the document is up to date then it doesn't want to waste the +bandwidth, and expects a ``304 Not Modified`` response. + +ETags are generally the best technique for these kinds of requests; +this is an opaque string that indicates the identity of the object. +For instance, it's common to use the mtime (last modified) of the file, +plus the number of bytes, and maybe a hash of the filename (if there's +a possibility that the same URL could point to two different +server-side filenames based on other variables). To test if a 304 +response is appropriate, you can use: + +.. code-block:: + + >>> server_token = 'opaque-token' + >>> server_token in req.if_none_match # You shouldn't return 304 + False + >>> req.if_none_match = server_token + >>> req.if_none_match + <ETag opaque-token> + >>> server_token in req.if_none_match # You *should* return 304 + True + +For date-based comparisons If-Modified-Since is used: + +.. code-block:: + + >>> from webob import UTC + >>> from datetime import datetime + >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC) + >>> req.headers['If-Modified-Since'] + 'Sun, 01 Jan 2006 12:00:00 GMT' + >>> server_modified = datetime(2005, 1, 1, 12, 0, tzinfo=UTC) + >>> req.if_modified_since and req.if_modified_since >= server_modified + True + +For range requests there are two important headers, If-Range (which is +form of conditional request) and Range (which requests a range). If +the If-Range header fails to match then the full response (not a +range) should be returned: + +.. code-block:: + + >>> req.if_range + <Empty If-Range> + >>> req.if_range.match(etag='some-etag', last_modified=datetime(2005, 1, 1, 12, 0)) + True + >>> req.if_range = 'opaque-etag' + >>> req.if_range.match(etag='other-etag') + False + >>> req.if_range.match(etag='opaque-etag') + True + +You can also pass in a response object with: + +.. code-block:: + + >>> from webob import Response + >>> res = Response(etag='opaque-etag') + >>> req.if_range.match_response(res) + True + +To get the range information: + + >>> req.range = 'bytes=0-100' + >>> req.range + <Range ranges=(0, 99)> + >>> cr = req.range.content_range(length=1000) + >>> cr.start, cr.stop, cr.length + (0, 99, 1000) + +Note that the range headers use *inclusive* ranges (the last byte +indexed is included), where Python always uses a range where the last +index is excluded from the range. The ``.stop`` index is in the +Python form. + +Another kind of conditional request is a request (typically PUT) that +includes If-Match or If-Unmodified-Since. In this case you are saying +"here is an update to a resource, but don't apply it if someone else +has done something since I last got the resource". If-Match means "do +this if the current ETag matches the ETag I'm giving". +If-Unmodified-Since means "do this if the resource has remained +unchanged". + +.. code-block:: + + >>> server_token in req.if_match # No If-Match means everything is ok + True + >>> req.if_match = server_token + >>> server_token in req.if_match # Still OK + True + >>> req.if_match = 'other-token' + >>> # Not OK, should return 412 Precondition Failed: + >>> server_token in req.if_match + False + +For more on this kind of conditional request, see `Detecting the Lost +Update Problem Using Unreserved Checkout +<http://www.w3.org/1999/04/Editing/>`_. + +Calling WSGI Applications +------------------------- + +The request object can be used to make handy subrequests or test +requests against WSGI applications. If you want to make subrequests, +you should copy the request (with ``req.copy()``) before sending it to +multiple applications, since applications might modify the request +when they are run. + +There's two forms of the subrequest. The more primitive form is +this: + +.. code-block:: + + >>> req = Request.blank('/') + >>> def wsgi_app(environ, start_response): + ... start_response('200 OK', [('Content-type', 'text/plain')]) + ... return ['Hi!'] + >>> req.call_application(wsgi_app) + ('200 OK', [('Content-type', 'text/plain')], ['Hi!']) + +Note it returns ``(status_string, header_list, app_iter)``. If +``app_iter.close()`` exists, it is your responsibility to call it. + +A handier response can be had with: + +.. code-block:: + + >>> res = req.get_response(wsgi_app) + >>> res + <Response ... 200 OK> + >>> res.status + '200 OK' + >>> res.headers + HeaderDict([('Content-type', 'text/plain')]) + >>> res.body + 'Hi!' + +You can learn more about this response object in the Response_ section. + +Thread-local Request Wrappers +----------------------------- + +You can also give the ``Request`` object a function to get the +environment. This can be used to make a single global request object +dynamic. An example: + +.. code-block:: + + >>> import threading + >>> import webob + >>> environments = threading.local() + >>> def get_environ(): + ... return environments.environ + >>> def set_thread_environ(environ): + ... environments.environ = environ + >>> request = webob.Request(environ_getter=get_environ) + >>> set_thread_environ({'SCRIPT_NAME': '/test'}) + >>> request.script_name + '/test' + +Ad-Hoc Attributes +----------------- + +You can assign attributes to your request objects. They will all go +in ``environ['webob.adhoc_attrs']`` (a dictionary). + +.. code-block:: + + >>> req = Request.blank('/') + >>> req.some_attr = 'blah blah blah' + >>> new_req = Request(req.environ) + >>> new_req.some_attr + 'blah blah blah' + >>> req.environ['webob.adhoc_attrs'] + {'some_attr': 'blah blah blah'} + +Response +======== + +The ``webob.Response`` object contains everything necessary to make a +WSGI response. Instances of it are in fact WSGI applications, but it +can also represent the result of calling a WSGI application (as noted +in `Calling WSGI Applications`_). It can also be a way of +accumulating a response in your WSGI application. + +A WSGI response is made up of a status (like ``200 OK``), a list of +headers, and a body (or iterator that will produce a body). + +Core Attributes +--------------- + +The core attributes are unsurprising: + +.. code-block:: + + >>> from webob import Response + >>> res = Response() + >>> res.status + '200 OK' + >>> res.headerlist + [('Content-Length', '0')] + >>> res.body + '' + +You can set any of these attributes, e.g.: + +.. code-block:: + + >>> res.status = 404 + >>> res.status + '404 Not Found' + >>> res.status_int + 404 + >>> res.headerlist = [('Content-type', 'text/html')] + >>> res.body = 'test' + >>> print res + 404 Not Found + Content-type: text/html + Content-Length: 4 + <BLANKLINE> + test + >>> res.body = u"test" + Traceback (most recent call last): + ... + TypeError: You cannot set Response.body to a unicode object (use Response.unicode_body) + >>> res.unicode_body = u"test" + Traceback (most recent call last): + ... + AttributeError: You cannot access Response.unicode_body unless charset is set + >>> res.charset = 'utf8' + >>> res.unicode_body = u"test" + >>> res.body + 'test' + +You can set any attribute with the constructor, like +``Response(charset='utf8')`` + +Headers +------- + +In addition to ``res.headerlist``, there is dictionary-like view on +the list in ``res.headers``: + +.. code-block:: + + >>> res.headers + HeaderDict([('content-type', 'text/html; charset=utf8'), ('Content-Length', '4')]) + +This is case-insensitive. It can support multiple values for a key, +though only if you use ``res.headers.add(key, value)`` or read them +with ``res.headers.getall(key)``. + +Body & app_iter +--------------- + +The ``res.body`` attribute represents the entire body of the request +as a single string (not unicode, though you can set it to unicode if +you have a charset defined). There is also a ``res.app_iter`` +attribute that reprsents the body as an iterator. WSGI applications +return these ``app_iter`` iterators instead of strings, and sometimes +it can be problematic to load the entire iterator at once (for +instance, if it returns the contents of a very large file). Generally +it is not a problem, and often the iterator is something simple like a +one-item list containing a string with the entire body. + +If you set the body then Content-Length will also be set, and an +``res.app_iter`` will be created for you. If you set ``res.app_iter`` +then Content-Length will be cleared, but it won't be set for you. + +There is also a file-like object you can access, which will update the +app_iter in-place (turning the app_iter into a list if necessary): + +.. code-block:: + + >>> res = Response(content_type='text/plain') + >>> f = res.body_file + >>> f.write('hey') + >>> f.write(u'test') + Traceback (most recent call last): + . . . + TypeError: You can only write unicode to Response.body_file if charset has been set + >>> f.encoding + >>> res.charset = 'utf8' + >>> f.encoding + 'utf8' + >>> f.write(u'test') + >>> res.app_iter + ['hey', 'test'] + >>> res.body + 'heytest' + +Header Getters +-------------- + +Like Request, HTTP response headers are also available as individual +properties. These represent parsed forms of the headers. + +Content-Type is a special case, as the type and the charset are +handled through two separate properties: + +.. code-block:: + + >>> res = Response() + >>> res.content_type = 'text/html' + >>> res.charset = 'utf8' + >>> res.content_type + 'text/html' + >>> res.headers['content-type'] + 'text/html; charset=utf8' + >>> res.content_type = 'application/atom+xml' + >>> res.content_type_params + {'charset': 'utf8'} + >>> res.content_type_params = {'type': 'entry', 'charset': 'utf8'} + >>> res.headers['content-type'] + 'application/atom+xml; charset=utf8; type=entry' + +Other headers: + +.. code-block:: + + >>> # Used with a redirect: + >>> res.location = 'http://localhost/foo' + + >>> # Indicates that the server accepts Range requests: + >>> res.accept_ranges = 'bytes' + + >>> # Used by caching proxies to tell the client how old the + >>> # response is: + >>> res.age = 120 + + >>> # Show what methods the client can do; typically used in + >>> # a 405 Method Not Allowed response: + >>> res.allow = ['GET', 'PUT'] + + >>> # Set the cache-control header: + >>> res.cache_control.max_age = 360 + >>> res.cache_control.no_transform = True + + >>> # Used if you had gzipped the body: + >>> res.content_encoding = 'gzip' + + >>> # What language(s) are in the content: + >>> res.content_language = ['en'] + + >>> # Seldom used header that tells the client where the content + >>> # is from: + >>> res.content_location = 'http://localhost/foo' + + >>> # Seldom used header that gives a hash of the body: + >>> res.content_md5 = 'big-hash' + + >>> # Means we are serving bytes 0-500 inclusive, out of 1000 bytes total: + >>> # you can also use the range setter shown earlier + >>> res.content_range = (0, 499, 1000) + + >>> # The length of the content; set automatically if you set + >>> # res.body: + >>> res.content_length = 4 + + >>> # Used to indicate the current date as the server understands + >>> # it: + >>> res.date = datetime.now() + + >>> # The etag: + >>> res.etag = 'opaque-token' + >>> # You can generate it from the body too: + >>> res.md5_etag() + >>> res.etag + '1B2M2Y8AsgTpgAmY7PhCfg' + + >>> # When this page should expire from a cache (Cache-Control + >>> # often works better): + >>> import time + >>> res.expires = time.time() + 60*60 # 1 hour + + >>> # When this was last modified, of course: + >>> res.last_modified = datetime(2007, 1, 1, 12, 0, tzinfo=UTC) + + >>> # Used with 503 Service Unavailable to hint the client when to + >>> # try again: + >>> res.retry_after = 160 + + >>> # Indicate the server software: + >>> res.server = 'WebOb/1.0' + + >>> # Give a list of headers that the cache should vary on: + >>> res.vary = ['Cookie'] + +Note in each case you can general set the header to a string to avoid +any parsing, and set it to None to remove the header (or do something +like ``del res.vary``). + +In the case of date-related headers you can set the value to a +``datetime`` instance (ideally with a UTC timezone), a time tuple, an +integer timestamp, or a properly-formatted string. + +After setting all these headers, here's the result: + +.. code-block:: + + >>> for name, value in res.headerlist: + ... print '%s: %s' % (name, value) + content-type: application/atom+xml; charset=utf8; type=entry + location: http://localhost/foo + Accept-Ranges: bytes + Age: 120 + Allow: GET, PUT + Cache-Control: max-age=360, no-transform + Content-Encoding: gzip + Content-Language: en + Content-Location: http://localhost/foo + Content-MD5: big-hash + Content-Range: bytes 0-500/1000 + Content-Length: 4 + Date: ... GMT + ETag: ... + Expires: ... GMT + Last-Modified: Mon, 01 Jan 2007 12:00:00 GMT + Retry-After: 160 + Server: WebOb/1.0 + Vary: Cookie + +You can also set Cache-Control related attributes with +``req.cache_expires(seconds, **attrs)``, like: + +.. code-block:: + + >>> res = Response() + >>> res.cache_expires(10) + >>> res.headers['Cache-Control'] + 'max-age=10' + >>> res.cache_expires(0) + >>> res.headers['Cache-Control'] + 'max-age=0, must-revalidate, no-cache, no-store' + >>> res.headers['Expires'] + '... GMT' + +You can also use the `timedelta +<http://python.org/doc/current/lib/datetime-timedelta.html>`_ +constants defined, e.g.: + +.. code-block:: + + >>> from webob import * + >>> res = Response() + >>> res.cache_expires(2*day+4*hour) + >>> res.headers['Cache-Control'] + 'max-age=187200' + +Cookies +------- + +Cookies (and the Set-Cookie header) are handled with a couple +methods. Most importantly: + +.. code-block:: + + >>> res.set_cookie('key', 'value', max_age=360, path='/', + ... domain='example.org', secure=True) + >>> res.headers['Set-Cookie'] + 'key=value; Domain=example.org; Max-Age=360; Path=/; secure;' + >>> # To delete a cookie previously set in the client: + >>> res.delete_cookie('bad_cookie') + >>> res.headers['Set-Cookie'] + 'bad_cookie=; Max-Age=0; Path=/;' + +The only other real method of note (note that this does *not* delete +the cookie from clients, only from the response object): + +.. code-block:: + + >>> res.unset_cookie('key') + >>> res.unset_cookie('bad_cookie') + >>> print res.headers.get('Set-Cookie') + None + +Binding a Request +----------------- + +You can bind a request (or request WSGI environ) to the response +object. This is available through ``res.request`` or +``res.environ``. This is currently only used in setting +``res.location``, to make the location absolute if necessary. + +Response as a WSGI application +------------------------------ + +A response is a WSGI application, in that you can do: + +.. code-block:: + + >>> req = Request.blank('/') + >>> status, headers, app_iter = req.call_application(res) + +A possible pattern for your application might be: + +.. code-block:: + + >>> def my_app(environ, start_response): + ... req = Request(environ) + ... res = Response() + ... res.content_type = 'text/plain' + ... parts = [] + ... for name, value in sorted(req.environ.items()): + ... parts.append('%s: %r' % (name, value)) + ... res.body = '\n'.join(parts) + ... return res(environ, start_response) + >>> req = Request.blank('/') + >>> res = req.get_response(my_app) + >>> print res + 200 OK + content-type: text/plain + Content-Length: 394 + <BLANKLINE> + HTTP_HOST: 'localhost:80' + PATH_INFO: '/' + QUERY_STRING: '' + REQUEST_METHOD: 'GET' + SCRIPT_NAME: '' + SERVER_NAME: 'localhost' + SERVER_PORT: '80' + SERVER_PROTOCOL: 'HTTP/1.0' + wsgi.errors: <open file '<stderr>', mode 'w' at ...> + wsgi.input: <cStringIO.StringI object at ...> + wsgi.multiprocess: False + wsgi.multithread: False + wsgi.run_once: False + wsgi.url_scheme: 'http' + wsgi.version: (1, 0) + +Exceptions +========== + +In addition to Request and Response objects, there are a set of Python +exceptions for different HTTP responses (3xx, 4xx, 5xx codes). + +These provide a simple way to provide these non-200 response. A very +simple body is provided. + +.. code-block:: + + >>> from webob.exc import * + >>> exc = HTTPTemporaryRedirect(location='foo') + >>> req = Request.blank('/path/to/something') + >>> print str(req.get_response(exc)).strip() + 307 Temporary Redirect + content-type: text/html + location: http://localhost/path/to/foo + Content-Length: 126 + <BLANKLINE> + 307 Temporary Redirect + <BLANKLINE> + The resource has been moved to http://localhost/path/to/foo; you should be redirected automatically. + +Note that only if there's an ``Accept: text/html`` header in the +request will an HTML response be given: + +.. code-block:: + + >>> req.accept += 'text/html' + >>> print str(req.get_response(exc)).strip() + 307 Temporary Redirect + content-type: text/html + location: http://localhost/path/to/foo + Content-Length: 270 + <BLANKLINE> + <html> + <head> + <title>307 Temporary Redirect</title> + </head> + <body> + <h1>307 Temporary Redirect</h1> + The resource has been moved to <a href="http://localhost/path/to/foo">http://localhost/path/to/foo</a>; + you should be redirected automatically. + <BLANKLINE> + <BLANKLINE> + </body> + </html> + + +This is taken from `paste.httpexceptions +<http://pythonpaste.org/module-paste.httpexceptions.html>`_, and if +you have Paste installed then these exceptions will be subclasses of +the Paste exceptions. + +Note that on Python 2.4 and before, new-style classes could not be +used as exceptions. ``Response`` objects must be new-style classes, +so this causes a bit of a conflict. The base class +``webob.exc.HTTPException`` *is* an exception, so you can catch that, +and it *is* a WSGI application. But you may not be able to use +Response methods. You can always get ``obj.exception`` to get an +exception that you can raise, and ``obj.wsgi_response`` to get the +``Response`` object that you can use. + +Conditional WSGI Application +---------------------------- + +The Response object can handle your conditional responses for you, +checking If-None-Match, If-Modified-Since, and Range/If-Range. + +To enable this you must create the response like +``Response(conditional_request=True)``, or make a subclass like: + +.. code-block:: + + >>> class AppResponse(Response): + ... default_content_type = 'text/html' + ... default_conditional_response = True + >>> res = AppResponse(body='0123456789', + ... last_modified=datetime(2005, 1, 1, 12, 0, tzinfo=UTC)) + >>> req = Request.blank('/') + >>> req.if_modified_since = datetime(2006, 1, 1, 12, 0, tzinfo=UTC) + >>> req.get_response(res) + <Response ... 304 Not Modified> + >>> del req.if_modified_since + >>> res.etag = 'opaque-tag' + >>> req.if_none_match = 'opaque-tag' + >>> req.get_response(res) + <Response ... 304 Not Modified> + >>> del req.if_none_match + >>> req.range = (1, 5) + >>> result = req.get_response(res) + >>> result.headers['content-range'] + 'bytes 1-6/10' + >>> result.body + '1234' |