Skip to content

File-Based Cache

Orionis Framework includes a file-based caching system that goes beyond simple key-value storage. Unlike conventional caching solutions that reduce everything to plain strings, Orionis preserves Python types across serialization boundaries — a Path is restored as a Path, a Decimal as a Decimal, and a datetime as a datetime. Combined with automatic invalidation driven by source-file monitoring, the cache provides a reliable, zero-configuration mechanism for persisting and recovering structured data.


FileBasedCache provides a dictionary-oriented cache backed by a single JSON file on disk. It supports automatic invalidation when monitored files or directories change, so you never serve stale data.

from pathlib import Path
from orionis.services.cache.file_based_cache import FileBasedCache
cache = FileBasedCache(
path=Path("storage/cache"),
filename="app_cache.json",
monitored_dirs=[Path("app/models"), Path("app/services")],
monitored_files=[Path("config/app.py")],
)
ParameterTypeRequiredDescription
pathPathYesDirectory where the cache file will be stored. Created automatically if it does not exist.
filenamestrYesName of the cache file inside path.
monitored_dirslist[Path] | NoneNoDirectories whose .py files are watched for changes.
monitored_fileslist[Path] | NoneNoIndividual files watched for changes.

Note: The path parameter must be a Path object. Passing a raw string raises TypeError.

The save() method writes a dictionary to disk:

version, sources_hash = cache.save({
"routes": ["/api/users", "/api/posts"],
"compiled_at": "2026-03-31T10:00:00",
})

Return value: a tuple of (cache_version, sources_hash) — useful for logging or debugging.

The cache applies a skip-on-unchanged optimization: if the existing cache already contains the same data and no monitored source has changed, the file is not rewritten. This avoids unnecessary disk I/O.

cache.save({"key": "value"}) # writes to disk
cache.save({"key": "value"}) # no-op — file untouched
cache.save({"key": "new"}) # writes to disk — data changed

Only dict values are accepted. Passing any other type raises TypeError.

The get() method returns the cached dictionary only if the cache is still valid:

data = cache.get()
if data is not None:
print(data["routes"])

get() returns None in any of these situations:

  • The cache file does not exist (first run or after clearing).
  • A monitored file or directory has changed since the last save.
  • The cache was created by an incompatible version of the framework.

When None is returned, you simply recompute and save the data — the cache handles the rest.

The clear() method removes the cache file from disk:

removed = cache.clear()
# True → file existed and was removed
# False → file did not exist

Calling clear() twice is safe — the second call returns False without raising an error.


The most distinctive feature of FileBasedCache is its source-aware invalidation. The cache automatically detects when monitored dependencies change and invalidates itself — no manual intervention required.

When you call save(), the cache takes a fingerprint of all monitored sources. On every get(), this fingerprint is recalculated and compared. If anything has changed, the cache is treated as stale and None is returned.

SourceWhat Is Watched
monitored_dirsAll *.py files recursively inside each directory
monitored_filesEach file individually

Key behaviors:

  • Non-existent directories and files are silently ignored — they do not cause errors.
  • The fingerprint is temporarily cached for rapid successive calls, avoiding redundant computation on high-frequency reads.
from pathlib import Path
from orionis.services.cache.file_based_cache import FileBasedCache
config_cache = FileBasedCache(
path=Path("storage/cache"),
filename="config.json",
monitored_files=[Path("config/app.py"), Path("config/database.py")],
)
# First run: no cache → compute and save
data = config_cache.get()
if data is None:
data = expensive_config_computation()
config_cache.save(data)
# Subsequent runs: cache is valid → instant retrieval
# If config/app.py or config/database.py changes → cache auto-invalidates

Orionis includes a standalone Serializer utility that you can use independently of FileBasedCache. It converts Python objects to JSON strings and back, preserving their original types through the round-trip.

from orionis.services.cache.serializer import Serializer

The serializer natively supports more than 18 Python types with full round-trip fidelity:

Python TypePreservation
str, int, float, bool, NoneExact
PathRestored as Path
bytesEncoded and restored
datetime, date, timeISO 8601 round-trip
timedeltaFull precision
DecimalFull precision
UUIDCanonical form
tupleRestored as tuple (not list)
set, frozensetRestored with correct type
complexFull precision
typeRestored via qualified module path
dict, listRecursive — nested types are also preserved

Unsupported types raise TypeError during serialization. Corrupted payloads raise ValueError on deserialization.

from pathlib import Path
from orionis.services.cache.serializer import Serializer
# Serialize to JSON string
raw = Serializer.dumps({"path": Path("/etc/config"), "count": 42})
# Deserialize back to Python — types are preserved
data = Serializer.loads(raw)
print(type(data["path"])) # <class 'pathlib.PosixPath'>

The optional indent parameter produces human-readable output:

raw = Serializer.dumps({"key": "value"}, indent=2)

The Serializer also provides direct file operations with safe write semantics — the target file is never left in a partial or corrupted state, even during unexpected process termination:

from pathlib import Path
from orionis.services.cache.serializer import Serializer
file = Path("storage/data.json")
# Write safely to file
Serializer.dumpToFile({"version": 1, "active": True}, file)
# Read from file — returns None if the file is missing or empty
data = Serializer.loadFromFile(file)
MethodSignatureDescription
dumps(data, indent=None) → strSerialize to JSON string
loads(raw: str) → AnyDeserialize from JSON string
dumpToFile(data, file_path: Path) → NoneSafe write to file
loadFromFile(file_path: Path) → Any | NoneRead from file; None if missing/empty

The serializer handles arbitrarily nested structures. Every element is recursively processed, preserving types at every level:

import decimal
from datetime import datetime
from pathlib import Path
from orionis.services.cache.serializer import Serializer
original = {
"timestamp": datetime(2026, 3, 31, 12, 0, 0),
"amount": decimal.Decimal("99.99"),
"files": [Path("/tmp/a.txt"), Path("/tmp/b.txt")],
"flags": (True, False, None),
}
raw = Serializer.dumps(original)
restored = Serializer.loads(raw)
assert restored["timestamp"] == original["timestamp"]
assert isinstance(restored["amount"], decimal.Decimal)
assert isinstance(restored["flags"], tuple)

The following example demonstrates a typical workflow: creating a cache with file monitoring, saving computed data, and retrieving it with automatic invalidation.

from pathlib import Path
from orionis.services.cache.file_based_cache import FileBasedCache
# Define the cache with source monitoring
cache = FileBasedCache(
path=Path("storage/cache"),
filename="routes.json",
monitored_dirs=[Path("app/http/controllers")],
monitored_files=[Path("routes/web.py"), Path("routes/api.py")],
)
# Try to load from cache
routes = cache.get()
if routes is None:
# Cache miss or invalidated — recompute
routes = discover_routes()
cache.save(routes)
# Use the routes
register(routes)
# When needed, clear explicitly
cache.clear()

Lifecycle:

  1. First run — no cache exists → get() returns None → routes are computed and saved.
  2. Subsequent runs — cache is valid, sources unchanged → get() returns cached data instantly.
  3. Source changes — a controller in app/http/controllers/ is modified → get() returns None → routes are recomputed automatically.