SPB Git

spb/job-ka

Public

[ka6] fix connecteur gsknch: reprise sur réponses Workday 200 non-JSON (fenêtres de maintenance nocturnes ~03h) — retry+backoff _req_json dans la plateforme workday, erreur diagnosticable, 11 offres retrouvées

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Aug 31, 2026) parent 95df645 Browse files

1 changed file +26 −5

Unified Split
modified jobka/connectors/workday.py +26 −5
@@ -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-08-17 8+# Créé : 2026-08-17 Modifié : 2026-08-31
9 # ============================================================================= 9 # =============================================================================
10 """Plateforme Workday. 10 """Plateforme Workday.
11 11
@@ -28,6 +28,7 @@ from __future__ import annotations
28 import hashlib 28 import hashlib
29 import os 29 import os
30 import re 30 import re
31+import time
31 32
32 from ..schema import JobPosting, clean_html, is_quebec_location 33 from ..schema import JobPosting, clean_html, is_quebec_location
33 from .base import BaseConnector 34 from .base import BaseConnector
@@ -54,6 +55,24 @@ class WorkdayConnector(BaseConnector):
54 def _base(self) -> str: 55 def _base(self) -> str:
55 return f"https://{self.TENANT}.{self.HOST}.myworkdayjobs.com" 56 return f"https://{self.TENANT}.{self.HOST}.myworkdayjobs.com"
56 57
58+ def _req_json(self, method: str, url: str, **kwargs) -> dict:
59+ # Workday renvoie parfois 200 avec un corps vide/HTML (fenêtres de
60+ # maintenance nocturnes ~03 h EDT, constatées les 29 et 31 août 2026
61+ # sur de nombreux tenants) : on retente avec backoff avant
62+ # d'abandonner, et l'erreur finale nomme le statut HTTP +
63+ # content-type pour que sync_log soit diagnostiquable.
64+ last: ValueError | None = None
65+ for attempt in range(3):
66+ resp = (self.post if method == "post" else self.get)(url, **kwargs)
67+ try:
68+ return resp.json()
69+ except ValueError as exc:
70+ last = exc
71+ time.sleep(10 * (attempt + 1))
72+ raise RuntimeError(
73+ f"Workday non-JSON après 3 essais (HTTP {resp.status_code}, "
74+ f"{resp.headers.get('content-type')}) : {last}")
75+
57 def _keep(self, item: dict) -> bool: 76 def _keep(self, item: dict) -> bool:
58 """Crochet de filtrage d'une ligne de liste (défaut : lieu québécois).""" 77 """Crochet de filtrage d'une ligne de liste (défaut : lieu québécois)."""
59 if not self.quebec_only: 78 if not self.quebec_only:
@@ -121,9 +140,10 @@ class WorkdayConnector(BaseConnector):
121 total = 0 140 total = 0
122 first = True 141 first = True
123 for _ in range(self.max_pages): 142 for _ in range(self.max_pages):
124− data = self.post(url, json={"appliedFacets": applied, 143+ data = self._req_json("post", url,
144+ json={"appliedFacets": applied,
125 "limit": PAGE_SIZE, 145 "limit": PAGE_SIZE,
126− "offset": offset, "searchText": ""}).json() 146+ "offset": offset, "searchText": ""})
127 if first: 147 if first:
128 first = False 148 first = False
129 self._first_facets = data.get("facets") or [] 149 self._first_facets = data.get("facets") or []
@@ -169,8 +189,9 @@ class WorkdayConnector(BaseConnector):
169 continue # facette indisponible : on n'invente rien 189 continue # facette indisponible : on n'invente rien
170 190
171 def _fetch_detail(self, external_path: str) -> dict: 191 def _fetch_detail(self, external_path: str) -> dict:
172− data = self.get(f"{self._base}/wday/cxs/{self.TENANT}/{self.SITE}" 192+ data = self._req_json("get",
173− f"{external_path}").json() 193+ f"{self._base}/wday/cxs/{self.TENANT}"
194+ f"/{self.SITE}{external_path}")
174 info = data.get("jobPostingInfo") or {} 195 info = data.get("jobPostingInfo") or {}
175 return { 196 return {
176 "description_html": info.get("jobDescription") or "", 197 "description_html": info.get("jobDescription") or "",
177 198
Menu

Theme