John Belmonte wrote:
> Graham Fawcett wrote:
>
>> I've been writing a Quixote app in a somewhat RESTful style, that
>> needs to do content negotiation on certain requests. By 'content
>> negotation', I mean that I need to examine the Accept header in the
>> request, and return an appropriate response in one of the MIME types
>> declared there.
>
>
> I'm also designing an application in REST style using Quixote. I don't
> have any criticisms of your solution, but I'd like to make a few
> observations.
>
>> A recurrent pattern was cropping up in my code:
>>
>> def _q_index(req):
>> acceptlist = req.environ['HTTP_ACCEPT'].split(',')
>> if 'text/html' in acceptlist:
>> # return a text/html representation
>> elif 'text/xml' in acceptlist:
>> # return a text/xml representation
>> ...
>
>
> There is no need for this. See
> quixote.http_request.HTTPRequest.get_accepted_types.
Thanks, John -- I had missed this method!
>
>> def text_html [html] (cls, req):
>> some_header(req)
>> 'An HTML response!
'
>> some_footer(req)
>> def text_xml [html] (cls, req):
>> ''
>> def text_ [plain] (cls, req):
>> 'A response to text/* requests'
>
>
> I'd guess that in many designs, XML is the "source" format, with HTML
> and plain text being generated by transforming the XML. So to me, this
> can all be automated even more, and doesn't warrant splitting into
> separate handlers.
Yes, that came to mind for me too. My reasoning is similar, though a bit
vague. Here's what's in my head, feel free to comment.
What I like: I like the idea of separating out 'dispatch based on
Accept', apart from actual presentation code. (Dispatching based on HTTP
method or whatever criteria would make sense as well.) And classes make
sense for this kind of thing, of course, since they can mix in, inherit
or otherwise acquire the dispatching logic without too much clutter in
the presentation code.
Where I'm headed: generally, the output of my methods will be a
representation of some basic data structure (a dict, a list of tuples,
etc.) and there are no DTDs to which I must ahdere in this app. For
HTML, a 'pretty-print' of the structure will be sufficient in most
cases; I could XML and CSS or transforms, I suppose. But I also want to
be able to output the structure in one or more of XML, YAML, and
eval'uable Python text. 'Format-agnostic' might be an accurate
decription of my intent.
What I want, eventually: In response to a request, a 'Quixote method'
builds a message as a Python data structure. The method's dispatcher
takes the message and passes it to an appropriate handler for rendering
into the desired output format. Conversions to python-text, XML, YAML
can be generalized and handled at a superclass level. In the corner
cases where I need to customize the output of a given Quixote method in
a given Content-Type (e.g., a particular 'index_html' needs a special
'text/html' rendering), I can just provide a 'text_html()' method in the
class in question.
None of this refutes your 'XML as source', of course. ;-) I'm just
trying to use Python data as source instead.
>
>> Back to acceptfunction. A similar approach could be used to make
>> 'function classes' for functions that need to dispatch based on method
>> (GET, PUT, ...), user agent, locale, etc. (Though I dread to think
>> about having to write a _q_index.text_html.put.en_us() function and
>> its hundred-odd companions!)
>
>
> In my design, I decided that making handlers according to HTTP method
> was most important. Instead of using a meta class (which I know little
> of) to implement this, I simply made a base class that scanned its
> function attributes in __init__.
Metaclasses may have been overkill, I probably could do everything I
needed with inheritance. It seemed the easiest way at the time...
> A nice side effect was being able to
> automatically generate a Method Not Allowed response with appropriate
> Allowed header (assuming the list of accepted methods is static).
Yes, I wanted to do something similar with my 406 Not Acceptable
responses (they're supposed to return a list of supported
content-types). In its current form, the naming conventions in my code
prevent this: for example, my handler method
'application_octet_stream()' cannot be mapped onto the MIME name
'application/octet-stream' without additional information. I will
probably just change my naming convention (e.g.
'application_octet_dash_stream()' is ugly but it maps).
Thanks very much for the feedback!
-- Graham