From 2ea23d9bce3f8c2ed169d28cc4df66084edab6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Fri, 7 Aug 2026 09:51:48 +0800 Subject: [PATCH] Classify the transient request errors in one place Co-Authored-By: Claude Opus 5 (1M context) --- .../commands/fetch_artists.py | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/tools/src/pop_fem_audit_tools/commands/fetch_artists.py b/tools/src/pop_fem_audit_tools/commands/fetch_artists.py index a069741..6c2cb54 100644 --- a/tools/src/pop_fem_audit_tools/commands/fetch_artists.py +++ b/tools/src/pop_fem_audit_tools/commands/fetch_artists.py @@ -641,32 +641,44 @@ class ArtistFetcher: time.sleep(SLEEP_SECONDS) self.__sent += 1 attempt: int = 1 - reason: str - cause: BaseException + reason: str | None while True: try: with urllib.request.urlopen( request, timeout=timeout) as response: return response.read() - except urllib.error.HTTPError as error: - if error.code not in RETRY_STATUSES: + except (urllib.error.HTTPError, TimeoutError, + urllib.error.URLError) as error: + reason = self.__retry_reason(error) + if reason is None: raise - reason = str(error) - cause = error - except TimeoutError as error: - reason = str(error) or "timed out" - cause = error - except urllib.error.URLError as error: - if not isinstance(error.reason, TimeoutError): - raise - reason = str(error.reason) or "timed out" - cause = error - if attempt >= MAX_ATTEMPTS: - raise RetryExhausted( - f"retries exhausted ({reason})") from cause + if attempt >= MAX_ATTEMPTS: + raise RetryExhausted( + f"retries exhausted ({reason})") from error time.sleep(RETRY_SECONDS * attempt) attempt += 1 + @staticmethod + def __retry_reason( + error: urllib.error.URLError | TimeoutError) \ + -> str | None: + """Tell whether an error is transient, and why. + + :param error: The HTTP or network error raised by the + request. + :return: The reason to report on the error, or None when + the error is not transient and must not be retried. + """ + if isinstance(error, urllib.error.HTTPError): + if error.code not in RETRY_STATUSES: + return None + return str(error) + if isinstance(error, TimeoutError): + return str(error) or "timed out" + if not isinstance(error.reason, TimeoutError): + return None + return str(error.reason) or "timed out" + @staticmethod def __literals(texts: Sequence[str]) -> str: """Build the SPARQL literals of texts at both languages.