selector

WSGI delegation based on URL path and method.

About

This distribution provides WSGI middleware for "RESTful" mapping of URL paths to WSGI applications. Selector now also comes with components for environ based dispatch and on-the-fly middleware composition. There is a very simple optional mini-language for path expressions. Alternately we can easily use regular expressions directly or even create our own mini-language. There is a simple "mapping file" format that can be used. There are no architecture specific features (to MVC or whatever). Neither are there any framework specific features. Best of all, selector is the simplest thing that will work well (IMHO).

Download

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

Browse selector's public subversion repository.

Read and subscribe to selector updates and info.

Updates

Path Consumption (0.8.11)

Selector now supports path consumption and it is now the default behavior. The __init__ method of the Selector class now has an additional optional keyword parameter, consume_path, which defaults to True. This sets the attribute Selector.consume_path. To support this new feature, Selector.select now returns a four tuple instead of a three tuple. The additional item returned is the matched portion of the path. If Selector.consume_path evaluates to true, SCRIPT_NAME and PATH_INFO are handled appropriately. The matched portion of the path is appended to a list found or created in environ['selector.matches']. To support the case of matching only some beginning portion of a path, that will be more common with this feature, the default path expression parser now supports using a pipe character at the end of a path expression, indicating an open-ended match. In other words, if you are going to match and consume a part of the path and leave the rest for some further dispatch (or whatever), you use an expression like this: /foo|. This will match anything that starts with /foo and only consume that part of the path.


Routing Args (0.8.10)

The previous experimental ['wsgi.url_vars'] environ key is now ['wsgiorg.routing_args'] and the spec is marked as accepted. This is no longer experimental and is here to stay. The pliant and opliant decorators are still considered experiments.


Changes One More Time (0.8.9)

['wsgi.url_vars'] is now used and is likely to be more portable so will be preferable to ['selector.vars']. Keep in mind that this is experimental, for now. ['selector.vars'] will stay around for a good while but may be dropped eventually. Positional args are now supported in path expressions and will show up in both:

Decorators to apply ['wsgi.url_vars'] as extra args to wsgi-like callables are included. These are quickly implemented and very experimental:

Selector now provides classes for naked object and HTTP method to object method based dispatch, for completeness. I am not advocating this style of dispatch, in general, but there is a time and a place for (most) everything. This should be pretty self explanatory, I hope. Email me if you have questions. I don't mind.


Yup, More Changes (0.8.7)

...and I still haven't folded everything into the rest of the tutorial. Sorry, everybody.

You can now specify a wrapper for the callables that you are mapping to. You could use YARO for instance:

You can also pass wrap into slurp or slurp_file and you can use it as a directive in your mapping files (@wrap yaro:Yaro) or even just set it directly.


Even More Little Changes (0.8.5)

You can now specify '_ANY_' as an HTTP method to catch any method of request. This is not recommended unless you have a good reason, like supporting a DAV component. (Thanks to Damjan Georgievski for pointing out good reasons to finally add this).


Little Changes (0.8.1)


Newer Features Still (0.8)

If you are new to selector, skip this section and come back to it at the end.

Support for a text file format for expressing mappings has been added. Lets call it a "mapping file".

Example

Explanation

Path expressions are each on a line with no leading whitespace. HTTP Method to WSGI app mappings for a given path expression are on lines with leading whitespace which immediately follow. The @parser and @prefix directives can be used to change the selector settings of the same names while the file is being parsed.

Resolver Statements

The statements after the HTTP method names, that specify the WSGI app to be called, are in the following format. I am calling them "resolver statements" after the function which interprets them for lack of a better name.

Feeding Mapping Files to Selector

There are two ways to feed a mapping file to a Selector instance. Either one may be given a file name or a file like object.


New New Features (0.7)

If you are new to selector, skip this section and come back to it at the end.

New classes EnvironDispatcher and MiddlewareComposer have been added.

The Selector class itself is only intended for HTTP method and path based delegation (based on the first line of an HTTP request, in other words). This means that you can look at your mappings and easily see what is going to respond to what. This traceability is very important. Other delegation decision may be desirable, however.

After your selector instance has decided what to call, that "handler" may conduct further delegation. I believe that Ward Cunningham has called this "Compound Delegation". Usually this would mean examining the environ to decide where to go next. (In theory the "handler" could even be another Selector instance.)

To implement this environ based dispatch (probably a secondary dispatch), selector now includes specialized WSGI middleware. EnvironDispatcher is instantiated with a list of (predicate, wsgi_app) pairs. Each predicate is a callable that takes one argument (environ) and return True or False. When called, the instance iterates through the pairs until it finds a predicate that returns True and runs the app paired with it.

Another new WSGI middleware included in selector allows us compose middleware on the fly (compose as in function composition) in a similar way. MiddlewareComposer also is instantiated with a list of rules, only instead of WSGI apps you have WSGI middleware. When called, the instance applies all the middlewares whose predicates are true for environ in reverse order, and calls the resulting app.

Give the code above a GET on /endpoint would be sent to an app equivalent to a(c(e(app))). a, b, c, d and e are, of course, WSGI middleware and app is a WSGI app.

Note: These changes to selector have not yet been folded into the rest of this tutorial.


New Features (0.6)

If you are new to selector, skip this section and come back to it at the end.

The default parser now supports two new features: optional portions and pattern types.

Optional portions of path expressions are indicated with square brackets. If you wanted to map to a path with or without a trailing slash, you would do something like this:

Nesting is supported, so the following will match /foo, /foo/, /foo/2 or /foo/2/:

Pattern types are used to control the matching behavior of path variables. Pattern types are specified in path variables after a colon. To capture a number called bar you might do something like this:

Pattern types are defined in a dict patterns on the parser object. There are a few types built in:

The default type (when none is specified) is chunk. Simply modifying this dict will extend the types supported.

Note: These changes to selector have not yet been folded into the rest of this tutorial.

Tutorial

Selector works like this:

Simple, no?

If you have ever designed a REST protocol you have probably made a table that looks something like this:

/foos/{id}
POST Create a new foo with id == {id}.
GET Retrieve the foo with id == {id}.
PUT Update the foo with id == {id}.
DELETE Delete the foo with id == {id}.

Selector was designed to fit mappings of this kind.

Basic Example

Lets suppose that we are creating some very simple app. The only requirement is that http://example.com/myapp/hello/Jim responds with some simple page that says hello to Jim (where Jim can actually be any name at all). The "URL space" looks like this:

/myapp/hello/{name}
GET Say hello to {name}.

This is all the code we need:

The above probably makes sense to you if you are familiar with WSGI (PEP 333).

To run this you need will need flup and any HTTP server that supports SCGI. Flup also comes with FCGI and AJP servers; just change the import statement. It is easy to use flup with Apache or lighttpd.

Of course, you can use selector under any server that will run a WSGI app. A web server that supports WSGI (called wsgiref) is slated for inclusion in a future version of the Python standard library.

Mappings and Matches

Mappings consist of a path expression and a dictionary of WSGI apps keyed by HTTP methods.

When a mapping is added, the path expression is converted into a regular expression by a syntax parser. This parser can be turned off so that you can use regular expression syntax directly.

Upon invocation (usually by a server who has received a request) selector will look for a match in the mappings it has been given, in the order it has been given them. If no match is found, it will send a 404 Not Found.

When a URL path is matched, the named groups in the regex are used to populate a dict in environ['selector.vars']. The HTTP method is then located in the corresponding dictionary. If it is found the appropriate WSGI app is run. Otherwise a 405 Method Not Supported is returned.

This example explains the basic concept most simply:

... and you receive a request to:

... you can do this in load_archive without raising an exception:

Path Expression Parsers

Selector objects have a parser member. This is a callable that takes one argument and returns a regex string. By default this member is an instance of SimpleParser. A Selector object passes each path expression through its parser and compiles the resulting regex.

SimpleParser makes a named group for each {name} that appears in the path expression, escapes the rest, and adds '^' and '$' to the ends, respectively.

So this:

... is translated into this:

To use regular expressions directly, set parser to a do-nothing. Our example would look like this:

This will now only say hello to names that begin with a capitol 'A' followed by one or more lowercase letters. You probably wont need the full power of regular expressions most of the time but when you do, it is that easy.

Using Prefix

Selector objects have a prefix member that can be used to prepend some common string to the beginning of each path expression as mappings are added. The prefix is attached to the path expression before it is passed through the parser. The prefix can be changed between adding mappings. Usually the prefix is set to some common base path used throughout your application.

Using Selector.add()

The add() method can be called in a number of ways.

You can specify supported HTTP methods for a mapping and how to delegate them with keyword arguments:

Alternately we can pass in a dictionary:

If we do both, the keyword arguments will override the dictionary. In this case new_get wins:

You could easily pass around a common dictionary and override methods for mappings that have special cases.

The add() method's third argument is prefix which can be passed positionally or by keyword. The given prefix will be used instead of the current prefix only for that mapping.

... is the same as:

Initializing a Selector

It is possible to populate a Selector object with a whole list of mappings at once:

The Selector class's __init__() can be used to set the prefix and parser as well:

Using Selector.slurp()

The slurp() method can also be used to add mappings by bulk.

slurp() can also be passed prefix and parser as keyword arguments. In this case the prefix and parser supplied will be used only for the batch of mappings being "slurped".

Custom HTTP 404 and 405

The Selector class has WSGI app members to send simple HTTP status messages for 404s and 405s. You can subclass Selector to customize their behavior:

Questions, comments, suggestions, bugs... :