[ka6] fix connecteur bell_textron: retry avec backoff sur 5xx transitoire dans la plateforme taleo — un 500 isolé de l'API searchjobs (constaté 2026-09-08 23h29 sur textron, reparti seul) faisait échouer tout le sync car raise_for_status dans base.get/post n'a aucun retry; nouveau _req couvrant amorce/liste/détail, 3 essais backoff 10-20-30 s, les 4xx restent des échecs immédiats (même correctif que workday b5b4be3); sync rejoué: 16 offres (médiane 16.0)
1 changed file +26 −5
modified
jobka/connectors/taleo.py
+26 −5
@@ -5,7 +5,7 @@ | ||
| 5 | 5 | # Fichier : jobka/connectors/taleo.py |
| 6 | 6 | # Rôle : Classe de plateforme Oracle Taleo Enterprise (careersection REST) |
| 7 | 7 | # — un employeur = une sous-classe (HOST, SECTION, EMPLOYER) |
| 8 | −# Créé : 2026-08-18 Modifié : 2026-08-18 | |
| 8 | +# Créé : 2026-08-18 Modifié : 2026-09-09 | |
| 9 | 9 | # ============================================================================= |
| 10 | 10 | """Plateforme Oracle Taleo Enterprise. |
| 11 | 11 | |
@@ -30,6 +30,9 @@ import hashlib | ||
| 30 | 30 | import json |
| 31 | 31 | import os |
| 32 | 32 | import re |
| 33 | +import time | |
| 34 | + | |
| 35 | +import requests | |
| 33 | 36 | |
| 34 | 37 | from ..normalize import parse_date |
| 35 | 38 | from ..schema import JobPosting, clean_html, is_quebec_location |
@@ -66,12 +69,30 @@ class TaleoConnector(BaseConnector): | ||
| 66 | 69 | def _base(self) -> str: |
| 67 | 70 | return f"https://{self.HOST}.taleo.net/careersection" |
| 68 | 71 | |
| 72 | + def _req(self, method: str, url: str, **kwargs) -> requests.Response: | |
| 73 | + # Les frontaux Taleo renvoient à l'occasion un 5xx isolé qui repart | |
| 74 | + # seul (500 constaté le 2026-09-08 23h29 sur textron/searchjobs) : | |
| 75 | + # raise_for_status dans base.get/post faisait échouer tout le sync. | |
| 76 | + # On retente avec backoff sur 5xx ; les 4xx restent des échecs | |
| 77 | + # immédiats (même correctif que la plateforme Workday, b5b4be3). | |
| 78 | + last: requests.HTTPError | None = None | |
| 79 | + for attempt in range(3): | |
| 80 | + try: | |
| 81 | + return (self.post if method == "post" else self.get)( | |
| 82 | + url, **kwargs) | |
| 83 | + except requests.HTTPError as exc: | |
| 84 | + if exc.response is None or exc.response.status_code < 500: | |
| 85 | + raise | |
| 86 | + last = exc | |
| 87 | + time.sleep(10 * (attempt + 1)) | |
| 88 | + raise RuntimeError(f"Taleo 5xx après 3 essais : {last}") | |
| 89 | + | |
| 69 | 90 | def _careers_url(self) -> str: |
| 70 | 91 | return f"{self._base}/{self.SECTION}/jobsearch.ftl?lang={self.LANG}" |
| 71 | 92 | |
| 72 | 93 | def _bootstrap(self) -> str: |
| 73 | 94 | """Cookies de session + numéro de portail.""" |
| 74 | − html = self.get(self._careers_url()).text | |
| 95 | + html = self._req("get", self._careers_url()).text | |
| 75 | 96 | if self.PORTAL: |
| 76 | 97 | return str(self.PORTAL) |
| 77 | 98 | m = re.search(r"portal=(\d+)", html) |
@@ -98,8 +119,8 @@ class TaleoConnector(BaseConnector): | ||
| 98 | 119 | return any(is_quebec_location(l.replace("-", " ")) for l in locations) |
| 99 | 120 | |
| 100 | 121 | def _fetch_detail(self, contest_no: str) -> dict: |
| 101 | − html = self.get(f"{self._base}/{self.SECTION}/jobdetail.ftl", | |
| 102 | − params={"job": contest_no, "lang": self.LANG}).text | |
| 122 | + html = self._req("get", f"{self._base}/{self.SECTION}/jobdetail.ftl", | |
| 123 | + params={"job": contest_no, "lang": self.LANG}).text | |
| 103 | 124 | # description : contenu des blocs « requisitionDescriptionInterface » |
| 104 | 125 | # (rendus côté serveur), sinon la zone principale de la page |
| 105 | 126 | chunks = re.findall( |
@@ -133,7 +154,7 @@ class TaleoConnector(BaseConnector): | ||
| 133 | 154 | page = 1 |
| 134 | 155 | seen: set[str] = set() |
| 135 | 156 | while page <= self.max_pages: |
| 136 | − data = self.post(url, json={**body_base, "pageNo": page}, | |
| 157 | + data = self._req("post", url, json={**body_base, "pageNo": page}, | |
| 137 | 158 | headers={"tz": "GMT-04:00"}).json() |
| 138 | 159 | reqs = data.get("requisitionList") or [] |
| 139 | 160 | if not reqs and page == 1 and self.LANG != "en": |
| 140 | 161 | |