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.
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 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 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.
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.
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.