Python Basics: Persist Small Trusted Objects with shelve
A local experiment can store Python values behind string keys using shelve. The database persists assigned values, but mutating a retrieved value does not automatically write it back with the default settings. A temporary fixture makes that distinction explicit.
Prepare a scratch directory
Tested on Linux with CPython 3.12.14. You need the named runtime, Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. Run Python without -O so the assertions remain enabled. Any generated fixture files live inside an automatically cleaned temporary directory.
Keep using the same terminal. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as demonstrate-shelve.py:
from pathlib import Path
from tempfile import TemporaryDirectory
import shelve
with TemporaryDirectory() as directory:
filename = str(Path(directory) / "practice_store")
with shelve.open(filename, flag="n", writeback=False) as store:
store["tasks"] = ["draft"]
store["version"] = 1
with shelve.open(filename, flag="w", writeback=False) as store:
tasks = store["tasks"]
tasks.append("review")
assert store["tasks"] == ["draft"]
print("Before reassignment:", store["tasks"])
store["tasks"] = tasks
with shelve.open(filename, flag="r") as store:
assert store["tasks"] == ["draft", "review"]
assert store["version"] == 1
print("After reopen:", store["tasks"])
print("Stored keys:", sorted(store.keys()))
try:
store["missing"]
except KeyError:
print("Missing key: rejected")
else:
raise AssertionError("Missing key unexpectedly existed")Run it:
python3 -B demonstrate-shelve.pyObserved output:
Before reassignment: ['draft']
After reopen: ['draft', 'review']
Stored keys: ['tasks', 'version']
Missing key: rejectedRead the result
The first context creates a new database inside an owned temporary directory. The next context requires an existing writable database. A retrieved list is a deserialized value; appending to it changes that local list, not the stored record. Reassigning the list persists it, and the final read-only reopen verifies the result. Context managers close the database before temporary cleanup.
Apply it carefully
shelve uses pickle for values: opening and reading an untrusted shelf can execute code. Use only data you created and trust. The backing DBM implementation and generated filenames vary by platform, so this example does not assume one filename extension. Do not use concurrent writers or treat shelves as a transactional multi-user database. writeback=True changes caching and write behavior and can increase memory use; explicit reassignment is clearer for this small case.
The official reference describes the API and its options. The checks above cover the specific fixture, not every possible input or environment.
Clean up
Remove the source and then the empty practice directory:
rm -- demonstrate-shelve.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect the remaining files rather than deleting unknown contents.
Acknowledgements
This post was created with assistance from AI.