refactor(config): migrer les endpoints Pronote

Co-authored-by: Codex <codex@antoineve.me>
This commit is contained in:
2026-09-13 11:54:19 +02:00
parent 097a27fcf3
commit ee89f13d67
9 changed files with 293 additions and 77 deletions
+69 -13
View File
@@ -88,13 +88,28 @@ class PronoteSettings(BaseSettings):
``PRONOTE_``.
"""
model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="PRONOTE_")
model_config = SettingsConfigDict(
env_file=".env",
env_nested_delimiter="__",
extra="ignore",
env_prefix="PRONOTE_",
)
ical_url: SecretStr | None = None
endpoint: ExternalEndpoint | None = None
ical_endpoint: ExternalEndpoint | None = None
ical_url: SecretStr | None = Field(
default=None,
exclude=True,
deprecated="Utiliser ical_endpoint.url à la place (PRONOTE_ICAL_URL obsolète).",
)
username: str | None = None
password: SecretStr | None = None
ent: str | None = None
url: str | None = None
url: str | None = Field(
default=None,
exclude=True,
deprecated="Utiliser endpoint.url à la place (PRONOTE_URL obsolète).",
)
account_type: Literal["student", "parent"] = "parent"
agenda_source: Literal["auto", "ical", "pronotepy"] = "auto"
homework_source: Literal["auto", "ical", "pronotepy"] = "auto"
@@ -104,17 +119,57 @@ class PronoteSettings(BaseSettings):
qr_pin: SecretStr | None = None
account_pin: SecretStr | None = None
@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).
@model_validator(mode="before")
@classmethod
def _migrate_legacy_endpoints(cls, data: object) -> object:
"""Migre les URL Pronote historiques vers les endpoints communs.
:param value: Valeur du champ ``ical_url``.
:return: ``"**********"`` si la valeur est définie, ``None`` sinon.
:rtype: str | None
:param data: Données brutes du modèle.
:return: Données complétées avec les endpoints si nécessaire.
:rtype: object
"""
if value is None:
return None
return "**********"
if not isinstance(data, dict):
return data
migrated_data = data.copy()
if migrated_data.get("url") is not None:
warnings.warn(
"PRONOTE_URL est obsolète : utiliser PRONOTE_ENDPOINT__URL.",
DeprecationWarning,
stacklevel=2,
)
if migrated_data.get("endpoint") is None:
migrated_data["endpoint"] = {"url": migrated_data["url"]}
if migrated_data.get("ical_url") is not None:
warnings.warn(
"PRONOTE_ICAL_URL est obsolète : utiliser PRONOTE_ICAL_ENDPOINT__URL.",
DeprecationWarning,
stacklevel=2,
)
if migrated_data.get("ical_endpoint") is None:
migrated_data["ical_endpoint"] = {"url": migrated_data["ical_url"]}
return migrated_data
@model_validator(mode="after")
def _validate_endpoint_policies(self) -> PronoteSettings:
"""Applique les transports autorisés aux deux endpoints Pronote.
L'API Pronote utilise HTTPS. Le flux iCal accepte également ``file``
afin de préserver les fixtures locales injectées.
:return: Instance validée inchangée.
:rtype: PronoteSettings
:raises ValueError: Si un endpoint utilise un schéma interdit.
"""
if (
self.endpoint is not None
and urlparse(self.endpoint.url.get_secret_value()).scheme != "https"
):
raise ValueError("URL Pronote invalide : HTTPS requis") from None
if self.ical_endpoint is not None and urlparse(
self.ical_endpoint.url.get_secret_value()
).scheme not in {"https", "file"}:
raise ValueError("URL iCal Pronote invalide : HTTPS ou file requis") from None
return self
@field_serializer("qr_pin")
def _serialize_qr_pin(self, value: SecretStr | None) -> str | None:
@@ -458,7 +513,8 @@ class Settings(BaseSettings):
:rtype: tuple[SecretStr, ...]
"""
secrets = [
self.pronote.ical_url,
self.pronote.endpoint.url if self.pronote.endpoint is not None else None,
self.pronote.ical_endpoint.url if self.pronote.ical_endpoint is not None else None,
self.pronote.password,
self.pronote.qr_pin,
self.pronote.account_pin,