feat: refresh prices every 5min, add API_UPSTREAM to docker-compose

This commit is contained in:
Markus Eisl
2026-07-01 09:27:41 +02:00
parent 90bfdbd0a8
commit 0b5ba7c76c
2 changed files with 43 additions and 29 deletions
+41 -29
View File
@@ -15,41 +15,45 @@ from app.services.price_fetcher import fetch_all_prices
logger = logging.getLogger(__name__)
def fetch_prices_and_snapshot(database_url: str) -> None:
"""
Daily job that fetches all prices and writes a net worth snapshot.
Runs outside a request context, so it creates its own DB engine and session.
All exceptions are caught and logged — the job never raises.
"""
def _make_engine(database_url: str):
engine = create_engine(
database_url,
connect_args={"check_same_thread": False} if "sqlite" in database_url else {},
)
# Enable WAL mode for SQLite to avoid locking conflicts with the main app
if "sqlite" in database_url:
@event.listens_for(engine, "connect")
def set_wal_mode(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA journal_mode=WAL")
cursor.close()
return engine
LocalSession = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = LocalSession()
def fetch_prices_job(database_url: str) -> None:
"""Runs every 5 minutes — fetches all prices, no snapshot."""
engine = _make_engine(database_url)
db = sessionmaker(autocommit=False, autoflush=False, bind=engine)()
try:
updated_count = fetch_all_prices(db)
logger.info("Price refresh: updated %d price(s)", updated_count)
except Exception as exc:
logger.error("Price refresh: unexpected error: %s", exc)
finally:
db.close()
engine.dispose()
def fetch_prices_and_snapshot(database_url: str) -> None:
"""Runs at midnight — fetches prices and writes a daily net worth snapshot."""
engine = _make_engine(database_url)
db = sessionmaker(autocommit=False, autoflush=False, bind=engine)()
try:
# Step 1: Fetch all prices
updated_count = fetch_all_prices(db)
logger.info("Daily job: fetched/updated %d price(s)", updated_count)
# Step 2: Calculate net worth
net_worth_result = calculate_net_worth(db)
logger.info(
"Daily job: calculated net worth = %.2f EUR", net_worth_result["total_eur"]
)
logger.info("Daily job: net worth = %.2f EUR", net_worth_result["total_eur"])
# Step 3: Upsert today's snapshot
today = date.today()
breakdown = json.dumps(net_worth_result["accounts"])
try:
@@ -57,23 +61,20 @@ def fetch_prices_and_snapshot(database_url: str) -> None:
if existing:
existing.total_eur = net_worth_result["total_eur"]
existing.breakdown_json = breakdown
logger.info("Daily job: updated existing snapshot for %s", today)
logger.info("Daily job: updated snapshot for %s", today)
else:
db.add(
NetWorthSnapshot(
snapshot_date=today,
total_eur=net_worth_result["total_eur"],
breakdown_json=breakdown,
)
)
logger.info("Daily job: created new snapshot for %s", today)
db.add(NetWorthSnapshot(
snapshot_date=today,
total_eur=net_worth_result["total_eur"],
breakdown_json=breakdown,
))
logger.info("Daily job: created snapshot for %s", today)
db.commit()
except Exception as exc:
logger.error("Daily job: failed to upsert snapshot for %s: %s", today, exc)
logger.error("Daily job: failed to upsert snapshot: %s", exc)
db.rollback()
logger.info("Daily job: completed successfully for %s", today)
logger.info("Daily job: completed for %s", today)
except Exception as exc:
logger.error("Daily job: unexpected error: %s", exc)
finally:
@@ -94,6 +95,17 @@ def create_scheduler(database_url: str) -> BackgroundScheduler:
scheduler = BackgroundScheduler(jobstores=jobstores)
# Refresh prices every 5 minutes
scheduler.add_job(
fetch_prices_job,
trigger="interval",
minutes=5,
id="price_refresh",
replace_existing=True,
args=[database_url],
)
# Write net worth snapshot once per day at midnight
scheduler.add_job(
fetch_prices_and_snapshot,
trigger="cron",
+2
View File
@@ -18,6 +18,8 @@ services:
build: ./web
ports:
- "80:80"
environment:
- API_UPSTREAM=api:8000
depends_on:
api:
condition: service_healthy