SPB Git forge

spb/job-ka

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

[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)

Simon-Pierre Boucher committed 15 days ago (Sep 9, 2026) parent c0f8946

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