|
1 | 1 | import functools |
2 | 2 | from functools import lru_cache |
| 3 | +import socket |
| 4 | +import time as _time |
3 | 5 |
|
4 | 6 | from curl_cffi import requests |
5 | 7 | from urllib.parse import urlsplit, urljoin |
|
9 | 11 | from frozendict import frozendict |
10 | 12 |
|
11 | 13 | from . import utils, cache |
| 14 | +from .config import YfConfig |
12 | 15 | import threading |
13 | 16 |
|
14 | 17 | from .exceptions import YFException, YFDataException, YFRateLimitError |
15 | 18 |
|
| 19 | + |
| 20 | +def _is_transient_error(exception): |
| 21 | + """Check if error is transient (network/timeout) and should be retried.""" |
| 22 | + if isinstance(exception, (TimeoutError, socket.error, OSError)): |
| 23 | + return True |
| 24 | + error_type_name = type(exception).__name__ |
| 25 | + transient_error_types = { |
| 26 | + 'Timeout', 'TimeoutError', 'ConnectionError', 'ConnectTimeout', |
| 27 | + 'ReadTimeout', 'ChunkedEncodingError', 'RemoteDisconnected', |
| 28 | + } |
| 29 | + return error_type_name in transient_error_types |
| 30 | + |
16 | 31 | cache_maxsize = 64 |
17 | 32 |
|
18 | 33 |
|
@@ -416,7 +431,15 @@ def _make_request(self, url, request_method, body=None, params=None, timeout=30) |
416 | 431 | if body: |
417 | 432 | request_args['json'] = body |
418 | 433 |
|
419 | | - response = request_method(**request_args) |
| 434 | + for attempt in range(YfConfig().retries + 1): |
| 435 | + try: |
| 436 | + response = request_method(**request_args) |
| 437 | + break |
| 438 | + except Exception as e: |
| 439 | + if _is_transient_error(e) and attempt < YfConfig().retries: |
| 440 | + _time.sleep(2 ** attempt) |
| 441 | + else: |
| 442 | + raise |
420 | 443 | utils.get_yf_logger().debug(f'response code={response.status_code}') |
421 | 444 | if response.status_code >= 400: |
422 | 445 | # Retry with other cookie strategy |
|
0 commit comments