SPB Git forge

spb/job-ka

Public
229commits 1branches 0releases
38.1 MBsize
maindefault branch
2 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%

[ka6] fix connecteur bento_sushi: reprise sur réponses ADP 200 non-JSON (retry+backoff, erreur diagnosticable)

Simon-Pierre Boucher committed 25 days ago (Aug 30, 2026) parent 4dcbd45

1 changed file +23 −6

modified jobka/connectors/adp.py +23 −6
@@ -5,7 +5,7 @@
5 5 # Fichier : jobka/connectors/adp.py
6 6 # Rôle : Classe de plateforme ADP Workforce Now (centre de carrières) —
7 7 # API JSON publique job-requisitions, un employeur = une sous-classe
8 −# Créé : 2026-08-18 Modifié : 2026-08-25
8 +# Créé : 2026-08-18 Modifié : 2026-08-30
9 9 # =============================================================================
10 10 """Plateforme ADP Workforce Now (workforcenow.adp.com).
11 11
@@ -21,6 +21,7 @@ API JSON publique du centre de carrières :
21 21 from __future__ import annotations
22 22
23 23 import os
24 +import time
24 25
25 26 from ..schema import JobPosting, clean_html, is_quebec_location
26 27 from .base import BaseConnector
@@ -50,6 +51,24 @@ class ADPWorkforceNowConnector(BaseConnector):
50 51 FORCE_CITY = "" # employeurs mono-site QC dont le babillard n'expose
51 52 # aucune requisitionLocation : ville imposée
52 53
54 + def _get_json(self, url: str, params: dict) -> dict:
55 + # ADP renvoie parfois 200 avec un corps vide/HTML (fenêtres de
56 + # maintenance nocturnes constatées les 29-30 août 2026) : on retente
57 + # avec backoff avant d'abandonner, et l'erreur finale nomme le statut
58 + # HTTP + content-type pour que sync_log soit diagnostiquable.
59 + last: ValueError | None = None
60 + for attempt in range(3):
61 + resp = self.get(url, params=params,
62 + headers={"Accept": "application/json"})
63 + try:
64 + return resp.json()
65 + except ValueError as exc:
66 + last = exc
67 + time.sleep(10 * (attempt + 1))
68 + raise RuntimeError(
69 + f"ADP non-JSON après 3 essais (HTTP {resp.status_code}, "
70 + f"{resp.headers.get('content-type')}) : {last}")
71 +
53 72 def _params(self, extra: dict | None = None) -> dict:
54 73 p = {"cid": self.CID, "ccId": self.CCID, "lang": self.LANG,
55 74 "locale": self.LANG}
@@ -81,8 +100,7 @@ class ADPWorkforceNowConnector(BaseConnector):
81 100 for l in locations)
82 101
83 102 def _fetch_detail(self, item_id: str) -> dict:
84 − data = self.get(f"{_API}/{item_id}", params=self._params(),
85 − headers={"Accept": "application/json"}).json()
103 + data = self._get_json(f"{_API}/{item_id}", params=self._params())
86 104 return {"description": clean_html(
87 105 data.get("requisitionDescription") or "")}
88 106
@@ -91,9 +109,8 @@ class ADPWorkforceNowConnector(BaseConnector):
91 109 details_used = 0
92 110 skip = 0
93 111 for _ in range(self.max_pages):
94 − data = self.get(_API, params=self._params(
95 − {"$top": str(PAGE_SIZE), "$skip": str(skip)}),
96 − headers={"Accept": "application/json"}).json()
112 + data = self._get_json(_API, params=self._params(
113 + {"$top": str(PAGE_SIZE), "$skip": str(skip)}))
97 114 reqs = data.get("jobRequisitions") or []
98 115 if not reqs and skip == 0 and self.LANG != "en_US":
99 116 # certains centres de carrières ne répondent qu'en anglais
100 117