cache-results v0.2 Release: A Lazy Cache for Raw get() and set(), and Colored Logs

cache-results v0.1 put Smarter’s @cache_results() decorator on PyPI. v0.2 brings the rest of Smarter’s general-purpose caching code with it. That’s lazy_cache, for the raw get() and set() calls that a decorator doesn’t cover, and a log-prefix hook that colors cache hits, misses and invalidations in your development console.

pip install --upgrade cache-results

The last item on v0.1’s list of what’s next is done, too. Smarter no longer keeps a copy of the code. It installs cache-results from PyPI, like any other project, and keeps only the two hooks that need its own libraries.

What’s newIn one sentence
lazy_cacheDjango’s cache api, imported on first use instead of at import time, so it’s never the wrong backend.
Checks on first useThe first call writes and reads a test key, and logs an error if the cache loses it or can’t be reached.
readyCode that may run while Django is starting can ask whether the cache can be used yet.
Colored log prefixescache_results.colors.log_prefix colors hits green, misses red and invalidations gray, and LOG_COLORS changes them.
Smarter runs on the packageThe platform that cache-results came from now installs it from PyPI.

lazy_cache, Django’s cache imported when it’s safe

The decorator is the right tool for a function whose result depends only on its arguments. Plenty of caching isn’t shaped like that, such as a counter, a flag, or a value that one request writes and another reads. For those, use lazy_cache wherever you’d use django.core.cache.cache.

from cache_results import lazy_cache as cache

cache.set("my_key", "my_value", timeout=300)
cache.get("my_key")  # "my_value"

The difference is when Django’s cache is imported. Importing django.core.cache while Django is still starting, from a module that a settings file or an app’s models.py imports, can leave it on a backend you didn’t configure, such as a local-memory cache. Nothing fails. Values are stored, but only in that process, and lost on every restart, so sessions disappear, and every server recomputes what the others already cached. lazy_cache imports Django’s cache only the first time you use it.

  • The same api and the same timeouts. It has get, set, add, touch, delete, has_key, incr, decr, get_many, set_many, delete_many, incr_version, decr_version, clear and close, each with version. Without timeout, the cache’s own TIMEOUT applies, None never expires, and 0 stores nothing, exactly as in Django.
  • Safe during startup. Before Django’s apps are ready, lazy_cache.cache is None and a warning is logged. Code that might run during startup checks lazy_cache.ready first, as the decorator does.
  • Checked on first use. The first call writes and reads a test key, and logs an error if the value doesn’t come back or the cache raises, so a broken cache shows up in your logs instead of as a mystery.

📖 Lazy Cache guide


Colored log prefixes

Cache logging is off by default. When you turn it on in development, one setting makes it easy to read.

CACHE_RESULTS = {
    "LOG_PREFIX": "cache_results.colors.log_prefix",
}

Hits are green, misses red, invalidations gray, and anything else, such as an error, bold red. These are the colors Smarter has always used in its own console. To change one, give LOG_COLORS an ANSI code, or None to turn it off.

CACHE_RESULTS = {
    "LOG_PREFIX": "cache_results.colors.log_prefix",
    "LOG_COLORS": {"hit": "\033[94m", "": None},
}

LOG_PREFIX takes any callable (text, event), so a project with its own log format can plug in its own instead. A hook can also read its own entries from CACHE_RESULTS, through the new cache_results_settings.option(name, default), which is how log_prefix reads LOG_COLORS.

📖 Logging guide


What changed

  • New public api. lazy_cache and LazyCache are exported from the package root, from the module cache_results.lazy. cache_results.colors.log_prefix and the LOG_COLORS setting are new. Nothing that v0.1 exported has changed, so upgrading needs no changes to your code.
  • The package says whose it is. Its metadata now names The Smarter Project as the author, and Lawrence McDaniel as the maintainer, and the README and every page of the docs say that cache-results is part of The Smarter Project. PyPI never changes a published version’s metadata, so v0.1.0 keeps its old author.
  • Smarter installs it from PyPI. Smarter’s own cache code is gone, apart from the two hooks that need Pydantic and django-waffle, which it names in its CACHE_RESULTS setting, as any project would. Its test suite passes against the package.

Built to be depended on

What v0.1 said still holds. It’s tested on every supported pair of Python and Django versions, from Python 3.10 with Django 4.2 to Python 3.14 with Django 6.0, against both the local-memory cache and Redis, typed, and published to PyPI with trusted publishing. v0.2 adds the following.

  • A decision about what belongs in the package. ADR-020 records the rule that brought lazy_cache and the colors here, which is that anything that depends only on Django belongs in the package. A hook that needs another library, such as Pydantic, belongs to the project that uses it. The package still depends only on Django and asgiref, and has no optional extras.
  • A test that no exported name hides a module. The package exports the instance lazy_cache, and during development its module had the same name. Python 3.11 and later found the module, Python 3.10 found the instance, and only CI’s Python 3.10 jobs noticed. The module is now cache_results.lazy, and a test imports every module of the package and checks that the package attribute of the same name is that module, so it can’t happen again.
  • A release on every merge. Every push to main now runs the whole test matrix and lets semantic-release decide, from the commit messages, whether to publish. The alpha and beta branches publish prereleases to TestPyPI.
  • 70 tests, with 99% coverage, and 100% for lazy and colors. The docs have a new Lazy Cache guide, and reference pages for cache_results.lazy and cache_results.colors.

Try it, and tell people about it

  • Upgrade, and replace from django.core.cache import cache with from cache_results import lazy_cache as cache, with pip install --upgrade cache-results.
  • Star the repository at github.com/smarter-sh/cache-results, so that other Django developers can find it.
  • Open an issue for a bug, a cache backend that misbehaves, or an argument type you need. Pull requests are welcome. make init sets up everything, and the contributing guide explains the rules.
  • Look at Smarter, the open-source platform for building and running AI applications in Django that cache-results came from.

What’s next

  • More backends in CI. The tests run against Django’s local-memory cache and Redis. Memcached and the database cache are not tested yet.

The changelog is on GitHub, with the v0.1.1 and v0.2.0 releases. The package is on PyPI, the docs are at docs.cache-results.smarter.sh, and the source is at github.com/smarter-sh/cache-results.