Files
college-infos/pronote_sync/config/settings.py
Antoine Van Elstraete 1b550aa818 fix(M4): pronote_url + account_type et parsing iCal (STATUS:CANCELLED + blocs multi-dates)
Configuration (D1+D3) :
- PronoteSettings : ajout pronote_url (str | None) et account_type
  (Literal student/parent, défaut parent)

Parsing iCal (Fix 4+7) :
- parse_ical : détection STATUS:CANCELLED en plus de CATEGORIES
- parse_body : list[tuple[date, str]] au lieu de dict[date, str]
  pour préserver les blocs multiples à la même date
- parse_homework_blocks : adapté aux listes

Co-authored-by: opencode/coder <coder@agents.invalid>
2026-09-06 15:23:34 +02:00

144 lines
4.7 KiB
Python

"""Configuration de l'application via ``pydantic-settings``.
Chaque sous-groupe de configuration est un modèle ``BaseSettings`` dédié, chargé
depuis les variables d'environnement (préfixées par groupe) et le fichier
``.env``. Tous les champs disposent de valeurs par défaut sûres afin que
``Settings()`` fonctionne même sans fichier de configuration présent.
"""
from __future__ import annotations
from typing import Literal
from pydantic import Field, SecretStr, field_serializer
from pydantic_settings import BaseSettings, SettingsConfigDict
class PronoteSettings(BaseSettings):
"""Paramètres d'accès à Pronote (flux iCal et API ``pronotepy``).
Les variables d'environnement correspondantes sont préfixées par
``PRONOTE_``.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="PRONOTE_")
ical_url: SecretStr | None = None
username: str | None = None
password: SecretStr | None = None
ent: str | None = None
pronote_url: str | None = None
account_type: Literal["student", "parent"] = "parent"
agenda_source: Literal["auto", "ical", "pronotepy"] = "auto"
homework_source: Literal["auto", "ical", "pronotepy"] = "auto"
messages_source: Literal["pronotepy"] = "pronotepy"
@field_serializer("ical_url")
def _serialize_ical_url(self, value: SecretStr | None) -> str | None:
"""Masque l'URL iCal lors de la sérialisation (repr, str, JSON).
:param value: Valeur du champ ``ical_url``.
:return: ``"**********"`` si la valeur est définie, ``None`` sinon.
:rtype: str | None
"""
if value is None:
return None
return "**********"
class CalDAVSettings(BaseSettings):
"""Paramètres d'accès au serveur CalDAV de destination.
Les variables d'environnement correspondantes sont préfixées par
``CALDAV_``.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="CALDAV_")
url: str | None = None
username: str | None = None
password: SecretStr | None = None
calendar_path: str = "/pronote-sync/"
class XmppSettings(BaseSettings):
"""Paramètres du canal de notifications XMPP (désactivé par défaut).
Tous les champs ont des valeurs par défaut afin que le canal XMPP reste
inactif tant qu'il n'est pas explicitement activé. Les variables
d'environnement correspondantes sont préfixées par ``XMPP_``.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="XMPP_")
enabled: bool = False
jid: str | None = None
password: SecretStr | None = None
host: str = ""
port: int = 5222
to: str | None = None
resource: str = "pronote-sync"
use_tls: bool = True
timeout: int = 30
class AISettings(BaseSettings):
"""Paramètres de la synthèse par IA (désactivée par défaut).
Les variables d'environnement correspondantes sont préfixées par ``AI_``.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="AI_")
enabled: bool = False
provider: Literal["openai", "litellm"] = "openai"
base_url: str | None = None
api_key: SecretStr | None = None
model: str | None = None
class BlogSettings(BaseSettings):
"""Paramètres de la source RSS du blog du collège (désactivée par défaut).
Les variables d'environnement correspondantes sont préfixées par
``BLOG_``.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="BLOG_")
enabled: bool = False
rss_url: str = "https://blogpeda.ac-bordeaux.fr/cjeliote/?feed=rss2"
class AppSettings(BaseSettings):
"""Paramètres généraux de l'application, sans préfixe d'environnement.
Contient notamment la fenêtre de synchronisation en jours
(``SYNC_PAST_DAYS`` / ``SYNC_FUTURE_DAYS``).
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
dry_run: bool = False
log_level: str = "INFO"
theoretical_agenda_path: str | None = None
sync_past_days: int = 7
sync_future_days: int = 30
class Settings(BaseSettings):
"""Configuration racine du pipeline ``pronote-sync``.
Agrège les sous-groupes de configuration : Pronote, CalDAV, XMPP, IA,
blog et paramètres généraux de l'application.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
pronote: PronoteSettings = Field(default_factory=PronoteSettings)
caldav: CalDAVSettings = Field(default_factory=CalDAVSettings)
xmpp: XmppSettings = Field(default_factory=XmppSettings)
ai: AISettings = Field(default_factory=AISettings)
blog: BlogSettings = Field(default_factory=BlogSettings)
app: AppSettings = Field(default_factory=AppSettings)