@danhachuel/pandanews 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ """Scheduler do PandaNews (APScheduler).
2
+
3
+ Um job por blog ativo, com intervalo = frequencia_horas do blog. O estado
4
+ global (configuracoes.scheduler_ativo) liga/desliga tudo. Os jobs chamam o
5
+ pipeline e registram logs; erros não derrubam o scheduler.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from apscheduler.schedulers.background import BackgroundScheduler
10
+ from apscheduler.triggers.interval import IntervalTrigger
11
+ from sqlalchemy import select
12
+
13
+ from backend.core.database import Blog, Log, SessionLocal
14
+
15
+ _scheduler: BackgroundScheduler | None = None
16
+
17
+
18
+ def esta_ativo() -> bool:
19
+ return _scheduler is not None and _scheduler.running
20
+
21
+
22
+ def iniciar() -> None:
23
+ global _scheduler
24
+ if esta_ativo():
25
+ return
26
+ _scheduler = BackgroundScheduler(timezone="UTC", daemon=True)
27
+ _scheduler.start()
28
+
29
+
30
+ def parar() -> None:
31
+ global _scheduler
32
+ if _scheduler is not None:
33
+ _scheduler.shutdown(wait=False)
34
+ _scheduler = None
35
+
36
+
37
+ def recarregar() -> None:
38
+ """Sincroniza os jobs com os blogs ativos (após CRUD ou toggle)."""
39
+ if _scheduler is None:
40
+ return
41
+ db = SessionLocal()
42
+ try:
43
+ ativos = db.scalars(select(Blog).where(Blog.status == "ativo")).all()
44
+ _scheduler.remove_all_jobs()
45
+ for blog in ativos:
46
+ _scheduler.add_job(
47
+ _executar,
48
+ IntervalTrigger(hours=max(1, int(blog.frequencia_horas or 6))),
49
+ id=f"blog_{blog.id}",
50
+ args=[blog.id],
51
+ replace_existing=True,
52
+ )
53
+ finally:
54
+ db.close()
55
+
56
+
57
+ def jobs() -> list[dict]:
58
+ if _scheduler is None:
59
+ return []
60
+ resultado = []
61
+ for job in _scheduler.get_jobs():
62
+ resultado.append(
63
+ {
64
+ "blog_id": job.args[0] if job.args else None,
65
+ "proxima_execucao": job.next_run_time.isoformat() if job.next_run_time else None,
66
+ }
67
+ )
68
+ return resultado
69
+
70
+
71
+ def _executar(blog_id: int) -> None:
72
+ from backend.core import pipeline
73
+
74
+ db = SessionLocal()
75
+ try:
76
+ db.add(Log(blog_id=blog_id, etapa="pipeline", status="sucesso", mensagem="Execução agendada iniciada"))
77
+ db.commit()
78
+ finally:
79
+ db.close()
80
+ pipeline.gerar_noticia(blog_id)