Unforgettable is a simple cache you can used for tiny repetitive executions.
Project description
Unforgettable - v0.3.0
Unforgettable is a tiny file-backed cache for Python code that repeats small pieces of work. It is useful for local scripts, notebooks, tests, prototypes, and automation where a value is expensive or annoying to compute more than once.
The library has no runtime dependencies and exposes one main class:
unforgettable.
Installation
Install with uv:
uv add unforgettable
Or with pip:
pip install unforgettable
Unforgettable requires Python 3.11 or newer.
Quick Start
from unforgettable import unforgettable
cache = unforgettable()
cached_value = cache.get(cache_id="expensive-operation")
if cached_value is None:
cached_value = "computed result"
cache.set(content=cached_value, cache_id="expensive-operation")
print(cached_value)
get() returns the cached value when it exists. If there is no value for that
cache_id, it returns None.
Use list() to inspect the cache IDs currently available in a cache folder.
from unforgettable import unforgettable
cache = unforgettable()
cache.set(content="computed result", cache_id="expensive-operation")
assert cache.list() == ["expensive-operation"]
Cache Text
from unforgettable import unforgettable
cache = unforgettable()
cache.set(content="hello", cache_id="greeting")
assert cache.get(cache_id="greeting") == "hello"
Cache Bytes
from unforgettable import unforgettable
cache = unforgettable()
content = b"\x80\x81cached bytes"
cache.set(content=content, cache_id="binary-value")
assert cache.get(cache_id="binary-value") == content
Persistent Cache Folder
By default, each cache instance creates its own temporary directory with
tempfile. Use cache_folder when you want cached values to persist across
Python process runs.
import os
from unforgettable import unforgettable
cache_dir = os.environ.get("UNFORGETTABLE_CACHE_DIR", ".unforgettable-cache")
cache = unforgettable(cache_folder=cache_dir)
cache.set(content="saved between runs", cache_id="stable-key")
Creating a new cache instance with the same folder can read values written by the previous instance.
from unforgettable import unforgettable
first_cache = unforgettable(cache_folder=".unforgettable-cache")
first_cache.set(content="persisted", cache_id="example")
second_cache = unforgettable(cache_folder=".unforgettable-cache")
assert second_cache.get(cache_id="example") == "persisted"
Command Line
Installing the package exposes an unforgettable command.
CLI commands use .unforgettable-memory in the current working directory by
default, so you can run commands without passing --cache-folder:
unforgettable set greeting "hello"
unforgettable get greeting
Pass --cache-folder to use a different persistent cache folder:
unforgettable --cache-folder .unforgettable-cache list
When an explicitly selected cache folder does not exist, the CLI asks before
creating it. Answer y or yes to create the folder and continue; any other
answer exits without creating the folder or changing cache contents.
For unattended automation, choose the missing-folder policy explicitly:
unforgettable --cache-folder .unforgettable-cache --create-cache-folder set greeting "hello"
unforgettable --cache-folder .unforgettable-cache --no-create-cache-folder list
--create-cache-folder creates a missing explicitly selected folder without
reading stdin. --no-create-cache-folder exits with status code 1 without
creating the folder or changing cache contents. Prompts and errors are written
to stderr; command values are written to stdout.
The list command prints one cache ID per line and prints nothing when the
selected cache folder has no user-created entries.
Use --output json when an agent or script needs structured output:
unforgettable --cache-folder .unforgettable-cache --output json list
JSON list output has the stable shape {"cache_ids": ["example"]} and uses
{"cache_ids": []} for an empty cache folder. Cache IDs with spaces and
punctuation are preserved as JSON strings. Plain text remains the default.
Store text under a cache ID:
unforgettable --cache-folder .unforgettable-cache set greeting "hello"
Store text from stdin, including multiline or piped content:
printf 'first line\nsecond line\n' | unforgettable --cache-folder .unforgettable-cache set notes --stdin
Retrieve cached text:
unforgettable --cache-folder .unforgettable-cache get greeting
Retrieved text is written directly to stdout without labels or added trailing newlines, so cached values can be piped into other tools.
Clean the selected cache folder:
unforgettable --cache-folder .unforgettable-cache clean
get exits with status code 1 when the requested cache ID is missing.
Local AI-Agent Tool Contract
Agents can discover and run Unforgettable without adding it to the current
project. Use uvx where that alias is available:
uvx unforgettable --help
uvx unforgettable --version
Use uv tool run as the portable equivalent:
uv tool run unforgettable --help
uv tool run unforgettable --version
Installed environments can use the console script or module execution:
unforgettable --help
python -m unforgettable --help
For compatibility-sensitive workflows, check unforgettable --version before
depending on a specific command contract.
The CLI uses stable process behavior suitable for unattended tools:
- Successful commands exit with status code
0. - Missing cache IDs for
getexit with status code1. - Rejected cache-folder creation exits with status code
1. - Invalid usage, such as missing required arguments, exits with status code
2. - Command values are written to stdout.
- Diagnostics, errors, and prompts are written to stderr.
By default, CLI commands use .unforgettable-memory in the current working
directory and create that default folder automatically. When --cache-folder
selects a missing explicit folder, the CLI prompts on stderr before creating
it. Agents should avoid prompts by choosing a policy:
unforgettable --cache-folder .agent-cache --create-cache-folder set run-id "started"
unforgettable --cache-folder .agent-cache --no-create-cache-folder list
Use --create-cache-folder to create the explicit folder without reading from
stdin. Use --no-create-cache-folder to fail with status code 1 if the
folder is missing.
Store multiline or piped content with set --stdin:
printf 'first line\nsecond line\n' \
| unforgettable --cache-folder .agent-cache --create-cache-folder set notes --stdin
Retrieve content with get; the cached value is written directly to stdout
without labels or added trailing newlines:
unforgettable --cache-folder .agent-cache get notes
Plain text output is the default. Use --output json when a command supports
structured output:
unforgettable --cache-folder .agent-cache --output json list
The JSON list shape is stable:
{"cache_ids": ["notes"]}
Custom Cache File Extension
Cache content files use the cache extension by default. You can choose a
different extension when creating the cache instance.
from unforgettable import unforgettable
cache = unforgettable(
cache_folder=".unforgettable-cache",
cache_files_extension="txt",
)
cache.set(content="inspectable text", cache_id="example")
Cleaning A Cache
Call clean() on a cache instance to remove the files in that instance's cache
folder.
from unforgettable import unforgettable
cache = unforgettable(cache_folder=".unforgettable-cache")
cache.set(content="temporary", cache_id="example")
cache.clean()
HTTP Request Example
This example caches successful HTTP responses by URL. Failed requests are not cached.
import requests
from unforgettable import unforgettable
cache = unforgettable(cache_folder=".request-cache")
def requests_get(url: str) -> bytes | None:
cached_response = cache.get(cache_id=url)
if cached_response is not None:
return cached_response
response = requests.get(url=url, timeout=5)
if response.status_code != 200:
return None
cache.set(content=response.content, cache_id=url)
return response.content
url = "https://github.com/bouli/unforgettable"
first_response = requests_get(url)
second_response = requests_get(url)
API Reference
unforgettable(cache_folder=None, cache_files_extension=None)
Creates a cache instance.
cache_folder: optional folder for cache files. When omitted, the instance uses a temporary directory.cache_files_extension: optional extension for stored cache content files. Defaults tocache.
cache.set(content, cache_id)
Stores content under cache_id.
contentcan bestrorbytes.- Calling
set()again with the samecache_idoverwrites the existing cached content.
cache.get(cache_id)
Returns the cached value for cache_id.
- Returns
strfor text files. - Returns
bytesfor binary files. - Returns
Nonewhen the cache entry does not exist.
cache.list()
Returns a list[str] containing user-created cache IDs available in the cache
folder.
- Returns an empty list when no user cache entries exist.
- Omits internal cache index bookkeeping.
- Preserves cache IDs with spaces and punctuation.
cache.clean()
Removes files from the cache instance's folder.
Development
Install dependencies:
uv sync --dev
Run tests:
make tests
Run the coverage report:
make report
Build the package:
make build
Release Verification
Before publishing a release, run the local tests, build the wheel, and verify the agent-facing execution paths:
uv run --dev pytest -quv run --dev pytest -q tests/test_packaging.pyuv build --wheeluv tool run unforgettable --helpuv tool run unforgettable --versionuvx unforgettable --helpwhere theuvxalias is installeduvx unforgettable --versionwhere theuvxalias is installedunforgettable --helpafter installing the built wheelpython -m unforgettable --helpafter installing the built wheelunforgettable --versionafter installing the built wheel
If uvx is not installed, record that it was unavailable and use
uv tool run unforgettable --help and uv tool run unforgettable --version as
the equivalent ephemeral execution checks. The packaging test builds the wheel
and verifies installed console script, module execution, and version output
contracts; keep it passing before release.
See Also
License
This package is distributed under the MIT license.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file unforgettable-0.3.0.tar.gz.
File metadata
- Download URL: unforgettable-0.3.0.tar.gz
- Upload date:
- Size: 14.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
910e82499c5ecdb0dc392702329161e0614429e78d389bad879204358120511b
|
|
| MD5 |
5271c6a1e859416b0d81807b4aece457
|
|
| BLAKE2b-256 |
5a7fbf049df44764fbc8cbf4d1a2801196df32b283a318b99bd46ad618194b7a
|
Provenance
The following attestation bundles were made for unforgettable-0.3.0.tar.gz:
Publisher:
publish-pypi.yml on bouli/unforgettable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
unforgettable-0.3.0.tar.gz -
Subject digest:
910e82499c5ecdb0dc392702329161e0614429e78d389bad879204358120511b - Sigstore transparency entry: 1740573647
- Sigstore integration time:
-
Permalink:
bouli/unforgettable@437179b2f362ba758ec81fe6710accbb06acd8eb -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/bouli
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@437179b2f362ba758ec81fe6710accbb06acd8eb -
Trigger Event:
push
-
Statement type:
File details
Details for the file unforgettable-0.3.0-py3-none-any.whl.
File metadata
- Download URL: unforgettable-0.3.0-py3-none-any.whl
- Upload date:
- Size: 8.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
581154d4f40d298354275102b71c2bd8eef8e2a048de04f8eaa170d4e40c72b7
|
|
| MD5 |
fd666e0a39ed3e892f1bfd07114ddd95
|
|
| BLAKE2b-256 |
df8a81520e5ad7da21b6194895bdd47e63f9fe403dd0703cfbacc3f2470eb424
|
Provenance
The following attestation bundles were made for unforgettable-0.3.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on bouli/unforgettable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
unforgettable-0.3.0-py3-none-any.whl -
Subject digest:
581154d4f40d298354275102b71c2bd8eef8e2a048de04f8eaa170d4e40c72b7 - Sigstore transparency entry: 1740573656
- Sigstore integration time:
-
Permalink:
bouli/unforgettable@437179b2f362ba758ec81fe6710accbb06acd8eb -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/bouli
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@437179b2f362ba758ec81fe6710accbb06acd8eb -
Trigger Event:
push
-
Statement type: