Files
Moon-Household-Budget/storage.py
2026-06-06 19:12:43 -05:00

51 lines
1.6 KiB
Python

"""Crash-safe JSON persistence shared across all data modules.
- load_json: returns `default` if the file is missing, empty, or corrupt
(e.g. a partial/interrupted write). A corrupt file is moved aside to
`<path>.corrupt` so it can be inspected rather than silently overwritten.
- save_json: atomic write — serialize to a temp file in the same directory,
fsync, then os.replace() (atomic on POSIX). An interrupted write can never
leave a truncated or empty target file.
This is the durability backbone for the app's JSON storage (issues #11/#12).
"""
import json
import os
import tempfile
DATA_DIR = os.environ.get("DATA_DIR", os.path.join(os.path.dirname(__file__), "data"))
def load_json(path, default):
if not os.path.exists(path):
return default
try:
with open(path) as f:
return json.load(f)
except (json.JSONDecodeError, ValueError):
# Corrupt/partial file — preserve it for inspection, fall back to default
# instead of crashing the request.
try:
os.replace(path, path + ".corrupt")
except OSError:
pass
return default
def save_json(path, data):
directory = os.path.dirname(path) or "."
os.makedirs(directory, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=directory, prefix=".tmp-", suffix=".json")
try:
with os.fdopen(fd, "w") as f:
json.dump(data, f, indent=2)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, path)
except BaseException:
try:
os.remove(tmp)
except OSError:
pass
raise