Response cache¶
Much of the API's data changes rarely — franchises, genres, the catalog's reference lists, a finished release — but an application may ask for it again and again: a website showing a franchise block on every page view, a matcher trying several spellings of one title. The SDK can keep such responses on disk and answer repeated calls from there.
examples/24_response_cache.py
is a runnable version of this page.
Turning it on¶
The cache is off by default. Give the client a directory and, usually, a lifetime:
async with AniLiberty(cache_dir=".aniliberty_cache", cache_ttl=6 * 3600) as client:
franchises = await client.franchises.for_release(9839) # cached if fresh
franchises = await client.franchises.for_release(9839, refresh=True) # straight to the API
| Parameter | Default | Meaning |
|---|---|---|
cache_dir |
None |
Directory for cache files, created on the first write. None disables the cache. |
cache_ttl |
None |
Seconds a cached response stays fresh. None — forever, until refresh=True. |
A stale entry isn't an error: the call simply goes to the API and the new response replaces
it. refresh=True does the same before the TTL runs out.
The cache lives only in files — nothing is kept in memory — so it survives restarts and is shared by every client and every process pointed at the same directory. Writes are atomic, so concurrent processes never read half an entry.
What is cached¶
A method uses the cache exactly when it has a refresh parameter:
| Namespace | Cached methods |
|---|---|
client.app |
search_releases() |
client.releases |
latest(), get(), members(), list(), iter_list() |
client.catalog |
releases(), iter_releases(), every references.*() |
client.schedule |
now(), week() |
client.genres |
list(), get(), releases(), iter_releases() |
client.franchises |
list(), get(), for_release() |
client.teams |
list(), roles(), users() |
client.media |
videos() |
Everything else always goes to the API:
- random picks —
releases.random(),releases.recommended()(random without arelease),genres.random(),franchises.random(),media.promotions()(shuffled on every call): caching them would freeze the "random" result; app.status()— it reports whether the API is alive right now;episodes.get()— HLS links are best fetched fresh. Note thatreleases.get()includes the release's episodes, links and all, so with a long TTL those may be old too;- torrents — their magnet links carry the account's passkey when logged in, and the
.torrentfiles and RSS feeds aren't JSON; - everything account-related —
client.me.*,releases.timecodes(),releases.ratings.*,episodes.timecode(),client.auth.*.
And while a session token is set (client.token, after client.auth.login() or
AniLiberty(token=...)), no request uses the cache at all — not even the cached methods
above. With a token, the API puts user-specific details into public responses, and those
must never end up in a file another user's client could read. A client used both logged-in
and anonymously reads and writes the cache only while anonymous.
client.http.request("GET", path, cache=True) opts a raw request into the same cache, under
the same rules.
How entries are stored¶
- One file per request, named by the SHA-256 of its key: the method, the full URL
(including
base_url, so a mirror has entries of its own) and the query parameters in sorted order. Different arguments — another page, another search text — are different entries; the same arguments in another order are the same entry. - The raw JSON is stored, not the model, together with the time it was written. Every cache hit is validated into models anew, so you get equal, not identical, objects.
- Only successful responses are cached. Errors (
NotFoundError, ...) are raised as usual and the next call asks the API again. - A broken cache never breaks a call: an unreadable or corrupted file is a miss, and a
failed write (a full disk, a read-only directory) is logged as a warning on the
anilibertylogger while the call returns the data it got.
There is no size limit and no automatic cleanup: stale entries stay on disk until they are fetched again. To clear the cache, delete the directory (or the files in it) — it is recreated on the next write.
Seeing it work¶
Hits are logged at DEBUG level on the aniliberty logger, next to the network requests:
import logging
logging.basicConfig()
logging.getLogger("aniliberty").setLevel(logging.DEBUG)
# DEBUG:aniliberty:GET https://aniliberty.top/api/v1/anime/genres -> 200
# DEBUG:aniliberty:GET https://aniliberty.top/api/v1/anime/genres: from cache