Storing complex data server-side between server function calls

Just want to share a little util, I’ve written to store complex data server-side between server function calls:

import time
from anvil.server import (
    background_task,
    list_background_tasks as get_background_tasks,
    launch_background_task,
    task_state,
)

class store:
    """Decorator for storing (caching) data server-side between server function/HTTP calls.
    Typically, used to serve static db data without requiring repeated db queries.
    Especially useful when the app is set up without the option "keep server running".

    Example:

    # In a server-side module
    @store()
    def get_media_list():
        value = []
        for row in app_tables.test.search():
            value.append(row["media"])
        return value

    # To access the stored data (e.g., inside a function made callable from client code):
    print("media_list: ", get_media_list())
    """

    # NOTE How it works: The decorated function is transformed into a background task.
    # This background task is only run once. The data to be stored is stored in the task's
    # state (rather than its return value) to also enable storing of media objects.

    def __init__(self, name=None):
        self.name = name

    def __call__(self, func: callable):
        if not callable(func):
            raise TypeError(f"Store functions should be callable. {func} is not.")

        # NOTE func should return the data object to be stored

        if not self.name:
            if not hasattr(func, "__name__"):
                raise TypeError(f"Cannot deduce name from {func}.")
            self.name = func.__name__

        self.task = self.search(self.name)
        if not self.task:

            def source():
                value = func()
                task_state["value"] = value

            # Rename 'source' to register background task with the 'self.name'
            source.__name__ = self.name
            # Register background task
            background_task()(source)
            self.launch()

        return self.get_value

    def get_value(self):
        """Awaits task completion and returns the value of the tast states's 'value' item."""
        while not self.task.is_completed():
            time.sleep(0.1)
        return self.task.get_state()["value"]

    def launch(self):
        """Launches task ans stores task in 'self.task'."""

        # XXX Consider implementing a try-except here.
        
        self.task = launch_background_task(self.name)

    @staticmethod
    def search(name):
        """Returns task by name; return None, if task not found."""
        for task in get_background_tasks():
            if task.get_task_name() == name:
                return task
3 Likes

Looks really cool! So do you use it to decorate an @anvil.server.callable function?

Thx. You simply decorate a regular function in a server module. When you need the data - typically inside a ‘@anvil.server.callable’-function - you just call the decorated function. I’ve updated the example in the docstring to make this a bit clearer.

1 Like