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:
- Generating a regular expression snippet that will match only valid input for the variable in question.
- Processing the captured string into useful data for the Application.
- 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 setauto404=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 callabort()
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()
‘sdoseq
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 theHTTPServer
, 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, callslisten()
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 Modulemodule_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
andHEAD
requests are allowed, and all others will result in a405 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
toFalse
.
-
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
andHEAD
requests are allowed, and all others will result in a405 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 anHTTPException
from this method if you wish to display an error page. Any other uncaught exceptions will result in a400 Bad Request
page.
-
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.