yaro

A simple but non-restrictive abstraction of WSGI for end users.

About

This distribution provides Yet Another Request Object (for WSGI) in a way that is intended to be simple and useful for web developers who don't want to have to know a lot about WSGI to get the job done. It's also a handy convenience for those who do like to get under the hood but would be happy to eliminate some boilerplate without the encumbrance of some all-singing-all-dancing framework.

Download

Download the latest release from yaro's Cheese Shop Page. (Blessed are the cheesemakers!)

Browse yaro's public subversion repository.

Read and subscribe to yaro updates and info.

Hello World

Making a "hello world" WSGI app with yaro is easy.


from yaro import Yaro

@Yaro
def hello_world(req):
    return "Hello World!"

yaro.Yaro

The Yaro class's __init__() method takes a callable which will accept a request object. The instance that is created is a WSGI application that uses this callable to process requests.

Another way to write your hello world is:


class MyApp(object):
    def __call__(self, req):
        req.res.body = "Hello World!"

app = Yaro(MyApp())

When handling a request, the Yaro instance creates a yaro.Request and passes it to the callable you have given it. If it gets None back it looks in Request.res.body (the body of the response of the request object). What is returned or found in Request.res.body can be a string, an iterable (such as a list or generator) or a file-like object.

Yaro (since version 0.3) now supports "extra properties" that are added to the yaro.Request object when it is instantiated. Rules for adding these properties are passed to the yaro.Yaro object when it is instantiated. Rules take on the format of 2 or 3 length tuples: ('propname', environ_key_or_callable, optional_default).


rules = [('session', get_session), ('user', 'REMOTE_USER', "nobody")]
app = Yaro(myapp, extra_props=rules)

yaro.Request

The Request object has a number of useful members:

MemberDescription
methodThe method of the request ('GET', 'POST'...)
content_typeThe content type of the request
content_lengthThe content length of the request
queryA dictionary of query string parameters
formA dictionary of fields from a web form (lazy)
bodyA string of the request body (lazy)
cookieA cookie.SimpleCookie populated from the headers (lazy)
uriAn object representing the request URI
redirectA method to redirect the request
forwardHand off to a yaro compatible callable
wsgi_forwardHand off to a WSGI compatible callable
resAn object representing the response to this request
environThe WSGI provided environ

The content_type and content_length fields come strait from the HTTP request, if those headers were provided.

environ depends on the environment, as its name implies. This is a dictionary of variables like those that you would be provided by CGI. See PEP 333 for details.

Some further descriptions can be found below.

yaro.Request.query

The query dictionary will represent the parameters of the request query string. Its values will be strings or lists of strings. Values associated with parameter names ending in a pair of brackets will end up in lists even if that parameter occurs only once. This is better explained by example:


?foo=1&bar=2&bar=3   ->  {'foo':'1', 'bar':['2','3']}

?foo[]=1&bar=2&bar=3   ->  {'foo':['1'], 'bar':['2','3']}

The brackets are a way of indicating that you want a list so as to avoid boilerplate. This may seem a little odd but it saves you from having to write silly code to uncollapse single values.


# HAVING TO DO THIS IS LAME. DON'T.

if not isinstance(req.query['foo'], list): 
  req.query['foo'] = [req.query['foo']]

Please note that if 'foo[]' and 'foo' are both used as input names, they will step on each other. Use one or the other.

yaro.Request.form

The form dictionary will have strings, cgi.FieldStorage objects (used for file uploads) or lists of strings and/or cgi.FieldStorage objects as its values.

As with query parameters, field names ending in a pair of brackets indicate that you want a list even if there is only one. This likewise results in avoiding boilerplate whenever you are processing a form field where a user could have chosen or input one or many instances.

When a name does not end in brackets, you will end up with a list only if there is more than one field by that name. Again, if 'foo[]' and 'foo' are both used as input names, they will step on each other. Use one or the other.

This is loaded lazily to prevent reading in everything from the client when you don't want to.

yaro.Request.body

This is a string of the request body. Good for AJAX and web services.

This is loaded lazily to prevent reading in everything from the client when you don't want to.

yaro.URI

The URI class is itself a callable and has several useful members:

MemberDescription
schemeThe URI scheme
hostThe URI host name
portThe URI port number
scriptThe script name portion of the path
pathThe rest of the path
queryThe raw query string
host_uriA method to recreate the host's root URI
application_uriThe host's root URI plus the script name

Calling the URI instance with no arguments returns a reconstruction of the entire URI, sans query string. Calling it with a with_qs=True will get you the query string. Passing it with another path will construct a URI like the original uri, except for the path.


# processing a request: GET http://example.com:9999/foo/bar?baz=blarg
# where /foo is the script name

>>> req.uri.server_uri()
'http://example.com:9999'

>>> req.uri.application_uri()
'http://example.com:9999/foo'

>>> req.uri()
'http://example.com:9999/foo/bar'

>>> req.uri(with_qs=True)
'http://example.com:9999/foo/bar?baz=blarg'

>>> req.uri('eggs')
'http://example.com:9999/foo/eggs'

>>> req.uri('../beer')
'http://example.com:9999/beer'

yaro.Request.redirect()

The redirect() method is just what it sounds like. It takes a URI to redirect to and an optional permanent=False. The default behavior is to set a 301 status on the response and add an appropriate Location header. If permanent=False None is returned.

The following is a simple app that just redirects the request at random. If this were handling requests for http://example.com/cheeses/ users would land at the page of a randomly selected cheese.


from yaro import Yaro
from random import choice

cheeses = ['Camembert.html', 'Limberger.html', 'Ilchester.html']

@Yaro
def random_cheese(req):
    return req.redirect(req.uri(choice(cheeses)))

yaro.Request.forward()

Send handling on to another yaro compatible callable.


def app(req):
    return req.forward(otherapp)

yaro.Request.wsgi_forward()

Send handling on to another WSGI compatible callable.


def app(req):
    return req.wsgi_forward(wsgi_app)

yaro.Response

The Response class, found in Request.res, is very simple.

MemberDescription
statusHTTP status like '200 OK'
headersA dictionary-like object of response headers
bodyThe response body

status defaults to '200 OK'. For more information about headers see the docs of wsgiref.headers. The body is a string, iterable, or file-like object, as mentioned above.

Questions, comments, suggestions, bugs... :