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