Route Class

The Route class is used to define routes for your app. When a user navigates to a path, the router will look for a matching route. The router will call anvil.open_form on the matching route's form.

# routes.py
from routing.router import Route


class IndexRoute(Route):
    path = "/"
    form = "Pages.Index"


class AboutRoute(Route):
    path = "/about"
    form = "Pages.About"


class ContactRoute(Route):
    path = "/contact"
    form = "Pages.Contact"

The above code can use the Route.create() method for convenience:

# routes.py
from routing.router import Route

IndexRoute = Route.create(path="/", form="Pages.Index")
AboutRoute = Route.create(path="/about", form="Pages.About")
ContactRoute = Route.create(path="/contact", form="Pages.Contact")

Route Attributes

path
The path to navigate to. e.g. /, /articles or /articles/:id.
form
The form to open when the route is matched. e.g. Pages.Index.
error_form (optional)
The form to open when an error occurs. e.g. Pages.Error.
not_found_form (optional)
The form to open when the route is not found. e.g. Pages.NotFound.
pending_form (optional)
The form to open when the data is loading. e.g. Pages.Loading.
pending_delay=1
The delay before showing the pending form when the data is loading.
pending_min=0.5
The minimum time to show the pending form when the data is loading.
scroll_default="auto"
Scroll policy for the document and managed panels: "auto" scrolls to the fragment or top, "restore" uses saved positions when available, and "none" leaves scrolling alone. See Scrolling.
scroll_document="default"
Override the document's policy with "auto", "restore", or "none". "default" uses scroll_default.
scroll_manage_elements=False
Set to True to manage scrollable elements marked with data-routing-scroll-id. Their data-routing-scroll attribute can override the route policy. By default, the router leaves elements unmanaged.
cache_form=False
Whether to cache the route's form. By default this is False.
cache_data=False
Set to True to reuse cached data, or False to load without retaining it for later navigation. CACHE_FIRST and NO_CACHE are aliases for these values. See Caching.
stale_time=0
The time in seconds before data is considered stale for STALE_WHILE_REVALIDATE. This does not expire CACHE_FIRST data.
gc_time=30*60
The time in seconds that determines when data is released from the cache for garbage collection. By default this is 30 minutes. When data is released from the cache, any cached forms with the same path and cache_deps will also be released.
server_fn (optional str)
The server function to call when the route is matched. e.g. "get_article". This server function will be called with the same keyword arguments as the route's load_data method. Note this is optional and equivalent to defining a load_data method that calls the same server function.
server_silent=False
If True then the server function will be called using anvil.server.call_s. By default this is False.
sitemap=True
Whether to include this static route in the sitemap. By default this is True. Routes with path parameters are omitted.

Route Methods

before_load
Called after the route is matched, before loading its data or form. If it raises a Redirect, navigation is redirected. If it returns a dictionary, its contents are merged into the navigation context (nav_context).

Note - you may prefer not to override this method, and instead use the @hooks.before_load decorator.

See the navigation documentation for practical usage examples.

parse_query
Should return a dictionary of query parameters. By default this returns the original query parameters.
parse_params
Should return a dictionary of path parameters. By default this returns the original path parameters.
meta
Should return a dictionary with the title and description of the page. This will be used to update the meta tags and the title of the page. By default this returns an empty dictionary.
load_data
Called when the route is matched. The return value will be available in the data property of the RoutingContext instance. By default this returns None.
load_form
This method is called with two arguments. The first argument is a form name (e.g. "Pages.Index") or, if you are using cached forms, the cached form instance. The second argument is the RoutingContext instance. By default this calls anvil.open_form on the form and returns the instance. Custom implementations must also return the attached destination form for scroll handling.
scroll_restoration_key(location)
Return a string identifying the visit whose scroll positions should be saved. The default, location.key, keeps each browser history entry separate. Return location.path to remember the latest position across new link clicks. See Scrolling.
cache_deps
Returns an object, by default the query dictionary (more information in the query section and the RoutingContext section). This method is part of the process of creating caching keys.
When a route needs to cache a form or data (more information in the caching section), it does so by storing it in a global dictionary under a caching key. This key is composed of the route's path and the return of its cache_deps method at the moment of caching.
If, when accessing the same route, its cache_deps method returns something different than when caching first occurred, the caching key points to a different place within the cache, usually empty. The router thus understands this as a new route and navigates to it again.

Excluding Routes from the Sitemap

Static routes are included in the sitemap by default. Set sitemap = False to exclude a route:

from routing.router import Route


class PrivateRoute(Route):
    path = "/admin"
    form = "Pages.Admin"
    sitemap = False  # Exclude /admin from the sitemap

The dependency's sitemap configuration must also be enabled to serve /sitemap.txt.

Routes with path parameters, such as /articles/:id, are omitted because the router cannot enumerate their concrete URLs. The root URL is included only when a / route is defined with sitemap = True.

Setting Meta Tags Per Route

Override meta to return the tags for a route. See Route meta for examples, asset URLs and client/server behaviour.

Not Found Form

There are two ways a route can be not found. The first is when the user navigates to a path that does not match any routes. The second is when a user raises a NotFound exception in a route's before_load or load_data method.

Not Found Route

By definition, if there is no matching route, the router has no route to navigate to. If you want to handle this case, you can define a not found route.

from routing.router import Route


class NotFoundRoute(Route):
    form = "Pages.NotFound"
    default_not_found = True

The NotFoundRoute will be used when the user navigates to a path that does not match any routes. The path attribute should not be set, since this will be determined based on the path the user navigates to.

If no default_not_found attribute is set, then the router will raise a NotFound exception, which will be caught by Anvil's exception handler.

Raising a NotFound Exception

If you raise a NotFound exception in a route's before_load or load_data method, the router will call the route's load_form method with the route's not found form.

import anvil.server
from routing.router import Route, NotFound


class ArticleRoute(Route):
    path = "/articles/:id"
    form = "Pages.Article"
    not_found_form = "Pages.ArticleNotFound"

    def load_data(self, **loader_args):
        id = loader_args["params"]["id"]
        article = anvil.server.call("get_article", id)
        if article is None:
            raise NotFound(f"No article with id {id}")
        return article

If a route raises a NotFound exception and there is no not_found_form attribute, the router will raise the exception, which will be caught by Anvil's exception handler.

Error Form

When a route throws an exception, the router will call anvil.open_form on the matching route's error form.

If no error form is defined, the error will be caught by Anvil's exception handler.

from routing.router import Route

# Either define the error form globally
Route.error_form = "Pages.Error"


# or define the error form per route
class IndexRoute(Route):
    path = "/"
    form = "Pages.Index"
    error_form = "Pages.Error"
# Pages.Error
from ._anvil_designer import ErrorTemplate
from routing.router import RoutingContext
import anvil


class Error(ErrorTemplate):
    def __init__(self, routing_context: RoutingContext, **properties):
        super().__init__(**properties)
        self.routing_context = routing_context
        self.label.text = f"Error when navigating to {routing_context.path!r}, got {routing_context.error!r}"

    def form_show(self, **event_args):
        if anvil.app.environment.name.startswith("Debug"):
            raise self.routing_context.error

Ordering Routes

The router will try to match routes in the order they are defined.

from routing.router import Route


class AuthorsRoute(Route):
    path = "/authors"
    form = "Pages.Authors"


class NewAuthorRoute(Route):
    path = "/authors/new"
    form = "Pages.NewAuthor"


class AuthorRoute(Route):
    path = "/authors/:id"
    form = "Pages.Author"

In the above example, it's important that the NewAuthorRoute comes before the AuthorRoute in the list of routes. This is because /authors/new is a valid path for the AuthorRoute, so the router would successfully match the route and open the form.

Server Routes

When a user navigates to a URL directly, the router will match routes on the server.

The router automatically imports your routes module (defaulting to routes) and creates a server route for each route. You do not need to manually import routes in server code.

Under the hood this will look something like:

# ServerRoutes.py
from . import routes

# pseudo code - for illustration only
for route in Routes.__subclasses__():
    if route.path is None:
        continue

    @anvil.server.route(route.path)
    def server_route(**params):
        ...
        # return a response object that will open the form on the client