refactor: rename package fabledassistant -> scribe (code-only)
Renames src/fabledassistant -> src/scribe and all imports, plus the default DB name and DB user/password (fabled -> scribe) in config + compose. 952 refs / 154 files. Reverses the old 'internal name stays fabledassistant' convention. Code-only: live databases are still physically named 'fabledassistant'. Deployed environments must set POSTGRES_DB / POSTGRES_USER (or rename the DB) since the defaults now resolve to 'scribe'. Repo (FabledScribe), git host (fabledsword), MCP (fabled-git) and the image name (fabledscribe) are intentionally unchanged. ruff check src/ clean locally; CI (typecheck + pytest) is the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""Daily APScheduler cron that purges expired trash.
|
||||
|
||||
Mirrors version_pinning_scheduler.py: a single global BackgroundScheduler job
|
||||
at 03:30 UTC bridges into the asyncio loop to run the async purge. Iterates
|
||||
every user and applies that user's own `trash_retention_days` setting; 0
|
||||
disables auto-purge for that user.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
from scribe.services import trash as trash_svc
|
||||
from scribe.services.settings import get_setting
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_scheduler: BackgroundScheduler | None = None
|
||||
_loop: asyncio.AbstractEventLoop | None = None
|
||||
|
||||
|
||||
def _run_purge_threadsafe() -> None:
|
||||
"""APScheduler invokes this from a worker thread; bridge into the loop."""
|
||||
if _loop is None:
|
||||
logger.warning("trash scheduler: no loop registered")
|
||||
return
|
||||
|
||||
async def _runner():
|
||||
try:
|
||||
from sqlalchemy import select
|
||||
|
||||
from scribe.models import async_session
|
||||
from scribe.models.user import User
|
||||
|
||||
async with async_session() as session:
|
||||
user_ids = (await session.execute(select(User.id))).scalars().all()
|
||||
|
||||
purged = 0
|
||||
for uid in user_ids:
|
||||
raw = await get_setting(uid, "trash_retention_days", "90")
|
||||
try:
|
||||
days = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
days = 90
|
||||
purged += await trash_svc.purge_expired(uid, days)
|
||||
if purged:
|
||||
logger.info("trash purge: removed %d expired row(s)", purged)
|
||||
else:
|
||||
logger.debug("trash purge: nothing expired")
|
||||
except Exception:
|
||||
logger.exception("trash purge run failed")
|
||||
|
||||
asyncio.run_coroutine_threadsafe(_runner(), _loop)
|
||||
|
||||
|
||||
def start_trash_scheduler(loop: asyncio.AbstractEventLoop) -> None:
|
||||
global _scheduler, _loop
|
||||
if _scheduler is not None:
|
||||
return
|
||||
_loop = loop
|
||||
_scheduler = BackgroundScheduler()
|
||||
_scheduler.add_job(
|
||||
_run_purge_threadsafe,
|
||||
trigger=CronTrigger(hour=3, minute=30, timezone="UTC"),
|
||||
id="trash_retention_purge",
|
||||
replace_existing=True,
|
||||
)
|
||||
_scheduler.start()
|
||||
logger.info("Trash retention scheduler started (daily 03:30 UTC)")
|
||||
|
||||
|
||||
def stop_trash_scheduler() -> None:
|
||||
global _scheduler
|
||||
if _scheduler is not None:
|
||||
_scheduler.shutdown(wait=False)
|
||||
_scheduler = None
|
||||
logger.info("Trash retention scheduler stopped")
|
||||
Reference in New Issue
Block a user