Resty applications in QP, Part II
In this installment we'll take a break from the implementation started in the post previous of a QP and Durus based RESTy song database application loosely following the meme which has arisen from Eric Florenzano's example bare-metal WSGI implementation of same. Today I want to point out some QP features which are useful and perhaps a little unusual.
Basics
While QP may not be a bare-metal framework, it also isn't an mega-framework encapsulating a ton of functionality. The designer's ethos might well be extracted from the last line in QP's README:
The abbreviation "qp" stands for "quantum placet", the Latin phrase meaning "as much as you please".
QP provides basic but solid building blocks for web applications out of the box, including via its partner package QPY high level protection against XSS (cross site scripting) attacks and a novel and very fast Python templating system. You'll also get a user authentication system including form, HTTP Basic and HTTP Digest authentication.
QP Hello World Prototype
QP application's typically minimally consist of a SitePublisher and SiteDirectory class. SitePublisher contains a configuration dictionary which can be used for both the framework and your applications. Since we are using the built-in Durus object database-backed User database for the Songs application, we'll employ a DurusPublisher and instruct the qp site management command line utility to start a web and Durus daemon:
class SitePublisher(DurusPublisher):
configuration = dict(
durus_address=('localhost', 7023),
http_address=('', 8023),
)
The "root directory" of a typical QP application is a class named SiteDirectory. A hello world application driven by the above publisher would look like this:
class SiteDirectory(Directory):
def get_exports(self):
# URI segment exported, method name, crumb, title
yield ('', 'index', 'Home', 'Home page of the site')
def index(self):
return 'hello, world.'
That's it.
Starting a QP application using the built-in site management tool is simple:
% qp -u songs
QP includes all the facilities for starting and stopping web and other processes. It uses a multi-process rather than multi-thread model and spawns new worker processes (configurable) as needed. Multi-CPU / multi-core machines can eek out significant performance with nothing but the base QP servers; for a large site typically I'll put the QP application behind an Apache or lighttpd web daemon (using either a proxy or SCGI configuration), and serve the static content with those servers. QP can also play in the WSGI space - look for a quick how-to in the next post.
QPY Templates
Let us now demonstrate one of the cooler or unusual features of QP / QPY - Python centric templating. Whether this model works for you or not depends a lot on how you look at code and (x)HTML / text templates. Programming teams who are also responsible for the site layout may find refreshing the QPY approach of putting content-in-Python, as opposed to most templating packages which focus on enabling Python-like languages within text / HTML / XMl templates.
An example should make things clear. We'll write a method to say hello back to us n-times based on url parameters age and name, to respond to a query like:
curl http://localhost:8023/?age=3&name=Mary
Instead of using return to send a string back to the Publisher, we use a special notation on our method - :xml:
from qp.pub.common import get_request
class SiteDirectory(Directory):
def index:xml(self):
context = get_request().fields
'''
<html>
<head>
<title>Hello</title>
</head>
<body>
<ol>
'''
for i in range(int(context.get('age', 1))):
'<li>%(name)s, %(age)s years old</li>' % context
'''
</ol>
</body>
</html>
'''
When index is called the string literal will be completed and passed back as a return value. Its a much cleaner looking approach than building up a string.
The method descriptor :xml is enabled by QPY. There are two such descriptors provided by QPY, :xml and :str. String literals within :xml templates are cast as a quote no more type, while variable data passed to the template (such as the context dictionary in the example) will be escaped or quoted for safety once and only once as it is included in the template. :str templates provide no automatic escaping and quoting.
The QPY templating system provides four major benefits:
- You already know the language because it is pure Python.
- Automatic protection from cross site scripting (XSS) attacks and other malicious value injection.
- Unicode sane and safe, like the rest of the QP stack.
- QPY is uncomplicated and very fast.
Speaking of Unicode, QP applications treat all text data internally as Unicode. The default character set is utf-8. Writing QP / QPY applications to run on either Python 2.x or 3.x requires no special Unicode gymnastics.
In addition, all parts of the QP stack - Durus, QPY and QP itself - are Python 3 compatible, today. QPY is packaged separately from QP to make it accessible for other templating package authors to employ features such as its quote no more class and Python extensions.
Authentication
QP's default authentication method is HTTP Digest; this can be changed by trivially overriding the ensure_signed_in() method of Publisher. For the SongDatabase API we won't alter the default since Form isn't workable for a RESTful API, and using Digest over Basic gives us the ability to offer at least authentication security without using HTTPS.
If we extended our hello, world prototype to provide some more functionality if you are logged on user, and even more if you are an administrator, it would look like this:
from qp.pub.common import get_publisher, get_user
class SiteDirectory(Directory):
def get_exports(self):
# URI segment exported, method name, crumb, title
yield ('', 'index', 'Home', 'Home page of the site')
get_publisher().ensure_signed_in()
yield ('users', 'users', 'Users', 'Pithy title here')
if get_user().is_admin():
yield ('secrets', 'top_secret', 'Home', 'Secrets', 'Something intriguing')
def index(self):
return 'hello, world.'
def users(self):
return 'hello, real user %s' % get_user().id
def top_secret(self):
return 'the password is "orange". Sssh!'
In the foregoing example an unauthenticated user will not even be able to navigate to /users or /secrets. An authenticated user who isn't also a site administrator will not be able to navigate to /secrets.
One could put the authentication checks within an exported method instead, but the nice thing about using the get_exports mechanism is menu and crumb generation routines also use the data yielded, so putting auth checks there is a benefit if you prefer not to show your site users menus they don't currently have authorization to access.
Coming up next, a quick note on running QP as a WSGI application, and we'll get back to the Song application.