thirdparty/google_appengine/lib/webob/docs/reference.txt
changeset 109 620f9b141567
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/lib/webob/docs/reference.txt	Tue Aug 26 21:49:54 2008 +0000
@@ -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'