"""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 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)