[ka6] fix connecteur firststudent: retry avec backoff sur 5xx transitoire dans la plateforme workday — un 500 isolé de l'API CXS (constaté 2026-09-07 18h18 sur firststudent.wd1, reparti seul) faisait échouer tout le sync car raise_for_status dans base.get/post court-circuitait le retry de _req_json (qui ne couvrait que le cas 200 non-JSON); les 4xx restent des échecs immédiats; sync rejoué: 35 offres (médiane 35.0)
1 changed file +17 −3
modified
jobka/connectors/workday.py
+17 −3
@@ -5,7 +5,7 @@ | |||||
| 5 | # Fichier : jobka/connectors/workday.py | 5 | # Fichier : jobka/connectors/workday.py | ||
| 6 | # Rôle : Classe de plateforme Workday (API JSON publique wday/cxs) — un | 6 | # Rôle : Classe de plateforme Workday (API JSON publique wday/cxs) — un | ||
| 7 | # employeur = une sous-classe de ~10 lignes (TENANT, HOST, SITE) | 7 | # employeur = une sous-classe de ~10 lignes (TENANT, HOST, SITE) | ||
| 8 | −# Créé : 2026-08-17 Modifié : 2026-09-04 | 8 | +# Créé : 2026-08-17 Modifié : 2026-09-07 | ||
| 9 | # ============================================================================= | 9 | # ============================================================================= | ||
| 10 | """Plateforme Workday. | 10 | """Plateforme Workday. | ||
| 11 | 11 | ||||
@@ -68,14 +68,28 @@ class WorkdayConnector(BaseConnector): | |||||
| 68 | # sur de nombreux tenants) : on retente avec backoff avant | 68 | # sur de nombreux tenants) : on retente avec backoff avant | ||
| 69 | # d'abandonner, et l'erreur finale nomme le statut HTTP + | 69 | # d'abandonner, et l'erreur finale nomme le statut HTTP + | ||
| 70 | # content-type pour que sync_log soit diagnostiquable. | 70 | # content-type pour que sync_log soit diagnostiquable. | ||
| 71 | − last: ValueError | None = None | 71 | + # Même traitement pour les 5xx transitoires (500 isolé constaté le | ||
| 72 | + # 2026-09-07 sur firststudent.wd1, reparti seul) : raise_for_status | ||||
| 73 | + # dans base.get/post court-circuitait le retry et un seul 500 faisait | ||||
| 74 | + # échouer tout le sync. | ||||
| 75 | + last: Exception | None = None | ||||
| 76 | + resp = None | ||||
| 72 | for attempt in range(3): | 77 | for attempt in range(3): | ||
| 73 | − resp = (self.post if method == "post" else self.get)(url, **kwargs) | 78 | + try: | ||
| 79 | + resp = (self.post if method == "post" else self.get)(url, **kwargs) | ||||
| 80 | + except requests.HTTPError as exc: | ||||
| 81 | + if exc.response is None or exc.response.status_code < 500: | ||||
| 82 | + raise | ||||
| 83 | + last = exc | ||||
| 84 | + time.sleep(10 * (attempt + 1)) | ||||
| 85 | + continue | ||||
| 74 | try: | 86 | try: | ||
| 75 | return resp.json() | 87 | return resp.json() | ||
| 76 | except ValueError as exc: | 88 | except ValueError as exc: | ||
| 77 | last = exc | 89 | last = exc | ||
| 78 | time.sleep(10 * (attempt + 1)) | 90 | time.sleep(10 * (attempt + 1)) | ||
| 91 | + if resp is None: | ||||
| 92 | + raise RuntimeError(f"Workday 5xx après 3 essais : {last}") | ||||
| 79 | raise RuntimeError( | 93 | raise RuntimeError( | ||
| 80 | f"Workday non-JSON après 3 essais (HTTP {resp.status_code}, " | 94 | f"Workday non-JSON après 3 essais (HTTP {resp.status_code}, " | ||
| 81 | f"{resp.headers.get('content-type')}) : {last}") | 95 | f"{resp.headers.get('content-type')}) : {last}") | ||
| 82 | 96 | ||||