Spaces:
Configuration error
Configuration error
File size: 2,722 Bytes
83bcbe3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | """
persistence.py — Save and restore alert engine state to/from a JSON file.
Called from main.py on startup (load) and graceful shutdown (save),
so tracked asset ranges survive bot restarts.
"""
import json
import os
from typing import TYPE_CHECKING
import config
from logger import get_logger
if TYPE_CHECKING:
from alert_engine import AlertEngine
log = get_logger("persistence")
def save_state(engine: "AlertEngine") -> None:
"""
Serialise the alert engine's tracked assets to PERSISTENCE_FILE.
Writes atomically via a temp file to avoid corruption on crash.
"""
details = engine.get_asset_details()
payload = {
"version": 1,
"assets": [
{
"name": d["name"],
"min_price": d["min"],
"max_price": d["max"],
}
for d in details
],
}
tmp_path = config.PERSISTENCE_FILE + ".tmp"
try:
with open(tmp_path, "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2)
os.replace(tmp_path, config.PERSISTENCE_FILE)
log.info("State saved → %s (%d assets)", config.PERSISTENCE_FILE, len(details))
except OSError as exc:
log.error("Failed to save state: %s", exc)
finally:
# Clean up temp file if replace failed
if os.path.exists(tmp_path):
try:
os.remove(tmp_path)
except OSError:
pass
def load_state(engine: "AlertEngine") -> int:
"""
Load tracked assets from PERSISTENCE_FILE into the alert engine.
Returns the number of assets restored.
Silently skips the file if it doesn't exist or is malformed.
"""
if not os.path.exists(config.PERSISTENCE_FILE):
log.info("No persistence file found at %s — starting fresh", config.PERSISTENCE_FILE)
return 0
try:
with open(config.PERSISTENCE_FILE, "r", encoding="utf-8") as f:
payload = json.load(f)
assets = payload.get("assets", [])
restored = 0
for entry in assets:
name = entry.get("name", "").upper()
min_p = float(entry["min_price"])
max_p = float(entry["max_price"])
if name and min_p < max_p:
engine.add_asset(name, min_p, max_p)
restored += 1
else:
log.warning("Skipping invalid entry in state file: %s", entry)
log.info("Restored %d asset(s) from %s", restored, config.PERSISTENCE_FILE)
return restored
except (json.JSONDecodeError, KeyError, ValueError, OSError) as exc:
log.error("Failed to load state from %s: %s", config.PERSISTENCE_FILE, exc)
return 0
|