""" Entrypoint worker arq – PecFlow IMAP Sync Engine. Avvio: python -m app.main Cosa fa: 1. Connette a Redis tramite arq 2. on_startup → avvia MailboxPool (N task IMAP asincroni) 3. on_shutdown → ferma MailboxPool 4. Registra job arq (sync_mailbox, future: send_pec, archive_batch, ecc.) 5. Loop arq per processare job dalla coda Redis L'event loop è condiviso tra arq e MailboxPool (asyncio task). """ import asyncio import logging import sys from typing import Any import redis.asyncio as aioredis from arq import run_worker from arq.connections import RedisSettings from app.config import get_settings from app.imap.pool import MailboxPool from app.jobs.sync_mailbox import sync_mailbox from app.storage.minio_client import ensure_bucket_exists settings = get_settings() # Logging logging.basicConfig( level=getattr(logging, settings.log_level.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", stream=sys.stdout, ) logger = logging.getLogger(__name__) # Pool globale (accessibile dalle callback) _mailbox_pool: MailboxPool | None = None # ─── Lifecycle callbacks arq ────────────────────────────────────────────────── async def on_startup(ctx: dict[str, Any]) -> None: """ Inizializzazione worker all'avvio. Avvia il MailboxPool con tutte le caselle attive. """ global _mailbox_pool logger.info("🚀 PecFlow Worker avviato") logger.info(f" DB: {settings.database_url.split('@')[-1]}") logger.info(f" Redis: {settings.redis_url}") logger.info(f" MinIO: {settings.minio_endpoint}") # Verifica/crea bucket MinIO try: await ensure_bucket_exists() except Exception as e: logger.warning(f"MinIO non disponibile al startup: {e}") # Crea client Redis condiviso redis_client = aioredis.from_url(settings.redis_url, decode_responses=True) ctx["redis"] = redis_client # Avvia MailboxPool _mailbox_pool = MailboxPool(redis_client=redis_client) ctx["mailbox_pool"] = _mailbox_pool await _mailbox_pool.start() logger.info( f"✅ Worker pronto: {_mailbox_pool.active_count} caselle IMAP attive" ) async def on_shutdown(ctx: dict[str, Any]) -> None: """Cleanup all'arresto del worker.""" global _mailbox_pool logger.info("🛑 PecFlow Worker in arresto...") pool = ctx.get("mailbox_pool") or _mailbox_pool if pool: await pool.stop() redis_client = ctx.get("redis") if redis_client: await redis_client.aclose() logger.info("🛑 Worker fermato") # ─── Worker health check ────────────────────────────────────────────────────── async def health_check(ctx: dict[str, Any]) -> dict: """ Job speciale per health check del worker. Può essere chiamato da monitoring esterno. """ pool: MailboxPool | None = ctx.get("mailbox_pool") return { "status": "ok", "active_imap_connections": pool.active_count if pool else 0, } # ─── WorkerSettings arq ─────────────────────────────────────────────────────── def _parse_redis_settings() -> RedisSettings: """Parsa REDIS_URL in RedisSettings arq.""" url = settings.redis_url # Supporta redis://host:port/db e redis://user:pass@host:port/db import urllib.parse parsed = urllib.parse.urlparse(url) host = parsed.hostname or "localhost" port = parsed.port or 6379 db = int(parsed.path.lstrip("/") or "0") password = parsed.password or None return RedisSettings(host=host, port=port, database=db, password=password) class WorkerSettings: """Configurazione del worker arq.""" # Funzioni/job registrati functions = [sync_mailbox, health_check] # Callbacks lifecycle on_startup = on_startup on_shutdown = on_shutdown # Redis redis_settings = _parse_redis_settings() # Concorrenza max_jobs = 20 # Timeout per ogni job (secondi) job_timeout = 300 # Retry automatico in caso di errore max_tries = 3 # Polling interval (arq controlla la coda ogni N ms) poll_delay = 0.5 # Keep job results per N secondi keep_result = 3600 # ─── Entrypoint ─────────────────────────────────────────────────────────────── if __name__ == "__main__": logger.info("Avvio PecFlow Worker (arq)...") run_worker(WorkerSettings)