pecan's routing mechanism

Keywords: JSON REST Python

Routing mechanism based on object distribution

pecan maps an HTTP request to a controller using object distribution.

The object distribution mechanism first divides the request path into a series of strings, which are used to find the next level controller from the root controller in turn.You can think of the set of controllers in an application as an object tree, where each branch corresponds to a URL path.The following code describes pecan's routing mechanism.

from pecan import expose

class BooksController(object):
    @expose()
    def index(self):
        return "Welcome to book section."

    @expose()
    def bestsellers(self):
        return "We have 5 books in the top 10."

class CatalogController(object):
    @expose()
    def index(self):
        return "Welcome to the catalog."

    books = BooksController()

class RootController(object):
    @expose()
    def index(self):
        return "Welcome to store.example.com!"

    @expose()
    def hours(self):
        return "Open 24/7 on the web."

    catalog = CatalogController()

For the above code, if there is a request at this time: /catalog/books/bestsellers, pecan first divides the request into catalog, books, bestsellers.Next, pecan will look for the catalog from the root controller, find the catalog object, then pecan will continue to look for books in the catalog controller, and so on, until it finds the bestsellers.If the URL ends with'/', pecan will look for the index method of the last controller.

Further, the following request paths:

└── /
    ├── /hours
    └── /catalog
         └── /catalog/books
            └── /catalog/books/bestsellers

These controller methods will be routed:

└── RootController.index
    ├── RootController.hours
    └── CatalogController.index
         └── BooksController.index
            └── BooksController.bestsellers

expose() method

You can tell pecan which methods in a controller class are publicly visible through the expose() method.If a method of a controller class is not decorated with expose (), pecan will not route the request to it.

expose() has many ways of using it.The simplest use is to pass no parameters to it.In this case, the controller returns a string representing the HTML response body.As shown in the code below.

from pecan import expose

class RootController(object):
    @expose()
    def hello(self):
        return 'Hello World'

However, a more common use is to specify a template and namespace, as shown below, html_template.mako is a template, and the hello() method returns a namespace {'msg':'hello!'}, which is used to render the html_template.mako template.

from pecan import expose

class RootController(object):
    @expose('html_template.mako')
    def hello(self):
        return {'msg': 'Hello!'}

html_template.mako template content:

<!-- html_template.mako -->
<html>
    <body>${msg}</body>
</html>

In addition to the HTML template, pecan also has a special json renderer built in, which renders the namespace into a json text as follows:

from pecan import expose

class RootController(object):
    @expose('json')
    def hello(self):
        return {'msg': 'Hello!'}

The expose() method can also be called cascading, which allows you to generate different response content depending on what is requested.

from pecan import expose

class RootController(object):
    @expose('json')
    @expose('text_template.mako', content_type='text/plain')
    @expose('html_template.mako')
    def hello(self):
        return {'msg': 'Hello!'}

As you can see here, we invoked the expose() method three times with different parameters.

@expose('json')

The first call tells pecan to render the namespace of the hello() method response into JSON text when the client requests / hello.json or http header contains "Accept: application/json".

@expose('text_template.mako', content_type='text/plain')

The second call tells pecan to use the text_template.mako template file when the client requests / hello.txt or http header contains "Accept: text/plain".

@expose('html_template.mako')

The third call tells pecan to use the html_template.mako template file when the client requests / hello.html.If the client requests/hello and does not explicitly specify the content format, pecan responds using the text/html content format by default, assuming the client wants HTML.

Explicitly specify path segments

Occasionally, you want to explicitly specify segments in the path, such as a request: /some-path, pecan cannot declare the processing of this request as some-path because of Python syntax restrictions, i.e. the following code is invalid in python:

class RootController(object):

    @pecan.expose()
    def some-path(self):
        return dict()

To circumvent this limitation, pecan allows you to specify a path segment within the expose() decorator, as shown in the following code snippet:

class RootController(object):

    @pecan.expose(route='some-path')
    def some_path(self):
        return dict()

In this example, the pecan application will return HTTP 200 to/some-path/request, but HTTP 404 to/some_path/request.

The route() method can also be explicitly used as a substitute for the route parameter in the expose() method, as shown in the following code snippet:

class RootController(object):

    @pecan.expose()
    def some_path(self):
        return dict()

pecan.route('some-path', RootController.some_path)

Further, in the same way, the route() method can be used to route requests to the next level controller.

class ChildController(object):

    @pecan.expose()
    def child(self):
        return dict()

class RootController(object):
    pass

pecan.route(RootController, 'child-path', ChildController())

In this example, the pecan application will respond to the request/child-path/child/return HTTP 200.

Request-based routing

The generic parameter in the expose() method can overload the URL based on the requested method.In the following example, the same URL can be handled in two different ways (one for HTTP GET requests and one for HTTP POST requests).When "generic=True" is specified in the parameters of the expose() method, GET requests to'/'are handled by the index() method, and POST requests to'/' are handled by the index_POST() method.

from pecan import expose


class RootController(object):

    # HTTP GET /
    @expose(generic=True, template='json')
    def index(self):
        return dict()

    # HTTP POST /
    @index.when(method='POST', template='json')
    def index_POST(self, **kw):
        uuid = create_something()
        return dict(uuid=uuid)

Pecan's routing algorithm

Sometimes the standard object distribution routing is not sufficient to route a URL to a controller.pecan provides several ways to short-circuit the route of object distribution to handle URLs with more control, and these special methods are used to achieve this goal: _lookup(), _default(), _route().Defining these methods on your controller allows you to be more flexible with all or part of a URL.

_lookup() method

_lookup() provides a way to process portions of a URL and return a new controller to process the rest of the URL.A _lookup() method can provide one or more parameters, as well as a slice of the URL.At the same time, the _lookup() method should represent the rest of the URL in a variable location and include the unhandled rest of the URL in its return value.In the following example, the object distribution routing algorithm passes the remainder list to the controller returned by the method.

The _lookup() method is called as the last method when no other controller method matches a URL and no _default() method is defined, except for the one used to dynamically create a controller.

from pecan import expose, abort
from somelib import get_student_by_name

class StudentController(object):
    def __init__(self, student):
        self.student = student

    @expose()
    def name(self):
        return self.student.name

class RootController(object):
    @expose()
    def _lookup(self, primary_key, *remainder):
        student = get_student_by_primary_key(primary_key)
        if student:
            return StudentController(student), remainder
        else:
            abort(404)

GET requests for'/8/name'will return the names of students whose primary_key equals 8.

_defulat() method

For a standard object distribution routing mechanism, the _default() method is called as the last method when no other controller method can match a URL.

from pecan import expose

class RootController(object):
    @expose()
    def english(self):
        return 'hello'

    @expose()
    def french(self):
        return 'bonjour'

    @expose()
    def _default(self):
        return 'I cannot say hello in that language'

In the example above, requests to/spanish are routed to the RootController._default() method.

_route() method

The _route() method allows a controller to completely override pecan's routing mechanism.Pecan itself also uses the _route() method to implement its RestController.If you want to define an alternative routing mechanism on top of a pecan, defining a base controller that includes the _route() method will give you full control of the requested route.

Posted by mcbeckel on Sat, 18 May 2019 11:45:44 -0700