1Differences Between WebOb and Other Systems
2+++++++++++++++++++++++++++++++++++++++++++
3
4This document points out some of the API differences between the
5Request and Response object, and the objects in other systems.
6
7.. contents::
8
9paste.wsgiwrappers and Pylons
10=============================
11
12The Pylons ``request`` and ``response`` object are based on
13``paste.wsgiwrappers.WSGIRequest`` and ``WSGIResponse``
14
15There is no concept of ``defaults`` in WebOb.  In Paste/Pylons these
16serve as threadlocal settings that control certain policies on the
17request and response object.  In WebOb you should make your own
18subclasses to control policy (though in many ways simply being
19explicit elsewhere removes the need for this policy).
20
21Request
22-------
23
24``body``:
25    This is a file-like object in WSGIRequest.  In WebOb it is a
26    string (to match Response.body) and the file-like object is
27    available through ``req.body_file``
28
29``languages()``:
30    This is available through ``req.accept_language``, particularly
31    ``req.accept_language.best_match(supported_languages)``
32
33``match_accept(mimetypes)``:
34    This is available through ``req.accept.first_match(mimetypes)``;
35    or if you trust the client's quality ratings, you can use
36    ``req.accept.best_match(mimetypes)``
37
38``errors``:
39    This controls how unicode decode errors are handled; it is now
40    named ``unicode_errors``
41
42There are also many extra methods and attributes on WebOb Request
43objects.
44
45Response
46--------
47
48``determine_charset()``:
49    Is now available as ``res.charset``
50
51``has_header(header)``:
52    Should be done with ``header in res.headers``
53
54``get_content()`` and ``wsgi_response()``:
55    These are gone; you should use ``res.body`` or ``res(environ,
56    start_response)``
57
58``write(content)``:
59    Available in ``res.body_file.write(content)``.
60
61``flush()`` and ``tell()``:
62    Not available.
63
64There are also many extra methods and attributes on WebOb Response
65objects.
66
67Django
68======
69
70This is a quick summary from reading `the Django documentation
71<http://www.djangoproject.com/documentation/request_response/>`_.
72
73Request
74-------
75
76``encoding``:
77    Is ``req.charset``
78
79``REQUEST``:
80    Is ``req.params``
81
82``FILES``:
83    File uploads are ``cgi.FieldStorage`` objects directly in
84    ``res.POST``
85
86``META``:
87    Is ``req.environ``
88
89``user``:
90    No equivalent (too connected to application model for WebOb).
91    There is ``req.remote_user``, which is only ever a string.
92
93``session``:
94    No equivalent
95
96``raw_post_data``:
97    Available with ``req.body``
98
99``__getitem__(key)``:
100    You have to use ``req.params``
101
102``is_secure()``:
103    No equivalent; you could use ``req.scheme == 'https'``.
104
105QueryDict
106---------
107
108QueryDict is the way Django represents the multi-key dictionary-like
109objects that are request variables (query string and POST body
110variables).  The equivalent in WebOb is MultiDict.
111
112Mutability:
113    WebOb dictionaries are sometimes mutable (req.GET is,
114    req.params is not)
115
116Ordering:
117    I believe Django does not order the keys fully; MultiDict is a
118    full ordering.  Methods that iterate over the parameters iterate
119    over keys in their order in the original request.
120
121``keys()``, ``items()``, ``values()`` (plus ``iter*``):
122    These return all values in MultiDict, but only the last value for
123    a QueryDict.  That is, given ``a=1&a=2`` with MultiDict
124    ``d.items()`` returns ``[('a', '1'), ('a', '2')]``, but QueryDict
125    returns ``[('a', '1')]``
126
127``getlist(key)``:
128    Available as ``d.getall(key)``
129
130``setlist(key)``:
131    No direct equivalent
132
133``appendlist(key, value)``:
134    Available as ``d.add(key, value)``
135
136``setlistdefault(key, default_list)``:
137    No direct equivalent
138
139``lists()``:
140    Is ``d.dict_of_lists()``
141
142The MultiDict object has a ``d.getone(key)`` method, that raises
143KeyError if there is not exactly one key.  There is a method
144``d.mixed()`` which returns a version where values are lists *if*
145there are multiple values for a list.  This is similar to how many
146cgi-based request forms are represented.
147
148Response
149--------
150
151Constructor:
152    Somewhat different.  WebOb takes any keyword arguments as
153    attribute assignments.  Django only takes a couple arguments.  The
154    ``mimetype`` argument is ``content_type``, and ``content_type`` is
155    the entire ``Content-Type`` header (including charset).
156
157dictionary-like:
158    The Django response object is somewhat dictionary-like, setting
159    headers.  The equivalent dictionary-like object is
160    ``res.headers``.  In WebOb this is a MultiDict.
161
162``has_header(header)``:
163    Use ``header in res.headers``
164
165``flush()``, ``tell()``:
166    Not available
167
168``content``:
169    Use ``res.body`` for the ``str`` value, ``res.text`` for
170    the ``unicode`` value
171
172Response Subclasses
173-------------------
174
175These are generally like ``webob.exc`` objects.
176``HttpResponseNotModified`` is ``HTTPNotModified``; this naming
177translation generally works.
178
179CherryPy/TurboGears
180===================
181
182The `CherryPy request object
183<http://www.cherrypy.org/wiki/RequestObject>`_ is also used by
184TurboGears 1.x.
185
186Request
187-------
188
189``app``:
190    No equivalent
191
192``base``:
193    ``req.application_url``
194
195``close()``:
196    No equivalent
197
198``closed``:
199    No equivalent
200
201``config``:
202    No equivalent
203
204``cookie``:
205    A ``SimpleCookie`` object in CherryPy; a dictionary in WebOb
206    (``SimpleCookie`` can represent cookie parameters, but cookie
207    parameters are only sent with responses not requests)
208
209``dispatch``:
210    No equivalent (this is the object dispatcher in CherryPy).
211
212``error_page``, ``error_response``, ``handle_error``:
213    No equivalent
214
215``get_resource()``:
216    Similar to ``req.get_response(app)``
217
218``handler``:
219    No equivalent
220
221``headers``, ``header_list``:
222    The WSGI environment represents headers as a dictionary, available
223    through ``req.headers`` (no list form is available in the request).
224
225``hooks``:
226    No equivalent
227
228``local``:
229    No equivalent
230
231``methods_with_bodies``:
232    This represents methods where CherryPy will automatically try to
233    read the request body.  WebOb lazily reads POST requests with the
234    correct content type, and no other bodies.
235
236``namespaces``:
237    No equivalent
238
239``protocol``:
240    As ``req.environ['SERVER_PROTOCOL']``
241
242``query_string``:
243    As ``req.query_string``
244
245``remote``:
246    ``remote.ip`` is like ``req.remote_addr``.  ``remote.port`` is not
247    available.  ``remote.name`` is in
248    ``req.environ.get('REMOTE_HOST')``
249
250``request_line``:
251    No equivalent
252
253``respond()``:
254    A method that is somewhat similar to ``req.get_response()``.
255
256``rfile``:
257    ``req.body_file``
258
259``run``:
260    No equivalent
261
262``server_protocol``:
263    As ``req.environ['SERVER_PROTOCOL']``
264
265``show_tracebacks``:
266    No equivalent
267
268``throw_errors``:
269    No equivalent
270
271``throws``:
272    No equivalent
273
274``toolmaps``:
275    No equivalent
276
277``wsgi_environ``:
278    As ``req.environ``
279
280Response
281--------
282
283From information `from the wiki
284<http://www.cherrypy.org/wiki/ResponseObject>`_.
285
286``body``:
287    This is an iterable in CherryPy, a string in WebOb;
288    ``res.app_iter`` gives an iterable in WebOb.
289
290``check_timeout``:
291    No equivalent
292
293``collapse_body()``:
294    This turns a stream/iterator body into a single string.  Accessing
295    ``res.body`` will do this automatically.
296
297``cookie``:
298    Accessible through ``res.set_cookie(...)``, ``res.delete_cookie``,
299    ``res.unset_cookie()``
300
301``finalize()``:
302    No equivalent
303
304``header_list``:
305    In ``res.headerlist``
306
307``stream``:
308    This can make CherryPy stream the response body out directory.
309    There is direct no equivalent; you can use a dynamically generated
310    iterator to do something similar.
311
312``time``:
313    No equivalent
314
315``timed_out``:
316    No equivalent
317
318Yaro
319====
320
321`Yaro <http://lukearno.com/projects/yaro/>`_ is a small wrapper around
322the WSGI environment, much like WebOb in scope.
323
324The WebOb objects have many more methods and attributes.  The Yaro
325Response object is a much smaller subset of WebOb's Response.
326
327Request
328-------
329
330``query``:
331    As ``req.GET``
332
333``form``:
334    As ``req.POST``
335
336``cookie``:
337    A ``SimpleCookie`` object in Yaro; a dictionary in WebOb
338    (``SimpleCookie`` can represent cookie parameters, but cookie
339    parameters are only sent with responses not requests)
340
341``uri``:
342    Returns a URI object, no equivalent (only string URIs available).
343
344``redirect``:
345    Not available (response-related).  ``webob.exc.HTTPFound()`` can
346    be useful here.
347
348``forward(yaroapp)``, ``wsgi_forward(wsgiapp)``:
349    Available with ``req.get_response(app)`` and
350    ``req.call_application(app)``.  In both cases it is a WSGI
351    application in WebOb, there is no special kind of communication;
352    ``req.call_application()`` just returns a ``webob.Response`` object.
353
354``res``:
355    The request object in WebOb *may* have a ``req.response``
356    attribute.
357
358Werkzeug
359========
360
361An offshoot of `Pocoo <http://pocoo.org>`_,
362this library is based around WSGI, similar to Paste and Yaro.
363
364This is taken from the `wrapper documentation
365<http://werkzeug.pocoo.org/documentation/wrappers>`_.
366
367Request
368-------
369
370path:
371    As ``req.path_info``
372args:
373    As ``req.GET``
374form:
375    As ``req.POST``
376values:
377    As ``req.params``
378files:
379    In ``req.POST`` (as FieldStorage objects)
380data:
381    In ``req.body_file``
382
383Response
384--------
385
386response:
387    In ``res.body`` (settable as ``res.body`` or ``res.app_iter``)
388status:
389    In ``res.status_code``
390mimetype:
391    In ``res.content_type``
392
393Zope 3
394======
395
396From the Zope 3 interfaces for the `Request
397<http://apidoc.zope.org/++apidoc++/Interface/zope.publisher.interfaces.browser.IBrowserRequest/index.html>`_
398and `Response
399<http://apidoc.zope.org/++apidoc++/Interface/zope.publisher.interfaces.http.IHTTPResponse/index.html>`_.
400
401Request
402-------
403
404``locale``, ``setupLocale()``:
405    This is not fully calculated, but information is available in
406    ``req.accept_languages``.
407
408``principal``, ``setPrincipal(principal)``:
409    ``req.remote_user`` gives the username, but there is no standard
410    place for a user *object*.
411
412``publication``, ``setPublication()``,
413    These are associated with the object publishing system in Zope.
414    This kind of publishing system is outside the scope of WebOb.
415
416``traverse(object)``, ``getTraversalStack()``, ``setTraversalStack()``:
417    These all relate to traversal, which is part of the publishing
418    system.
419
420``processInputs()``, ``setPathSuffix(steps)``:
421    Also associated with traversal and preparing the request.
422
423``environment``:
424    In ``req.environ``
425
426``bodyStream``:
427    In ``req.body_file``
428
429``interaction``:
430    This is the security context for the request; all the possible
431    participants or principals in the request.  There's no
432    equivalent.
433
434``annotations``:
435    Extra information associated with the request.  This would
436    generally go in custom keys of ``req.environ``, or if you set
437    attributes those attributes are stored in
438    ``req.environ['webob.adhoc_attrs']``.
439
440``debug``:
441    There is no standard debug flag for WebOb.
442
443``__getitem__(key)``, ``get(key)``, etc:
444    These treat the request like a dictionary, which WebOb does not
445    do.  They seem to take values from the environment, not
446    parameters.  Also on the Zope request object is ``items()``,
447    ``__contains__(key)``, ``__iter__()``, ``keys()``, ``__len__()``,
448    ``values()``.
449
450``getPositionalArguments()``:
451    I'm not sure what the equivalent would be, as there are no
452    positional arguments during instantiation (it doesn't fit into
453    WSGI).  Maybe ``wsgiorg.urlvars``?
454
455``retry()``, ``supportsRetry()``:
456    Creates a new request that can be used to retry a request.
457    Similar to ``req.copy()``.
458
459``close()``, ``hold(obj)``:
460    This closes resources associated with the request, including any
461    "held" objects.  There's nothing similar.
462
463Response
464--------
465
466``authUser``:
467    Not sure what this is or does.
468
469``reset()``:
470    No direct equivalent; you'd have to do ``res.headers = [];
471    res.body = ''; res.status = 200``
472
473``setCookie(name, value, **kw)``:
474    Is ``res.set_cookie(...)``.
475
476``getCookie(name)``:
477    No equivalent.  Hm.
478
479``expireCookie(name)``:
480    Is ``res.delete_cookie(name)``.
481
482``appendToCookie(name, value)``:
483    This appends the value to any existing cookie (separating values
484    with a colon).  WebOb does not do this.
485
486``setStatus(status)``:
487    Availble by setting ``res.status`` (can be set to an integer or a
488    string of "code reason").
489
490``getHeader(name, default=None)``:
491    Is ``res.headers.get(name)``.
492
493``getStatus()``:
494    Is ``res.status_code`` (or ``res.status`` to include reason)
495
496``addHeader(name, value)``:
497    Is ``res.headers.add(name, value)`` (in Zope and WebOb, this does
498    not clobber any previous value).
499
500``getHeaders()``:
501    Is ``res.headerlist``.
502
503``setHeader(name, value)``:
504    Is ``res.headers[name] = value``.
505
506``getStatusString()``:
507    Is ``res.status``.
508
509``consumeBody()``:
510    This consumes any non-string body to turn the body into a single
511    string.  Any access to ``res.body`` will do this (e.g., when you
512    have set the ``res.app_iter``).
513
514``internalError()``:
515    This is available with ``webob.exc.HTTP*()``.
516
517``handleException(exc_info)``:
518    This is provided with a tool like ``paste.exceptions``.
519
520``consumeBodyIter()``:
521    This returns the iterable for the body, even if the body was a
522    string.  Anytime you access ``res.app_iter`` you will get an
523    iterable.  ``res.body`` and ``res.app_iter`` can be interchanged
524    and accessed as many times as you want, unlike the Zope
525    equivalents.
526
527``setResult(result)``:
528    You can achieve the same thing through ``res.body = result``, or
529    ``res.app_iter = result``.  ``res.body`` accepts None, a unicode
530    string (*if* you have set a charset) or a normal string.
531    ``res.app_iter`` only accepts None and an interable.  You can't
532    update all of a response with one call.
533
534    Like in Zope, WebOb updates Content-Length.  Unlike Zope, it does
535    not automatically calculate a charset.
536
537
538mod_python
539==========
540
541Some key attributes from the `mod_python
542<http://modpython.org/live/current/doc-html/pyapi-mprequest-mem.html>`_
543request object.
544
545Request
546-------
547
548``req.uri``:
549    In ``req.path``.
550
551``req.user``:
552    In ``req.remote_user``.
553
554``req.get_remote_host()``:
555    In ``req.environ['REMOTE_ADDR']`` or ``req.remote_addr``.
556
557``req.headers_in.get('referer')``:
558    In ``req.headers.get('referer')`` or ``req.referer`` (same pattern
559    for other request headers, presumably).
560
561Response
562--------
563
564``util.redirect`` or ``req.status = apache.HTTP_MOVED_TEMPORARILY``:
565
566.. code-block:: python
567
568    from webob.exc import HTTPTemporaryRedirect
569    exc = HTTPTemporaryRedirect(location=url)
570    return exc(environ, start_response)
571
572``req.content_type = "application/x-csv"`` and
573``req.headers_out.add('Content-Disposition', 'attachment;filename=somefile.csv')``:
574
575.. code-block:: python
576
577    res = req.ResponseClass()
578    res.content_type = 'application/x-csv'
579    res.headers.add('Content-Disposition', 'attachment;filename=somefile.csv')
580    return res(environ, start_response)
581
582webapp Response
583===============
584
585The Google App Engine `webapp
586<http://code.google.com/appengine/docs/python/tools/webapp/>`_
587framework uses the WebOb Request object, but does not use its Response
588object.
589
590The constructor for ``webapp.Response`` does not take any arguments.
591The response is created by the framework, so you don't use it like
592``return Response(...)``, instead you use ``self.response``.  Also the
593response object automatically has ``Cache-Control: no-cache`` set,
594while the WebOb response does not set any cache headers.
595
596``resp.set_status(code, message=None)``:
597    This is handled by setting the ``resp.status`` attribute.
598
599``resp.clear()``:
600    You'd do ``resp.body = ""``
601
602``resp.wsgi_write(start_response)``:
603    This writes the response using the ``start_response`` callback,
604    and using the ``start_response`` writer.  The WebOb response
605    object is called as a WSGI app (``resp(environ, start_response)``)
606    to do the equivalent.
607
608``resp.out.write(text)``:
609    This writes to an internal ``StringIO`` instance of the response.
610    This uses the ability of the standard StringIO object to hold
611    either unicode or ``str`` text, and so long as you are always
612    consistent it will encode your content (but it does not respect
613    your preferred encoding, it always uses UTF-8).  The WebOb method
614    ``resp.write(text)`` is basically equivalent, and also accepts
615    unicode (using ``resp.charset`` for the encoding).  You can also
616    write to ``resp.body_file``, but it does not allow unicode.
617
618Besides exposing a ``.headers`` attribute (based on
619`wsgiref.headers.Headers
620<http://docs.python.org/library/wsgiref.html#wsgiref.headers.Headers>`_)
621there is no other API for the webapp response object.  This means the
622response lacks:
623
624* A usefully readable body or status.
625* A useful constructor that makes it easy to treat responses like
626  objects.
627* Providing a non-string ``app_iter`` for the body (like a generator).
628* Parsing of the Content-Type charset.
629* Getter/setters for parsed forms of headers, specifically
630  cache_control and last_modified.
631* The ``cache_expires`` method
632* ``set_cookie``, ``delete_cookie``, and ``unset_cookie``.  Instead
633  you have to simply manually set the Set-Cookie header.
634* ``encode_content`` and ``decode_content`` for handling gzip encoding.
635* ``md5_etag()`` for generating an etag from the body.
636* Conditional responses that will return 304 based on the response and
637  request headers.
638* The ability to serve Range request automatically.
639
640PHP
641===
642
643PHP does not have anything really resembling a request and response
644object.  Instead these are encoded in a set of global objects for the
645request and functions for the response.
646
647``$_POST``, ``$_GET``, ``$_FILES``
648----------------------------------
649
650These represent ``req.POST`` and ``req.GET``.
651
652PHP uses the variable names to tell whether a variable can hold
653multiple values.  For instance ``$_POST['name[]']``, which will be an
654array.  In WebOb any variable can have multiple values, and you can
655get these through ``req.POST.getall('name')``.
656
657The files in ``$_FILES`` are simply in ``req.POST`` in WebOb, as
658FieldStorage instances.
659
660``$_COOKIES``
661-------------
662
663This is in ``req.cookies``.
664
665``$_SERVER``, ``$_REQUEST``, ``$_ENV``
666--------------------------------------
667
668These are all in ``req.environ``.  These are not split up like they
669are in PHP, it's all just one dictionary.  Everything that would
670typically be in ``$_ENV`` is technically optional, and outside of a
671couple CGI-standard keys in ``$_SERVER`` most of those are also
672optional, but it is common for WSGI servers to populate the request
673with similar information as PHP.
674
675``$HTTP_RAW_POST_DATA``
676-----------------------
677
678This contains the unparsed data in the request body.  This is in
679``req.body``.
680
681The response
682------------
683
684Response headers in PHP are sent with ``header("Header-Name:
685value")``.  In WebOb there is a dictionary in ``resp.headers`` that
686can have values set; the headers aren't actually sent until you send
687the response.  You can add headers without overwriting (the equivalent
688of ``header("...", false)``) with ``resp.headers.add('Header-Name',
689'value')``.
690
691The status in PHP is sent with ``http_send_status(code)``.  In WebOb
692this is ``resp.status = code``.
693
694The body in PHP is sent implicitly through the rendering of the PHP
695body (or with ``echo`` or any other functions that send output).
696