I’ve have it in my head to extract/rewrite parts of Paste lately. Tempita was one example.
The request and response functions in Paste grew very organically. I wasn’t trying to create a framework, so I studiously avoided anything that might look like a request or response object. I felt that would be stepping on toes or something. Eventually, though, Ben Bangert really wanted a request object for Pylons, and it went in paste.wsgiwrappers. And at a certain point I decided that the class-based access was really just fine, and doing lots of function(environ, ...) was no better than Request(environ).function(...).
So I started WebOb. WebOb has Request, Response, and some exceptions, incorporating the functionality of Paste’s paste.request, paste.response, paste.wsgilib, paste.httpexceptions, and paste.httpheaders. And some extra stuff.
I’ve included a comparison with a few other framework request/response objects. What this doesn’t note, though, is that WebOb has a much larger Request and Response objects. I’ve taken almost all the HTTP headers and mapped them to parsed attributes. So req.if_modified_since returns a datetime object, and req.if_none_match returns a somewhat set-like object, as a few examples. I created a lot of view-like objects for this, representing the canonical form of the information in several other forms (the WSGI request environment, and the status/headers/body of the response).
It’s fairly well tested and includes almost everything I think it should include, but I reserve the right to change the API any way I want until 1.0; this means if you have any opinion on the API I have nothing to stop me from taking your opinions into account.
Oh, and it has docs, really. They may not be the best docs, but they mention most everything and are automatically tested for accuracy. If you just want a sense of the feel, maybe the file-serving example would be a good place to start (though really you’ll only read about the Response object there).
Automatically generated list of related posts:
- WebOb decorator Lately I’ve been writing a few applications (e.g., PickyWiki and...
- What Does A WebOb App Look Like? Lately I’ve been writing code using WebOb and just a...
- JSON-RPC WebOb Example I just saw this json-rpc recipe go by as a...
- WebOb Do-It-Yourself Framework My old do-it-yourself framework tutorial was getting a bit long...
- WebTest HTTP testing I’ve yet to see another testing system for local web...
Hm. I’ve always resisted making a separate attribute for each header, for two reasons: 1) it makes dir(req) or help(req) insanely long, and 2) it hides one API vocabulary behind another, in the simple fact that
if_none_match
(the Request attribute name) is now not the same set of characters as"If-None-Match"
(the request header key). That sort of API-to-API translation step has always seemed to me to be something worth minimizing. Of course, if you could writeRequest.If-None-Match
there’d be no issue, but that’s not a legal Python identifier. So I’ve preferredRequest.headers['If-None-Match']
instead, even though it’s more keystrokes.Comments on either of those design pressures?
It does make
dir()
very long, as well as the extracted docs. I can imagine perhapsreq.headers.if_none_match
might be a way of partitioning out those headers. I don’t know if the value of that outweighs the extra typing.req.headers['if-none-match']
(case-insensitive) gives you the actual header value.req.if_none_match
gives you a parsed form. So there is a distinct difference between the two. There’s no genericreq.x_y_z
map toreq.headers['X-Y-Z']
; only explicit attributes I’ve setup with explicit parsing.I think the name mismatch isn’t much of an issue. It’s pretty obvious how the names map; everything in the API is the PEP 8 preferred lower-case underscore form, and headers are an obvious translation to that.
It would be great if you’ve provided an example of how to use these objects CherryPy / RhubabTart style. This is done via
paste.registry
middleware IIRC, so I suppose I can implement that myself, but an example from the author would be much appreciated. Thanks.Ian:
Sure, gotta have both. CherryPy solves the huge-namespace problem via a
headers.elements(key)
method, soreq.headers['if-none-match']
gives you the actual header value, andreq.headers.elements('if-none-match')
gets you the parsed value. There are plenty of variations on that theme–evenreq.headers.if_none_match
might be an improvement.(btw, your Markdown parser doesn’t seem to do blockquotes?)
I really don’t like the idea of parsed values coming out of a dictionary-like object. The parsed headers supported are limited and constrained and have different types, which is exactly the place where attributes fit and a dictionary interface doesn’t. Dictionaries are right when there might be arbitrary keys, and the values are generally of the same type or meaning.
(Markdown uses leading
>
for blockquotes; seems to work for me, but sometimes WordPress has pre- or post-filters that can mess up markup)I’ve been talking to both Ben Bangert and James Gardner about the possibility of making the Django and Pylons/Paste request/response objects compatible with each other in some way – either through changes to the API or just by ensuring they are similar enough that a very simple bi-directional conversion could be possible. This would make code written against Django and code written against Paste interchangeable, which would be a massive boost for Web programming on Python in general.
Would you be interested in helping out with this? I should probably start a mailing list dedicated to the effort.
I’ve got something very similar to this, so thank you, you’ve enabled me to delete code :).
One thing I would like to see though, is proper decoding of incoming cookies. They aren’t necessarily plain strings, they can have various properties attached to them, like expiry dates and comments. The Cookie module in the stdlib can handle this.
Do clients send structured cookie information? I haven’t seen this; outgoing cookies can have comments and whatnot, and I guess theoretically incoming cookies could have this, but they don’t.
If you are in a weird situation where you are getting more information in Cookie headers than what a normal browser sends, you can always parse the cookies yourself using
req.header['Cookie']
> Do clients send structured cookie information?
Yes. At the very least, multiple values are incredibly common (e.g. Cookie: foo=bar;baz=qux). Clients are also supposed to include the path and domain attributes when they are specified by the server (but they often don’t).
Multiple values are of course supported (it’s a dictionary). Because no clients I know of include the path and domain attributes, I’m not that interested; I find the Cookie objects really lousy, and I don’t want to expose them when they don’t carry any meaningful information for the majority of user agents. If you are in an environment where there are unusual user agents that send more information you can parse it yourself easily enough (
c = Cookie.SimpleCookie(); c.load(req.headers.get('Cookie', ''))
).Simon: “I’ve been talking to both Ben Bangert and James Gardner about the possibility of making the Django and Pylons/Paste request/response objects compatible with each other in some way – either through changes to the API or just by ensuring they are similar enough that a very simple bi-directional conversion could be possible.”
Of course, such things should be possible: WebStack shows that very few request/response object features are so exotic that such objects cannot be manipulated using a common abstraction.
Simon: “This would make code written against Django and code written against Paste interchangeable, which would be a massive boost for Web programming on Python in general.”
This is probably possible already with WebStack in that Django is supported (unless the API is changing a lot in recent snapshots) along with WSGI (but not Paste explicitly). And along with a bunch of other frameworks, of course.
Hey Paul – I hadn’t seen WebStack. Is there any chance you could put the readme.txt and docs/ files online so they can be viewed without downloading the archive? Makes it much easier to link to things.
The charset attribute is a bit of a misnomer. You aren’t looking at the character set, you are looking at the character encoding. For example, UTF-8 and UTF-16 are the same charset but a different encoding. Any chance of changing it to be more accurate?
Jim: charset is meant to match
text/html; charset=utf8
, where the name is “charset”. To me this felt like the most obvious name as a result. If it’s something of a misnomer then I’m only copying the misnomer (which I didn’t even realize — I suppose there’s really just one charset in Python, unicode, and it treats everything as an encoding, so I don’t really have a distinction in my head between the two concepts).I understand why you did it, but most other Python refers to character encodings properly. For example u”".encode(…), genshi.core.Stream.render(encoding=…), # -*- encoding: … -*-, etc. IMHO it’s better to have all the Python consistent and keep the charset an unfortunate artefact of the file format rather than have the Python disagree. Maybe just provide both?