pants.web.application

pants.web.application implements a minimalistic framework for building websites on top of Pants.

The Application class features a powerful, easy to use request routing system and an API similar to that of the popular Flask project.

Note

Application does not provide out of the box support for sessions or templates, and it is not compatible with WSGI middleware as it is not implemented via WSGI.

Applications

Instances of the Application class are callable and act as request handlers for the pants.http.server.HTTPServer class. As such, to implement a server you just have to create an HTTPServer instance using your application.

from pants.http import HTTPServer
from pants.web import Application

app = Application()

HTTPServer(app).listen(8080)

Alternatively, you may call the Application’s run() method, which creates an instance of HTTPServer for you and starts Pants’ global engine.

The main features of an Application are its powerful request routing table and its output handling.

Routing

When registering new request handlers with an Application instance, you are required to provide a specially formatted rule. These rules allow you to capture variables from URLs on top of merely routing requests, making it easy to create attractive URLs bereft of unfriendly query strings.

Rules in their simplest form will match a static string.

@app.route("/")
def index(request):
    return "Index Page"

@app.route("/welcome")
def welcome(request):
    return "Hello, Programmer!"

Such an Application would have two pages, and not be exceptionally useful by any definition. Adding a simple variable makes things much more interesting.

@app.route("/welcome/<name>")
def welcome(request, name):
    return "Hello, %s!" % name

Variables are created using inequality signs, as demonstrated above, and allow you to capture data directly from a URL. By default, a variable accepts any character except a slash (/) and returns the entire captured string as an argument to your request handler.

It is possible to change this behavior by naming a Converter within the variable definition using the format <converter:name> where converter is the name of the converter to use. It is not case-sensitive. For example, the int converter:

@app.route("/user/<int:id>")
def user(request, id):
    return session.query(User).filter_by(id=id).first().username

In the above example, the id is automatically converted to an integer by the framework. The converter also serves to limit the URLs that will match a rule. Variables using the int converter will only match numbers.

Finally, you may provide default values for variables:

@app.route("/page/<path:slug=welcome>")

Default values are used if there is no string to capture for the variable in question, and are processed via the converter’s decode() method each time the rule is matched.

When using default values, they allow you to omit the entirety of the URL following the point at which they are used. As such, if you have a rule such as /page/<int:id=2>/other, the URL /page/ will match it.

Domains

The route rule strings are very similar to those used by the popular Flask framework. However, in addition to that behavior, the Application allows you to match and extract variables from the domain the page was requested from.

@app.route("<username>.my-site.com/blog/<int:year>/<slug>")

To use domains, simply place the domain before the first slash in the route rule.

Rule Variable Converters

Converters are all subclasses of Converter that have been registered with Pants using the register_converter() decorator.

A Converter has three uses:

  1. Generating a regular expression snippet that will match only valid input for the variable in question.
  2. Processing the captured string into useful data for the Application.
  3. Encoding values into URL-friendly strings for inclusion into URLs generated via the url_for() method.

Converters can accept configuration information from rules using a basic format.

@app.route("/page/<regex('(\d{3}-\d{4})'):number>")

@app.route("/user/<id(digits=4 min=200):id>")

Configuration must be provided within parenthesis, with separate values separated by simple spaces. Strings may be enclosed within quotation marks if they need to contain spaces.

The values true, false, and none are converted to the appropriate Python values before being passed to the Converter’s configuration method and it also attempts to convert values into integers or floats if possible. Use quotation marks to avoid this behavior if required.

Arguments may be passed by order or by key, and are passed to the Converter’s configure() method from the constructor via: self.configure(*args, **kwargs)

Several basic converters have been included by default to make things easier.

Any

The any converter will allow you to match one string from a list of possible strings.

@app.route("/<any(call text im):action>/<int:id>")

Using the above rule, you can match URLs starting with /call/, /text/, or /im/ (and followed, of course, by an integer named id).

DomainPart

DomainPart is a special converter used when matching sections of a domain name that will not match a period (.) but that otherwise works identically to the default String converter.

You do not have to specify the DomainPart converter. It will be used automatically in place of String for any variable capture within the domain name portion of the rule.

Float

The float converter will match a negation, the digits 0 through 9, and a single period. It automatically converts the captured string into a floating point number.

Argument Default Description
min None The minimum value to allow.
max None The maximum value to allow.

Values outside of the range defined by min and max will result in an error and not merely the rule not matching the URL.

Integer

The int (or integer) converter will match a negation and the digits 0 through 9, automatically converting the captured string into an integer.

Argument Default Description
digits None The exact number of digits to match with this variable.
min None The minimum value to allow.
max None The maximum value to allow.

As with the Float converter, values outside of the range defined by min and max will result in an error and not merely the rule not matching the URL.

Path

The path converter will match any character at all and merely returns the captured string. This is useful as a catch all for placing on the end of URLs.

Regex

The regex converter allows you to specify an arbitrary regular expression snippet for inclusion into the rule’s final expression.

Argument Default Description
match   A regular expression snippet for inclusion into the rule’s final expression.
namegen None The string format to use when building a URL for this variable with url_for().
@app.route("/call/<regex('(\d{3}-\d{4})'):number>")

The above variable would match strings such as 555-1234.

String

The string converter is the default converter used when none is specified, and it matches any character except for a slash (/), allowing it to easily capture individual URL segments.

Argument Default Description
min None The minimum length of the string to capture.
max None The maximum length of the string to capture.
length None An easy way to set both min and max at once.

Note

Setting length overrides any value of min and max.

Writing a Variable Converter

To create your own variable converters, you must create subclasses of Converter and register it with Pants using the decorator register_converter().

The simplest way to use converters is as a way to store common regular expressions that you use to match segments of a URL. If, for example, you need to match basic phone numbers, you could use:

@app.route("/tel/<regex('(\d{3})-(\d{4})'):number>")

Placing the expression in the route isn’t clean, however, and it can be a pain to update–particularly if you use the same expression across many different routes.

A better alternative is to use a custom converter:

from pants.web import Converter, register_converter

@register_converter
class Telephone(Converter):
    regex = r"(\d{3})-(\d{4})"

After doing that, your rule becomes as easy as /tel/<telephone:number>. Of course, you could stop there, and deal with the resulting tuple of two strings within your request handler.

However, the main goal of converters is to convert your data. Let’s store our phone number in a collections.namedtuple. While we’re at it, we’ll switch to a slightly more complex regular expression that can capture area codes and extensions as well.

from collections import namedtuple
from pants.web import Converter, register_converter

PhoneNumber = namedtuple('PhoneNumber', ['npa','nxx','subscriber', 'ext'])

@register_converter
class Telephone(Converter):
    regex = r"(?:1[ -]*)?(?:\(? *([2-9][0-9]{2}) *\)?[ -]*)?([2-9](?:1[02-9]|[02-9][0-9]))[ -]*(\d{4})(?:[ -]*e?xt?[ -]*(\d+))?"

    def decode(self, request, *values):
        return PhoneNumber(*(int(x) if x else None for x in values))

Now we’re getting somewhere. Using our existing rule, now we can make a request for the URL /tel/555-234-5678x115 and our request handler will receive the variable PhoneNumber(npa=555, nxx=234, subscriber=5678, ext=115).

Lastly, we need a way to convert our nice PhoneNumber instances into something we can place in a URL, for use with the url_for() function:

@register_converter
class Telephone(Converter):

    ...

    def encode(self, request, value):
        out = '%03d-%03d-%04d' % (value.npa, value.nxx, value.subscriber)
        if value.ext:
            out += '-ext%d' % value.ext
        return out

Now, we can use url_for('route', PhoneNumber(npa=555, nxx=234, subscriber=5678, ext=115)) and get a nice and readable /tel/555-234-5678-ext115 back (assuming the rule for route is /tel/<telephone:number>).

Output Handling

Sending output from a request handler is as easy as returning a value from the function. Strings work well:

@app.route("/")
def index(request):
    return "Hello, World!"

The example above would result in a 200 OK response with the headers Content-Type: text/plain and Content-Length: 13.

Response Body

If the returned string begins with <!DOCTYPE or <html it will be assumed that the Content-Type should be text/html if a content type is not provided.

If a unicode string is returned, rather than a byte string, it will be encoded automatically using the encoding specified in the Content-Type header. If that header is missing, or does not contain an encoding, the document will be encoded in UTF-8 by default and the content type header will be updated.

Dictionaries, lists, and tuples will be automatically converted into JSON and the Content-Type header will be set to application/json, making it easy to send JSON to clients.

If any other object is returned, the Application will attempt to cast it into a byte string using str(object). To provide custom behavior, an object may be given a to_html method, which will be called rather than str(). If to_html is used, the Content-Type will be assumed to be text/html.

Status and Headers

Of course, in any web application it is useful to be able to return custom status codes and HTTP headers. To do so from an Application’s request handlers, simply return a tuple of (body, status) or (body, status, headers).

If provided, status must be an integer or a byte string. All valid HTTP response codes may be sent simply by using their numbers.

If provided, headers must be either a dictionary, or a list of tuples containing key/value pairs ([(heading, value), ...]).

You may also use an instance of pants.web.application.Response rather than a simple body or tuple.

The following example returns a page with the status code 404 Not Found:

@app.route("/nowhere/")
def nowhere(request):
    return "This does not exist.", 404

Helper Functions

pants.web.application.abort(status=404, message=None, headers=None)[source]

Raise a HTTPException to display an error page.

pants.web.application.all_or_404(*args)[source]

If any of the provided arguments aren’t truthy, raise a 404 Not Found exception. This is automatically called for you if you set auto404=True when using the route decorator.

pants.web.application.error(message=None, status=None, headers=None, request=None, debug=None)[source]

Return a very simple error page, defaulting to a 404 Not Found error if no status code is supplied. Usually, you’ll want to call abort() in your code, rather than error(). Usage:

return error(404)
return error("Some message.", 404)
return error("Blah blah blah.", 403, {'Some-Header': 'Fish'})
pants.web.application.redirect(url, status=302, request=None)[source]

Construct a 302 Found response to instruct the client’s browser to redirect its request to a different URL. Other codes may be returned by specifying a status.

Argument Default Description
url   The URL to redirect the client’s browser to.
status 302 Optional. The status code to send with the response.
pants.web.application.register_converter(name=None, klass=None)[source]

Register a converter with the given name. If a name is not provided, the class name will be converted to lowercase and used instead.

pants.web.application.url_for(name, *values, **kw_values)[source]

Generates a URL for the route with the given name. You may give either an absolute name for the route or use a period to match names relative to the current route. Multiple periods may be used to traverse up the name tree.

Passed arguments will be used to construct the URL. Any unknown keyword arguments will be appended to the URL as query arguments. Additionally, there are several special keyword arguments to customize url_for‘s behavior.

Argument Default Description
_anchor None Optional. An anchor string to be appended to the URL.
_doseq True Optional. The value to pass to urllib.urlencode()‘s doseq parameter for building the query string.
_external False Optional. Whether or not a URL is meant for external use. External URLs never have their host portion removed.
_scheme None Optional. The scheme of the link to generate. By default, this is set to the scheme of the current request.

Application

class pants.web.application.Application(name=None, debug=False, fix_end_slash=False)[source]

The Application class builds upon the Module class and acts as a request handler for the HTTPServer, with request routing, error handling, and a degree of convenience that makes sending output easier.

Instances of Application are callable, and should be used as a HTTPServer’s request handler.

Argument Description
debug Optional. If this is set to True, the automatically generated 500 Internal Server Error pages will display additional debugging information.
run(address=None, ssl_options=None, engine=None)[source]

This function exists for convenience, and when called creates a HTTPServer instance with its request handler set to this application instance, calls listen() on that HTTPServer, and finally, starts the Pants engine to process requests.

Argument Description
address Optional. The address to listen on. If this isn’t specified, it will default to ('', 80).
ssl_options Optional. A dictionary of SSL options for the server. See pants.server.Server.startSSL() for more information.
engine Optional. The pants.engine.Engine instance to use.

Module

class pants.web.application.Module(name=None)[source]

A Module is, essentially, a group of rules for an Application. Rules grouped into a Module can be created without any access to the final Application instance, making it simple to split a website into multiple Python modules to be imported in the module that creates and runs the application.

add(rule, module)[source]

Add a Module to this Module under the given rule. All rules within the sub-module will be accessible to this Module, with their rules prefixed by the rule provided here.

For example:

module_one = Module()

@module_one.route("/fish")
def fish(request):
    return "This is fish."

module_two = Module()

module_two.add("/pie", module_one)

Given that code, the request handler fish would be available from the Module module_two with the rules /pie/fish.

basic_route(rule, name=None, methods=('GET', 'HEAD'), headers=None, content_type=None, func=None)[source]

The basic_route decorator registers a route with the Module without holding your hand about it.

It functions similarly to the Module.route() decorator, but it doesn’t wrap the function with any argument processing code. Instead, the function is given only the request object, and through it access to the regular expression match.

Example Usage:

@app.basic_route("/char/<char>")
def my_route(request):
    char, = request.match.groups()
    return "The character is %s!" % char

That is essentially equivalent to:

@app.route("/char/<char>")
def my_route(request, char):
    return "The character is %s!" % char

Note

Output is still handled the way it is with a normal route, so you can return strings and dictionaries as usual.

Argument Description
rule The route rule to match for a request to go to the decorated function. See Module.route() for more information.
name Optional. The name of the decorated function, for use with the url_for() helper function.
methods Optional. A list of HTTP methods to allow for this request handler. By default, only GET and HEAD requests are allowed, and all others will result in a 405 Method Not Allowed error.
headers Optional. A dictionary of HTTP headers to always send with the response from this request handler. Any headers set within the request handler will override these headers.
content_type Optional. The HTTP Content-Type header to send with the response from this request handler. A Content-Type header set within the request handler will override this.
func Optional. The function for this view. Specifying the function bypasses the usual decorator-like behavior of this function.
request_finished(func)[source]

Register a method to be executed immediately after the request handler and before the output is processed and send to the client.

This can be used to transform the output of request handlers.

Note

These hooks are not run if there is no matching rule for a request, if there is an exception while running the request handler, or if the request is not set to have its output processed by the Application by setting request.auto_finish to False.

request_started(func)[source]

Register a method to be executed immediately after a request has been successfully routed and before the request handler is executed.

Note

Hooks, including request_started, are not executed if there is no matching rule to handle the request.

This can be used for the initialization of sessions, a database connection, or other details. However, it is not always the best choice. If you wish to modify all requests, or manipulate the URL before routing occurs, you should wrap the Application in another method, rather than using a request_started hook. As an example of the difference:

from pants.web import Application
from pants.http import HTTPServer
from pants import Engine

from my_site import sessions, module

app = Application()

# The Hook
@app.request_started
def handle(request):
    logging.info('Request matched route: %s' % request.route_name)

# The Wrapper
def wrapper(request):
    request.session = sessions.get(request.get_secure_cookie('session_id'))
    app(request)

# Add rules from another module.
app.add('/', module)

HTTPServer(wrapper).listen()
Engine.instance().start()
request_teardown(func)[source]

Register a method to be executed after the output of a request handler has been processed and has begun being transmitted to the client. At this point, the request is not going to be used again and can be cleaned up.

Note

These hooks will always run if there was a matching rule, even if the request handler or other hooks have exceptions, to prevent any potential memory leaks from requests that aren’t torn down properly.

route(rule, name=None, methods=('GET', 'HEAD'), auto404=False, headers=None, content_type=None, func=None)[source]

The route decorator is used to register a new route with the Module instance. Example:

@app.route("/")
def hello_world(request):
    return "Hiya, Everyone!"

See also

See Routing for more information on writing rules.

Argument Description
rule The route rule to be matched for the decorated function to be used for handling a request.
name Optional. The name of the decorated function, for use with the url_for() helper function.
methods Optional. A list of HTTP methods to allow for this request handler. By default, only GET and HEAD requests are allowed, and all others will result in a 405 Method Not Allowed error.
auto404 Optional. If this is set to True, all response handler arguments will be checked for truthiness (True, non-empty strings, etc.) and, if any fail, a 404 Not Found page will be rendered automatically.
headers Optional. A dictionary of HTTP headers to always send with the response from this request handler. Any headers set within the request handler will override these headers.
content_type Optional. The HTTP Content-Type header to send with the response from this request handler. A Content-Type header set within the request handler will override this.
func Optional. The function for this view. Specifying the function bypasses the usual decorator-like behavior of this function.

Converter

class pants.web.application.Converter(options, default)[source]

The Converter class is the base class for all the different value converters usable in routing rules.

default

A string provided with the variable declaration to be used as a default value if no value is provided by the client.

This value will also be placed in urls generated via the method url_for() if no other value is provided.

configure()[source]

The method receives configuration data parsed from the rule creating this Converter instance as positional and keyword arguments.

You must build a regular expression for matching acceptable input within this function, and save it as the instance’s regex attribute. You may use more than one capture group.

decode(request, *values)[source]

This method receives captured strings from URLs and must process the strings and return variables usable within request handlers.

If the converter’s regular expression has multiple capture groups, it will receive multiple arguments.

Note

Use abort() or raise an HTTPException from this method if you wish to display an error page. Any other uncaught exceptions will result in a 400 Bad Request page.

encode(request, value)[source]

This method encodes a value into a URL-friendly string for inclusion into URLs generated with url_for().

Exceptions

class pants.web.application.HTTPException(status=404, message=None, headers=None)[source]

Raising an instance of HTTPException will cause the Application to render an error page out to the client with the given HTTP status code, message, and any provided headers.

This is, generally, preferable to allowing an exception of a different type to bubble up to the Application, which would result in a 500 Internal Server Error page.

The abort() helper function makes it easy to raise instances of this exception.

Argument Description
status Optional. The HTTP status code to generate an error page for. If this isn’t specified, a 404 Not Found page will be generated.
message Optional. A text message to display on the error page.
headers Optional. A dict of extra HTTP headers to return with the rendered page.
class pants.web.application.HTTPTransparentRedirect(url)[source]

Raising an instance of HTTPTransparentRedirect will cause the Application to silently redirect a request to a new URL.