Routing engine

../_images/routing_engine.png

Fantastico routing engine is design by having extensibility in mind. Below you can find the list of concerns for routing engine:

  1. Support multiple sources for routes.
  2. Load all available routes.
  3. Select the controller that can handle the request route (if any available).
class fantastico.routing_engine.router.Router(settings_facade=<class 'fantastico.settings.SettingsFacade'>)[source]

This class is used for registering all available routes by using all registered loaders.

get_loaders()[source]

Method used to retrieve all available loaders. If loaders are not currently instantiated they are by these method. This method also supports multi threaded workers mode of wsgi with really small memory footprint. It uses an internal lock so that it makes sure available loaders are instantiated only once per wsgi worker.

handle_route(url, environ)[source]

Method used to identify the given url method handler. It enrich the environ dictionary with a new entry that holds a controller instance and a function to be executed from that controller.

register_routes()[source]

Method used to register all routes from all loaders. If the loaders are not yet initialized this method will first load all available loaders and then it will register all available routes. Also, this method initialize available routes only once when it is first invoked.

Routes loaders

Fantastico routing engine is designed so that routes can be loaded from multiple sources (database, disk locations, and others). This give huge extensibility so that developers can use Fantastico in various scenarios:

  • Create a CMS that allows people to create new pages (mapping between page url / controller) is hold in database. Just by adding a simple loader in which the business logic is encapsulated allows routing engine extension.
  • Create a blog that loads articles from disk.

I am sure you can find other use cases in which you benefit from this extension point.

How to write a new route loader

Before digging in further details see the RouteLoader class documentation below:

class fantastico.routing_engine.routing_loaders.RouteLoader(settings_facade)[source]

This class provides the contract that must be provided by each concrete implementation. Each route loader is responsible for implementing its own business logic for loading routes.

class DummyRouteLoader(RouteLoader):
    def __init__(self, settings_facade):
        self_settings_facade = settings_facade

    def load_routes(self):
        return {"/index.html": {"http_verbs": {
                                            "GET": "fantastico.plugins.static_assets.StaticAssetsController.resolve_text"
                                            }
                                },
                "/images/image.png": {"http_verbs": {
                                            "GET": "fantastico.plugins.static_assets.StaticAssetsController.resolve_binary"
                                            }
                                     }
        }
load_routes()[source]

This method must be overriden by each concrete implementation so that all loaded routes can be handled by fantastico routing engine middleware.

As you can, each concrete route loader receives in the constructor settings facade that can be used to access fantastico settings. In the code example above, DummyRouteLoader maps a list of urls to a controller method that can be used to render it. Keep in mind that a route loader is a stateless component and it can’t in anyway determine the wsgi environment in which it is used. In addition this design decision also make sure clear separation of concerned is followed.

Once your RouteLoader implementation is ready you must register it into settings profile. The safest bet is to add it into BaseSettings provider. For more information read Fantastico settings.

Configuring available loaders

You can find all available loaders for the framework configured in your settings profile. You can find below a sample configuration of available loaders:

class CustomSettings(BasicSettings):
    @property
    def routes_loaders(self):
        return ["fantastico.routing_engine.custom_loader.CustomLoader"]

The above configuration tells Fantastico routing engine that only CustomLoader is a source of routes. If you want to learn more about multiple configurations please read Fantastico settings.

DummyRouteLoader

class fantastico.routing_engine.dummy_routeloader.DummyRouteLoader(settings_facade)[source]

This class represents an example of how to write a route loader. DummyRouteLoader is available in all configurations and it provides a single route to the routing engine: /dummy/route/loader/test. Integration tests rely on this loader to be configured in each available profile.

display_test(request)[source]

This method handles /dummy/route/loader/test route. It is expected to receive a response with status code 400. We do this for being able to test rendering and also avoid false positive security scans messages.

Routing middleware

Fantastico routing engine is designed as a standalone component. In order to be able to integrate it into Fantastico request lifecycle (:doc:/features/request_response.) we need an adapter component.

class fantastico.middleware.routing_middleware.RoutingMiddleware(app, router_cls=<class 'fantastico.routing_engine.router.Router'>)[source]

Class used to integrate routing engine fantastico component into request / response lifecycle. This middleware is responsible for:

  1. instantiating the router component and make it available to other components / middlewares through WSGI environment.
  2. register all configured fantastico loaders (fantastico.routing_engine.router.Router.get_loaders()).
  3. register all available routes (fantastico.routing_engine.router.Router.register_routes()).
  4. handle route requests (fantastico.routing_engine.router.Router.handle_route()).

It is important to understand that routing middleware assume a WebOb request available into WSGI environ. Otherwise, fantastico.exceptions.FantasticoNoRequestError will be thrown. You can read more about request middleware at Request lifecycle.