Caching
Routing allows two different types of caching: Form caching, and data caching. Both types involve storing Python object instances in a dictionary contained within the router for later access.
Form caching
When a Route with Form caching enabled is navigated to for the first time, it instantiates its Form and caches that instance for later navigations to this Route. Were Form caching not enabled, a new instance of the Form would be re-created every time the Route is navigated to.
By default, Form caching is disabled. To enable it, you must set the cache_form
flag to True
in an individual Route’s definition.
class MyRouteWithFormCaching(Route):
path = '/with-form-caching'
form = 'FormToBeCached'
cache_form = True
Invalidation
The cache is automatically emptied after 30 minutes. This time can be changed by adjusting the gc_time
attribute when defining your Route.
class MyRouteWithFastGarbageCollection(Route):
path = '/with-fast-garbage-collection'
form = 'FormWithFastGarbageCollection'
gc_time = 1 * 60 # gc_time is measured in seconds
You can also flush the cache manually in code by calling either the clear_cache
function, which will flush the entire cache of your app, or the invalidate
function, if you only wish to clear the cache related to a specific path.
Data loading
Under the hood, the Form opening process starts on the server. If we need to make a server call to fetch data for our Form upon initialization, we are basically doing a round trip between server and client, which is slower than ideal.
Routes have a load_data
method, which is part of the navigation process. It is called before the Form is opened on the client browser.
By overriding a Route’s load_data
method, you can execute code (including server calls) before the Form even reaches the client’s browser, preventing the round trip and speeding up the process.
class MyRouteWithDataLoading(Route):
path = '/with-data-loading'
form = 'FormThatNeedsToLoadData'
def load_data(self, **loader_args):
# Perform all your data loading operations here
When opening a Form via router navigation, Forms are given a RoutingContext
object as a keyword argument to their __init__
function. You can retrieve the load_data
method’s result from the RoutingContext
object’s data
attribute.
class FormThatNeedsToLoadData(FormThatNeedsToLoadDataTemplate):
def __init__(self, routing_context=None, **properties):
self.routing_context = routing_context
properties["item"] = routing_context.data
self.init_components(**properties)
Unless Form caching is enabled and an instance of the Form is available in the cache, the entire data loading process happens every time the Route is navigated to. If we wish to cache the result of the data loading process but not the Form instance itself, we can enable data caching.
Data caching
Data caching functions just as Form caching does. It lets you keep the data loaded on first opening, that is to say the result of the load_data
method, in a cache for later use.
This can be done by setting a Route’s cache_data
attribute to True
.
class MyRouteWithDataCaching(Route):
path = '/with-data-caching'
form = 'FormWithDataToBeCached'
cache_data = True
Just like Form caches, data caches will be flushed once the duration set in the Route’s gc_time
has passed or when invalidated is called on this Route’s path.
Keep in mind that, if you have Form caching enabled, data caching is redundant, as the data loading process is skipped in case a Form instance is available in cache.
How it works
The Routing dependency’s cache is composed of two Python dictionaries, one for Forms and one for data.
When a Route is navigated to for the first time and caching is enabled for this Route, the router module stores the created object in the appropriate dictionary under a caching key.
This caching key is a string
object, built from two parts joined by a :
character:
- The path that is being navigated to
- The matched Route’s cache dependencies
For example, if we were to open the following path:
/my-page?show-side-panel=true
The caching key generated by the router module would be:
/my-page:{"show-side-panel": true}
It helps to think of caching keys as unique identifiers for your app’s pages.
Let’s look at a more extensive example.
Say we have the following route for a profile page that displays the profile of different users.
from routing.router import Route
import anvil.server
class Profile(Route):
cache_form = True
cache_data = True
path = "/my-app/profiles/:user-id
form = "Profile"
def load_data(self, **loader_args):
return anvil.server.call(
'get_user_data',
loader_args['routing_context'].params['user-id']
)
We add a user-id
parameter to our path, so that this Route can be matched for multiple user profiles. This parameter is used to populate the Profile
form with the data associated to the requested user ID.
If we have two users registered, User 1 and User 2, we basically have two pages associated with this route, each with their own URL:
/my-app/profiles/user-1
/my-app/profiles/user-2
When navigating to /my-app/profiles/user-1
, the Form instance and its loaded data will be cached under the following caching key:
/my-app/profiles/user-1:{}
These cached objects are only relevant for User 1, and we don’t want the router module to fetch these cached objects when requesting to view User 2’s profile page.
When navigating to /my-app/profiles/user-2
, the router module generates the following caching key:
/my-app/profiles/user-2:{}
Since this key is different from the one generated when navigating to /my-app/profiles/user-1
, opening User 2’s page will not retrieve User 1’s content. It will instead either fetch whatever is stored under this different key or create new instances and store them under it for later use.
Do you still have questions?
Our Community Forum is full of helpful information and Anvil experts.