Skip to content

Commit bc39c24

Browse files
authored
fix: add retry logic and improve error messages in OpenAPI update scripts (#76)
- Add 3 retries with 5s delay for HTTP failures in both scripts - Set explicit 30s timeout for HTTP requests - Include exception type in error messages to prevent empty error output - Catch specific httpx exceptions for targeted retry behavior
1 parent 57e86d2 commit bc39c24

2 files changed

Lines changed: 45 additions & 27 deletions

File tree

scripts/update_idp_openapi_spec.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,31 @@ def compute_spec_hash(spec_data: dict) -> str:
2929
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
3030

3131

32-
async def download_idp_openapi_spec() -> dict:
32+
async def download_idp_openapi_spec(max_retries: int = 3, retry_delay: float = 5.0) -> dict:
3333
"""Download the IdP OpenAPI spec JSON from Authlete IdP API."""
3434
print(f"Downloading IdP OpenAPI spec from {AUTHLETE_IDP_SPEC_URL}...")
3535

36-
async with httpx.AsyncClient(follow_redirects=True) as client:
37-
response = await client.get(AUTHLETE_IDP_SPEC_URL)
38-
response.raise_for_status()
36+
last_error: Exception | None = None
37+
for attempt in range(1, max_retries + 1):
38+
try:
39+
async with httpx.AsyncClient(follow_redirects=True, timeout=30.0) as client:
40+
response = await client.get(AUTHLETE_IDP_SPEC_URL)
41+
response.raise_for_status()
3942

40-
print(f"Downloaded {len(response.content)} bytes")
43+
print(f"Downloaded {len(response.content)} bytes")
4144

42-
# Parse JSON directly
43-
try:
44-
spec_data = response.json()
45-
return spec_data
46-
except ValueError as exc:
47-
raise ValueError(f"Failed to parse JSON response: {exc}") from exc
45+
spec_data = response.json()
46+
return spec_data
47+
except (httpx.HTTPStatusError, httpx.RequestError, ValueError) as exc:
48+
last_error = exc
49+
print(f"Attempt {attempt}/{max_retries} failed: {type(exc).__name__}: {exc}")
50+
if attempt < max_retries:
51+
print(f"Retrying in {retry_delay}s...")
52+
await asyncio.sleep(retry_delay)
53+
54+
raise RuntimeError(
55+
f"Failed to download IdP OpenAPI spec after {max_retries} attempts: {type(last_error).__name__}: {last_error}"
56+
)
4857

4958

5059
def save_idp_openapi_spec(spec_data: dict) -> None:
@@ -165,7 +174,7 @@ async def main():
165174
)
166175

167176
except Exception as e:
168-
print(f"ERROR: Failed to update IdP OpenAPI spec: {e}")
177+
print(f"ERROR: Failed to update IdP OpenAPI spec: {type(e).__name__}: {e}")
169178
sys.exit(1)
170179

171180

scripts/update_openapi_spec.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,34 @@
1818
OPENAPI_SPEC_FILE = RESOURCES_DIR / "openapi-spec.json"
1919

2020

21-
async def download_openapi_spec() -> dict:
21+
async def download_openapi_spec(max_retries: int = 3, retry_delay: float = 5.0) -> dict:
2222
"""Download the OpenAPI spec (YAML format) from Authlete docs."""
2323
print(f"Downloading OpenAPI spec from {AUTHLETE_SPEC_URL}...")
2424

25-
async with httpx.AsyncClient(follow_redirects=True) as client:
26-
response = await client.get(AUTHLETE_SPEC_URL)
27-
response.raise_for_status()
25+
last_error: Exception | None = None
26+
for attempt in range(1, max_retries + 1):
27+
try:
28+
async with httpx.AsyncClient(follow_redirects=True, timeout=30.0) as client:
29+
response = await client.get(AUTHLETE_SPEC_URL)
30+
response.raise_for_status()
2831

29-
print(f"Downloaded {len(response.content)} bytes")
30-
content_type = response.headers.get("content-type", "").lower()
31-
print(f"Content-Type: {content_type}")
32+
print(f"Downloaded {len(response.content)} bytes")
33+
content_type = response.headers.get("content-type", "").lower()
34+
print(f"Content-Type: {content_type}")
3235

33-
# Parse as YAML
34-
try:
35-
spec_data = yaml.safe_load(response.text)
36-
print("Parsed as YAML format")
37-
return spec_data
38-
except yaml.YAMLError as e:
39-
raise ValueError(f"Failed to parse YAML response: {e}")
36+
spec_data = yaml.safe_load(response.text)
37+
print("Parsed as YAML format")
38+
return spec_data
39+
except (httpx.HTTPStatusError, httpx.RequestError, yaml.YAMLError) as exc:
40+
last_error = exc
41+
print(f"Attempt {attempt}/{max_retries} failed: {type(exc).__name__}: {exc}")
42+
if attempt < max_retries:
43+
print(f"Retrying in {retry_delay}s...")
44+
await asyncio.sleep(retry_delay)
45+
46+
raise RuntimeError(
47+
f"Failed to download OpenAPI spec after {max_retries} attempts: {type(last_error).__name__}: {last_error}"
48+
)
4049

4150

4251
def save_openapi_spec(spec_data: dict) -> None:
@@ -123,7 +132,7 @@ async def main():
123132
print("OpenAPI spec update completed successfully!")
124133

125134
except Exception as e:
126-
print(f"ERROR: Failed to update OpenAPI spec: {e}")
135+
print(f"ERROR: Failed to update OpenAPI spec: {type(e).__name__}: {e}")
127136
sys.exit(1)
128137

129138

0 commit comments

Comments
 (0)