Skip to main content

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 get exit with status code 1.
  • 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 to cache.

cache.set(content, cache_id)

Stores content under cache_id.

  • content can be str or bytes.
  • Calling set() again with the same cache_id overwrites the existing cached content.

cache.get(cache_id)

Returns the cached value for cache_id.

  • Returns str for text files.
  • Returns bytes for binary files.
  • Returns None when 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 -q
  • uv run --dev pytest -q tests/test_packaging.py
  • uv build --wheel
  • uv tool run unforgettable --help
  • uv tool run unforgettable --version
  • uvx unforgettable --help where the uvx alias is installed
  • uvx unforgettable --version where the uvx alias is installed
  • unforgettable --help after installing the built wheel
  • python -m unforgettable --help after installing the built wheel
  • unforgettable --version after 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

unforgettable-0.3.0.tar.gz (14.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unforgettable-0.3.0-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

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

Hashes for unforgettable-0.3.0.tar.gz
Algorithm Hash digest
SHA256 910e82499c5ecdb0dc392702329161e0614429e78d389bad879204358120511b
MD5 5271c6a1e859416b0d81807b4aece457
BLAKE2b-256 5a7fbf049df44764fbc8cbf4d1a2801196df32b283a318b99bd46ad618194b7a

See more details on using hashes here.

Provenance

The following attestation bundles were made for unforgettable-0.3.0.tar.gz:

Publisher: publish-pypi.yml on bouli/unforgettable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for unforgettable-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 581154d4f40d298354275102b71c2bd8eef8e2a048de04f8eaa170d4e40c72b7
MD5 fd666e0a39ed3e892f1bfd07114ddd95
BLAKE2b-256 df8a81520e5ad7da21b6194895bdd47e63f9fe403dd0703cfbacc3f2470eb424

See more details on using hashes here.

Provenance

The following attestation bundles were made for unforgettable-0.3.0-py3-none-any.whl:

Publisher: publish-pypi.yml on bouli/unforgettable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page