|
1 | 1 | """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" |
2 | 2 |
|
3 | 3 | import asyncio |
| 4 | +import json |
4 | 5 | import random |
5 | 6 | import time |
6 | 7 | from datetime import datetime |
7 | 8 | from email.utils import parsedate_to_datetime |
8 | | -from typing import List, Optional |
| 9 | +from typing import Any, List, Optional |
9 | 10 |
|
10 | 11 | import httpx |
11 | 12 |
|
@@ -93,6 +94,79 @@ def _parse_retry_after_header(response: httpx.Response) -> Optional[int]: |
93 | 94 | return None |
94 | 95 |
|
95 | 96 |
|
| 97 | +TRANSIENT_STATUS_CODES = {408, 429} |
| 98 | +TRANSIENT_ERROR_CODES = {408, 429, 500, 502, 503, 504, 524, 529} |
| 99 | + |
| 100 | + |
| 101 | +def _matches_retry_status_code(response: httpx.Response, status_code: str) -> bool: |
| 102 | + if "X" in status_code.upper(): |
| 103 | + code_range = int(status_code[0]) |
| 104 | + status_major = response.status_code / 100 |
| 105 | + return code_range <= status_major < code_range + 1 |
| 106 | + |
| 107 | + return response.status_code == int(status_code) |
| 108 | + |
| 109 | + |
| 110 | +def _error_code_from_json_body(response: httpx.Response) -> Optional[int]: |
| 111 | + try: |
| 112 | + body: Any = response.json() |
| 113 | + except json.JSONDecodeError: |
| 114 | + return None |
| 115 | + |
| 116 | + if not isinstance(body, dict): |
| 117 | + return None |
| 118 | + |
| 119 | + error = body.get("error") |
| 120 | + if not isinstance(error, dict): |
| 121 | + return None |
| 122 | + |
| 123 | + code = error.get("code") |
| 124 | + if isinstance(code, int): |
| 125 | + return code |
| 126 | + if isinstance(code, str) and code.isdigit(): |
| 127 | + return int(code) |
| 128 | + |
| 129 | + return None |
| 130 | + |
| 131 | + |
| 132 | +def _is_retryable_response(response: httpx.Response, status_codes: List[str]) -> bool: |
| 133 | + if response.status_code in TRANSIENT_STATUS_CODES: |
| 134 | + return True |
| 135 | + |
| 136 | + for status_code in status_codes: |
| 137 | + if _matches_retry_status_code(response, status_code): |
| 138 | + return True |
| 139 | + |
| 140 | + if response.status_code != 400: |
| 141 | + return False |
| 142 | + |
| 143 | + if not response.is_closed: |
| 144 | + response.read() |
| 145 | + |
| 146 | + inner_code = _error_code_from_json_body(response) |
| 147 | + return inner_code in TRANSIENT_ERROR_CODES |
| 148 | + |
| 149 | + |
| 150 | +async def _is_retryable_response_async( |
| 151 | + response: httpx.Response, status_codes: List[str] |
| 152 | +) -> bool: |
| 153 | + if response.status_code in TRANSIENT_STATUS_CODES: |
| 154 | + return True |
| 155 | + |
| 156 | + for status_code in status_codes: |
| 157 | + if _matches_retry_status_code(response, status_code): |
| 158 | + return True |
| 159 | + |
| 160 | + if response.status_code != 400: |
| 161 | + return False |
| 162 | + |
| 163 | + if not response.is_closed: |
| 164 | + await response.aread() |
| 165 | + |
| 166 | + inner_code = _error_code_from_json_body(response) |
| 167 | + return inner_code in TRANSIENT_ERROR_CODES |
| 168 | + |
| 169 | + |
96 | 170 | def _get_sleep_interval( |
97 | 171 | exception: Exception, |
98 | 172 | initial_interval: int, |
@@ -131,19 +205,8 @@ def do_request() -> httpx.Response: |
131 | 205 | try: |
132 | 206 | res = func() |
133 | 207 |
|
134 | | - for code in retries.status_codes: |
135 | | - if "X" in code.upper(): |
136 | | - code_range = int(code[0]) |
137 | | - |
138 | | - status_major = res.status_code / 100 |
139 | | - |
140 | | - if code_range <= status_major < code_range + 1: |
141 | | - raise TemporaryError(res) |
142 | | - else: |
143 | | - parsed_code = int(code) |
144 | | - |
145 | | - if res.status_code == parsed_code: |
146 | | - raise TemporaryError(res) |
| 208 | + if _is_retryable_response(res, retries.status_codes): |
| 209 | + raise TemporaryError(res) |
147 | 210 | except httpx.ConnectError as exception: |
148 | 211 | if retries.config.retry_connection_errors: |
149 | 212 | raise |
@@ -180,19 +243,8 @@ async def do_request() -> httpx.Response: |
180 | 243 | try: |
181 | 244 | res = await func() |
182 | 245 |
|
183 | | - for code in retries.status_codes: |
184 | | - if "X" in code.upper(): |
185 | | - code_range = int(code[0]) |
186 | | - |
187 | | - status_major = res.status_code / 100 |
188 | | - |
189 | | - if code_range <= status_major < code_range + 1: |
190 | | - raise TemporaryError(res) |
191 | | - else: |
192 | | - parsed_code = int(code) |
193 | | - |
194 | | - if res.status_code == parsed_code: |
195 | | - raise TemporaryError(res) |
| 246 | + if await _is_retryable_response_async(res, retries.status_codes): |
| 247 | + raise TemporaryError(res) |
196 | 248 | except httpx.ConnectError as exception: |
197 | 249 | if retries.config.retry_connection_errors: |
198 | 250 | raise |
|
0 commit comments