Traversing the URI Path in QP Applications
In order to complete the Songs application we should understand how QP's
default traversal mechanism makes it easy to provide a navigable UI for our
objects we want to expose to the web.
In the last article in this series we saw through a hello, world example
that a QP application has two key classes, a SitePublisher and a
SiteDirectory, and that SiteDirectory exposes the root UI of a QP
application. Let's flesh out a HTML interface to go along with the REST API
offered by the Songs application, to include:
URI segment Name Returns to web browser
(export)
-------------------------------------------------------------
class SiteDirectory
/ index HTML listing of songs
/new new HTML form / creation of new song
/delete clear HTML form allowing deletion of all songs
The functionality at these URI segments will be provided by SiteDirectory:
class SiteDirectory(Directory):
def get_exports(self):
yield ('', 'index', 'Song List', 'This is the root / page of this site.')
yield ('api', 'api', 'API', 'API documentation and entry point')
yield ('new', 'new', 'New', 'Add a song to collection')
yield ('delete', 'clear', 'Delete', 'Delete all songs from collection')
def index:xml(self):
header('Song Database')
'<h1>Songs</h1>'
'<ul>'
for song in get_song_db().itervalues():
'<li>%s: <a href="%s/">%s</a></li>' % (
song.key, song.key, song.title)
'</ul>'
footer()
# ... for the forms examples provided by new/delete
# read the SongDirectory class at the end of this article
What of the API? Thanks to content-negotiation capability provided by the
Resource subclass of Directory, we could intermingle our API
functionality at the same URI as our HTML UI. Emphasis is on the word could
because from this point forward, purely for the sake of clarity in this and
subsequent articles on this theme, I'm going to risk angering the RESTafarian
gods and instead host the entire REST API under the /api/ url segment.
To the SiteDirectory class above we'll merely add an attribute and an entry in
get_exports():
class SiteDirectory(Directory):
def get_exports(self):
yield ('', 'index', 'Song List', 'This is the root / page of this site.')
yield ('new', 'new', 'New', 'Add a song to collection')
yield ('delete', 'clear', 'Delete', 'Delete all songs from collection')
yield ('api', 'api', 'API', 'API documentation and entry point')
api = SongDatabaseResource()
# ...
Therefore our API will be accessible via the SongDatabaseResource subclass
(first discussed here) of Resource and will expose the following:
URI method accept Return
-------------------------------------------------------------
class SongDatabaseResource
/api/ GET application/json json dict of all songs
/api/ DELETE application/json json {'result': 'true'} on success
/api/ POST application/json json dict of new song data
Architectural style purity aside, there are reasons in the practical world why
one might wish to bury an API, even a REST API, behind a mount point as we've
done. The busy developer with security (or marketing) concerns might want to
only expose the API to authenticated users. Let's do that:
class SongDatabaseResource(Resource):
def get_resources(self):
get_publisher().ensure_signed_in()
yield export('', '__api__') # docs for web browsers
yield export('', 'dump', method='GET', accept='application/json')
yield export('', 'new', method='POST', accept='application/json')
yield export('', 'clear', method='DELETE', accept='application/json')
# ...
OK, we sketched out the first / of the HTML UI. How do we traverse to
individual songs?
class SiteDirectory(Directory):
def get_exports(self):
yield ('', 'index', 'Song List', 'This is the root / page of this site.')
yield ('api', 'api', 'API', 'API documentation and entry point')
yield ('new', 'new', 'New', 'Add a song to collection')
yield ('delete', 'clear', 'Delete', 'Delete all songs from collection')
# ...
def _q_lookup(self, component):
try:
component = int(component)
song = get_song_db().get(component)
if song:
return SongDirectory(song)
except ValueError:
# None / an empty '' response being returned from _q_lookup
# will result in 404 not_found being delivered as the response.
return None
api = SongDatabaseResource()
The important mechanism to note here is _q_lookup. The default QP
traversal mechanism (which may easily be changed) traverses Directory
subclasses looking for callables or objects. One can create arbitrarily complex
URI schemes with levels nested many deep, if that turns your crank.
The object traversal approach is a flexible mechanism, quite different than
mapping callables or objects to URIs via regular expressions. But one could
do that in QP too, if you found a need to. If you need even more flexibility
you can override the Directory class _q_traverse method or dive into
the Publisher itself.
One benefit of this approach is others who may use these classes generally do
not need to concern themselves at all about where in their URI namespace they
deploy these UI components.
The attached file: song.qpy, is a QPY template file containing a quick draft of the HTML UI.
This RESTy Songs database example has morphed into something of a QP
howto. For those who just want to see the end result, fear not, I see that
coming around the bend soon. It is time to tie it all together... tomorrow.