Features
Caching
Caching the results of expensive computations between sessions
Overview
Framework's cache saves the result of an expensive computation so later sessions can reuse it. A cached value is stored as a file in the project's cache folder and recorded in the cache table of framework.db with its hash and an optional expiry time.
DataFrames are stored as Parquet. Other values are stored with pickle.
Warning
Loading a pickle can run arbitrary code. Load caches only from projects you trust.
The Cache Folder
The cache folder is chosen in this order.
- The
FW_CACHE_DIRenvironment variable, relative to the project root unless absolute. directories.cachein the settings.options.data.cache_dirin the settings.outputs/cache.
The project types set these defaults.
| Project type | Cache folder |
|---|---|
| Standard | outputs/cache/ |
| Privacy-sensitive | outputs/private/cache/ |
| Course | cache/ |
| Presentation | cache/ |
The folder is created the first time a value is cached. Every project type's .gitignore excludes cache/ folders.
Remembering a Result
cache_remember() returns the cached value when one exists. Otherwise it calls a function, caches the result, and returns it. The function takes no arguments.
import framework as fw
fw.scaffold()
def fit_model():
survey = fw.data_read("inputs.final.analysis")
return expensive_fit(survey)
model = fw.cache_remember("survey_model", fit_model)
A lambda works for short expressions.
counts = fw.cache_remember("site_counts", lambda: survey.groupby("site").size().reset_index())
cache_remember() also works as a decorator. The decorated function takes no arguments, and calling it returns the cached value when one exists.
@fw.cache_remember("survey_clean", expire_after="1 week")
def survey_clean():
return fw.data_read("inputs.raw.survey").dropna()
clean = survey_clean()
Expiration
expire_after sets how long a value stays valid. It takes a number of hours or a string with a unit.
fw.cache_remember("exchange_rates", fetch_rates, expire_after=1)
fw.cache_remember("daily_extract", run_extract, expire_after="1 day")
fw.cache_remember("weekly_model", fit_model, expire_after="2 weeks")
The units are hour, hours, hr, hrs, and h; day, days, and d; and week, weeks, and w. A value without an expiry stays valid until it is removed. options.data.cache_default_expire in the settings sets an expiry for values that do not give one.
An expired value is removed when it is next looked up, and cache_remember() computes it again.
Refreshing
refresh=True removes the cached value and computes it again.
model = fw.cache_remember("survey_model", fit_model, refresh=True)
refresh can also be a function that returns True or False, which decides at call time.
import os
model = fw.cache_remember(
"survey_model",
fit_model,
refresh=lambda: os.environ.get("REFIT") == "1",
)
Storing and Retrieving Values
cache() stores a value and returns it. cache_get() returns a stored value, or default when the value is missing, expired, or unreadable.
fw.cache("site_lookup", lookup, expire_after=24)
lookup = fw.cache_get("site_lookup")
lookup = fw.cache_get("site_lookup", default={})
The file argument of these functions stores the value in a specific file instead of the cache folder.
Removing Values
cache_forget() removes one value and its record. cache_flush() removes every cached file in the cache folder and every record.
fw.cache_forget("site_lookup")
fw.cache_flush()
Listing the Cache
cache_list() returns every cache record with its expiry, creation, update, and last-read times, and a status of active or expired.
fw.cache_list()
Integrity
Framework records each cached file's SHA-256 hash. When a file no longer matches its hash, the lookup issues a CacheWarning and treats the value as missing, so cache_remember() computes it again.
Caches Shared with R
R and Python share the cache table. R stores values as .rds files, which Python cannot read. A name that R cached is a miss in Python, with a CacheWarning, and R's file is left in place. cache_forget() and cache_flush() in Python remove entries from either language.
When to Cache
Cache computations that take long enough to slow down iteration, such as model fits, large joins, and database or API queries. Use descriptive names, such as sales_2024_by_region, so that cache_list() shows what each entry holds. Give values that come from outside sources an expiry, so they are fetched again.