Amanda Torres
initial commit
db521fa
import logging
import time
from typing import Any, Optional
logger = logging.getLogger(__name__)
_MAX_RETRIES = 3
class App:
def __init__(self, debug: bool = False, dry_run: bool = False) -> None:
self.debug = debug
self.dry_run = dry_run
self._started = False
self._components: list[Any] = []
self._hooks: dict[str, list] = {"startup": [], "shutdown": []}
def on_startup(self, fn) -> None:
self._hooks["startup"].append(fn)
def on_shutdown(self, fn) -> None:
self._hooks["shutdown"].append(fn)
def _run_hooks(self, event: str) -> None:
for fn in self._hooks.get(event, []):
try:
fn()
except Exception as exc:
logger.error("Hook %s raised: %s", fn.__name__, exc)
def _load_components(self) -> None:
logger.debug("Loading components")
self._components = []
def _warmup(self) -> bool:
for attempt in range(1, _MAX_RETRIES + 1):
try:
self._load_components()
return True
except Exception as exc:
logger.warning("Warmup attempt %d/%d: %s", attempt, _MAX_RETRIES, exc)
time.sleep(0.5 * attempt)
return False
def run(self) -> None:
logger.info("Starting (debug=%s dry_run=%s)", self.debug, self.dry_run)
if not self._warmup():
logger.error("Warmup failed, aborting")
return
self._started = True
self._run_hooks("startup")
try:
self._main_loop()
finally:
self.shutdown()
def _main_loop(self) -> None:
if self.dry_run:
logger.info("[dry-run] skipping main loop")
return
logger.info("Running main loop")
def shutdown(self) -> None:
if not self._started:
return
self._run_hooks("shutdown")
self._components.clear()
self._started = False
logger.info("Shutdown complete")
def status(self) -> dict:
return {
"started": self._started,
"debug": self.debug,
"dry_run": self.dry_run,
"components": len(self._components),
}