gateway/modules/shared/eventManagement.py
2025-09-23 22:47:54 +02:00

120 lines
3.6 KiB
Python

import logging
from typing import Callable, Optional, Dict, Any
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger
from zoneinfo import ZoneInfo
logger = logging.getLogger(__name__)
class EventManagement:
"""
Generic event scheduler wrapper around APScheduler's AsyncIOScheduler.
Features:
- start/stop lifecycle
- register timed events with either cron or interval style
- remove events by id
"""
def __init__(self, timezone: str = "Europe/Zurich"):
self._timezone = ZoneInfo(timezone)
self._scheduler: Optional[AsyncIOScheduler] = None
@property
def scheduler(self) -> AsyncIOScheduler:
if self._scheduler is None:
self._scheduler = AsyncIOScheduler(timezone=self._timezone)
return self._scheduler
def start(self) -> None:
if not self.scheduler.running:
self.scheduler.start()
logger.info("EventManagement scheduler started")
def stop(self) -> None:
if self._scheduler and self._scheduler.running:
try:
self._scheduler.shutdown(wait=False)
logger.info("EventManagement scheduler stopped")
except Exception as exc:
logger.error(f"Error stopping scheduler: {exc}")
def register_cron(
self,
job_id: str,
func: Callable,
*,
cron_kwargs: Optional[Dict[str, Any]] = None,
replace_existing: bool = True,
coalesce: bool = True,
max_instances: int = 1,
misfire_grace_time: int = 1800,
**kwargs: Any,
) -> None:
"""
Register a job using CronTrigger. Provide cron fields as keyword args, e.g.:
cron_kwargs={"minute": "0,20,40"}
"""
trigger = CronTrigger(timezone=self._timezone, **(cron_kwargs or {}))
self.scheduler.add_job(
func,
trigger,
id=job_id,
replace_existing=replace_existing,
coalesce=coalesce,
max_instances=max_instances,
misfire_grace_time=misfire_grace_time,
**kwargs,
)
logger.info(f"Registered cron job '{job_id}' with args {cron_kwargs}")
def register_interval(
self,
job_id: str,
func: Callable,
*,
seconds: Optional[int] = None,
minutes: Optional[int] = None,
hours: Optional[int] = None,
replace_existing: bool = True,
coalesce: bool = True,
max_instances: int = 1,
misfire_grace_time: int = 1800,
**kwargs: Any,
) -> None:
"""
Register a job using IntervalTrigger.
"""
trigger = IntervalTrigger(
seconds=seconds, minutes=minutes, hours=hours, timezone=self._timezone
)
self.scheduler.add_job(
func,
trigger,
id=job_id,
replace_existing=replace_existing,
coalesce=coalesce,
max_instances=max_instances,
misfire_grace_time=misfire_grace_time,
**kwargs,
)
logger.info(
f"Registered interval job '{job_id}' (h={hours}, m={minutes}, s={seconds})"
)
def remove(self, job_id: str) -> None:
try:
self.scheduler.remove_job(job_id)
logger.info(f"Removed job '{job_id}'")
except Exception as exc:
logger.warning(f"Could not remove job '{job_id}': {exc}")
# Singleton instance for easy import and reuse
eventManager = EventManagement()