fix(M4): modes explicites ical/pronotepy stricts — suppression du repli réciproque
Les modes explicites (ical, pronotepy) ne basculent plus silencieusement vers l'autre source en cas d'échec. Seul le mode auto conserve le repli iCal → pronotepy sur exception. Conforme au contrat de TODO.md M4 et AGENTS.md. - fallback.py : _agenda_sources() et _homework_sources() retournent (source, None) en mode explicite ; docstrings mises à jour. - test_fallback.py : 4 tests remplacés, 4 tests ajoutés (non-repli + empty-list-is-success), 4 tests ajustés ; :rtype: None ajouté. Co-authored-by: coder/coder coder@agents.invalid Co-authored-by: test-engineer/test-engineer test-engineer@agents.invalid
This commit is contained in:
@@ -4,9 +4,9 @@ Ce module fournit l'enum :class:`AgendaSource`, le protocole
|
||||
:class:`PronoteFetcherProtocol` consommé par le pipeline ainsi que la
|
||||
classe :class:`PronoteFetcher` qui sélectionne la source selon la
|
||||
configuration (``PRONOTE_AGENDA_SOURCE`` / ``PRONOTE_HOMEWORK_SOURCE``).
|
||||
La logique applique un repli **unique** : une source primaire est
|
||||
essayée en premier et, en cas d'échec, une seule source de repli (jamais
|
||||
réciproque ni itératif) est essayée si elle est configurée. Les messages
|
||||
Contrat strict : les modes explicites n'utilisent que la source
|
||||
configurée, sans aucun repli ; seul le mode ``auto`` applique un repli
|
||||
unique iCal → pronotepy, en cas d'exception uniquement. Les messages
|
||||
et informations proviennent toujours de pronotepy. Toutes les erreurs
|
||||
sont journalisées avec des secrets masqués via
|
||||
:func:`~pronote_sync.utils.redaction.redact_exception` ; les exceptions
|
||||
@@ -82,13 +82,14 @@ class PronoteFetcherProtocol(Protocol):
|
||||
|
||||
|
||||
class PronoteFetcher:
|
||||
"""Récupère les données Pronote via iCal ou pronotepy avec repli unique.
|
||||
"""Récupère les données Pronote via iCal ou pronotepy, repli réservé au mode ``auto``.
|
||||
|
||||
Unifie les sources iCal et pronotepy selon la source configurée
|
||||
(``agenda_source`` / ``homework_source``) : la source primaire est
|
||||
essayée en premier et, si elle échoue, une seule source de repli est
|
||||
essayée lorsqu'elle est configurée. Les messages et informations
|
||||
proviennent toujours de pronotepy.
|
||||
(``agenda_source`` / ``homework_source``) : les modes explicites
|
||||
n'utilisent que la source configurée, sans aucun repli ; seul le mode
|
||||
``auto`` essaie une source primaire puis, si elle échoue, une seule
|
||||
source de repli lorsqu'elle est configurée. Les messages et
|
||||
informations proviennent toujours de pronotepy.
|
||||
"""
|
||||
|
||||
def __init__(self, settings: Settings, pronote_client: PronoteClientProtocol) -> None:
|
||||
@@ -171,11 +172,10 @@ class PronoteFetcher:
|
||||
def _agenda_sources(self) -> tuple[_SourceName, _SourceName | None]:
|
||||
"""Sélectionne la source primaire et le repli unique pour l'agenda.
|
||||
|
||||
En mode ``AUTO``, iCal est primaire si ``ical_url`` est configuré
|
||||
(repli pronotepy si la configuration pronotepy est complète),
|
||||
sinon pronotepy sans repli. Les modes explicites ``ICAL`` et
|
||||
``PRONOTEPY`` désignent la source primaire et tentent l'autre
|
||||
source en repli unique si elle est configurée.
|
||||
Les modes explicites ``ICAL`` et ``PRONOTEPY`` désignent la seule
|
||||
source utilisée, sans aucun repli. En mode ``AUTO``, iCal est
|
||||
primaire si ``ical_url`` est configuré (repli pronotepy si la
|
||||
configuration pronotepy est complète), sinon pronotepy sans repli.
|
||||
|
||||
:return: Tuple ``(source primaire, source de repli ou ``None``)``.
|
||||
:rtype: tuple[_SourceName, _SourceName | None]
|
||||
@@ -183,9 +183,9 @@ class PronoteFetcher:
|
||||
"""
|
||||
source = AgendaSource(self._settings.pronote.agenda_source)
|
||||
if source is AgendaSource.ICAL:
|
||||
return "ical", "pronotepy" if self._is_pronotepy_configured() else None
|
||||
return "ical", None
|
||||
if source is AgendaSource.PRONOTEPY:
|
||||
return "pronotepy", "ical" if self._is_ical_configured() else None
|
||||
return "pronotepy", None
|
||||
if self._is_ical_configured():
|
||||
return "ical", "pronotepy" if self._is_pronotepy_configured() else None
|
||||
if self._is_pronotepy_configured():
|
||||
@@ -208,11 +208,13 @@ class PronoteFetcher:
|
||||
def fetch_agenda(self) -> tuple[list[Lesson], list[SchoolEvent]]:
|
||||
"""Récupère les cours et les événements scolaires selon la source configurée.
|
||||
|
||||
La source primaire est essayée en premier ; si elle échoue, la
|
||||
source de repli unique (l'autre source, si configurée) est
|
||||
essayée. Si la source primaire et le repli échouent — ou si
|
||||
aucune source n'est configurée en mode ``AUTO`` — une erreur
|
||||
critique est levée.
|
||||
En mode explicite (``ical`` ou ``pronotepy``), la source désignée
|
||||
est la seule tentée : si elle échoue, une erreur critique est levée
|
||||
sans repli. En mode ``auto``, la source primaire est essayée en
|
||||
premier puis, si elle échoue, la source de repli unique (l'autre
|
||||
source, si configurée) l'est à son tour ; si la source primaire et
|
||||
le repli échouent — ou si aucune source n'est configurée — une
|
||||
erreur critique est levée.
|
||||
|
||||
:return: Tuple ``(cours, événements scolaires)``.
|
||||
:rtype: tuple[list[Lesson], list[SchoolEvent]]
|
||||
@@ -289,11 +291,10 @@ class PronoteFetcher:
|
||||
def _homework_sources(self) -> tuple[_SourceName, _SourceName | None]:
|
||||
"""Sélectionne la source primaire et le repli unique pour les devoirs.
|
||||
|
||||
En mode ``AUTO``, iCal est primaire si ``ical_url`` est configuré
|
||||
(repli pronotepy si la configuration pronotepy est complète),
|
||||
sinon pronotepy sans repli. Les modes explicites ``ICAL`` et
|
||||
``PRONOTEPY`` désignent la source primaire et tentent l'autre
|
||||
source en repli unique si elle est configurée.
|
||||
Les modes explicites ``ICAL`` et ``PRONOTEPY`` désignent la seule
|
||||
source utilisée, sans aucun repli. En mode ``AUTO``, iCal est
|
||||
primaire si ``ical_url`` est configuré (repli pronotepy si la
|
||||
configuration pronotepy est complète), sinon pronotepy sans repli.
|
||||
|
||||
:return: Tuple ``(source primaire, source de repli ou ``None``)``.
|
||||
:rtype: tuple[_SourceName, _SourceName | None]
|
||||
@@ -301,9 +302,9 @@ class PronoteFetcher:
|
||||
"""
|
||||
source = AgendaSource(self._settings.pronote.homework_source)
|
||||
if source is AgendaSource.ICAL:
|
||||
return "ical", "pronotepy" if self._is_pronotepy_configured() else None
|
||||
return "ical", None
|
||||
if source is AgendaSource.PRONOTEPY:
|
||||
return "pronotepy", "ical" if self._is_ical_configured() else None
|
||||
return "pronotepy", None
|
||||
if self._is_ical_configured():
|
||||
return "ical", "pronotepy" if self._is_pronotepy_configured() else None
|
||||
if self._is_pronotepy_configured():
|
||||
@@ -327,11 +328,13 @@ class PronoteFetcher:
|
||||
def fetch_homework(self, target_date: date) -> list[Homework]:
|
||||
"""Récupère les devoirs selon la source configurée.
|
||||
|
||||
La source primaire est essayée en premier ; si elle échoue, la
|
||||
source de repli unique (l'autre source, si configurée) est
|
||||
essayée. Si la source primaire et le repli échouent — ou si
|
||||
aucune source n'est configurée en mode ``AUTO`` — une erreur
|
||||
critique est levée.
|
||||
En mode explicite (``ical`` ou ``pronotepy``), la source désignée
|
||||
est la seule tentée : si elle échoue, une erreur critique est levée
|
||||
sans repli. En mode ``auto``, la source primaire est essayée en
|
||||
premier puis, si elle échoue, la source de repli unique (l'autre
|
||||
source, si configurée) l'est à son tour ; si la source primaire et
|
||||
le repli échouent — ou si aucune source n'est configurée — une
|
||||
erreur critique est levée.
|
||||
|
||||
:param target_date: Date cible pour laquelle collecter les devoirs.
|
||||
:return: Liste des devoirs.
|
||||
|
||||
Reference in New Issue
Block a user