Integration of REST API into site urls, and using piston resources to serve standard views

James Fisher avatarJames Fisher created an issue

The way I would like to use piston differs from the recommendations in one big way: I don't place my API under /api/, and instead, I scatter the API across my whole site to correspond with the standard HTML views that my site produces. In such a way:

/people # Standard HTML list of people, with fancy CSS, JS, etc. /people.xml # Structured XML list of people /people.json # Structured JSON list of people

My logic behind this is that a standard views, templates and everything, is not really anything more than an Emitter that is specialised to be lovely and human-readable.

My ideal way of accomplishing this would be to format my urls.py like this:

url(u'^people(.(?P<emitter_format>.+))?$', PeopleResource),

Thus if we visit /people.xml, we get an XML list; if we visit /people.json we get a JSON list, etc.

We can also chop off the extension and just visit /people. Unfortunately, this again produces a JSON list. This is because in resource.py, in determine_emitter, we have:

        if not em:
            em = request.GET.get('format', 'json')

Let's comment that out, so the function signals that no known emitter was requested by returning None.

Unfortunately, Resource.call expects to be given a valid emitter, and if it isn't, it attempts to call None(). Instead, what I would like it to do if it finds no emitter is to use a function defined by me, and what this function would essentially be is a 'view': a standard python function that returns an HttpResponse. So let's let the Resource know what view we would like to use:

class Resource(object):
    ...
    def __init__(self, handler, authentication=None, view=None):
        ...
        self.view = view

And finally teach it to use the view as an alternative to the emitter, so in call:

        try:
            emitter, ct = Emitter.get(em_format)
        except ValueError: # Either the format is unrecognised, or not given
            if self.view:
                return self.view(request, result, typemapper, handler, fields, anonymous)
            else:
                raise NotImplementedError("No explicit emitter format was given, and no default view exists.")
        else: # Use the requested emitter
            srl = emitter(result, typemapper, handler, fields, anonymous)
            try:
                """
                Decide whether or not we want a generator here,
                or we just want to buffer up the entire result
                before sending it to the client. Won't matter for
                smaller datasets, but larger will have an impact.
                """
                if self.stream: stream = srl.stream_render(request)
                else: stream = srl.render(request)

                if not isinstance(stream, HttpResponse):
                    resp = HttpResponse(stream, mimetype=ct)
                else:
                    resp = stream

                resp.streaming = self.stream

                return resp
            except HttpStatusCode, e:
                return e.response

So now, to implement my desired way of using Piston, I would first define a view (the only difference from normal views is that it takes the result (queryset) directly):

#views.py
def people_view(request, result, *args, **kwargs):
    return render_to_response(
		'people.html',
		{
			'people': result
			}
		)

And let the Resource know about my view:

PeopleResource = Resource(PeopleHandler, view=people_view)

Now, personally I'm pretty happy with the url schema I can quickly define with that. What I'm wondering is - does this contravene any established principles? Is it unusual or confusing to provide my JSON list of people just by appending ".json" to the standard url?

Comments (5)

  1. Jesper Nøhr
    • changed status to open

    It's an unusual way to use piston, but I see where you're going. There's certainly a use for it, for people like you.

    You know the drill; fork, test, pull request. Also, if I can get you to write a test for this use case, I'll be very happy.

    Using .json for the emitter is not unusual; emitter_format is there for a reason, and you can also override it like so:

    url(r'regex', resource, { 'emitter_format': 'json' })
    

    Perhaps a custom emitter that calls the view would be possible? I dunno. Your way is cleaner. Just a thought that wouldn't involve any code change.

  2. James Fisher

    I'll fork in a bit. But first I'm writing up another issue that kinda relates to this and I think comes before it logically: handlers for collections and for members. I think this is my final biggish quibble :).

  3. Log in to comment
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.