新增独立 xiaobai-datahub 服务(SQLite WAL、Tushare 盘后发布、/v1 契约和管理后台),不改现站页面与数据链路。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
36 lines
1.0 KiB
Python
36 lines
1.0 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from collections.abc import Callable
|
|
from typing import TypeVar
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class RetryError(RuntimeError):
|
|
def __init__(self, message: str, attempts: int, last_error: BaseException | None = None) -> None:
|
|
super().__init__(message)
|
|
self.attempts = attempts
|
|
self.last_error = last_error
|
|
|
|
|
|
def retry_call(
|
|
fn: Callable[[], T],
|
|
attempts: int = 5,
|
|
base_delay: float = 0.2,
|
|
max_delay: float = 8.0,
|
|
sleeper: Callable[[float], None] = time.sleep,
|
|
retry_on: tuple[type[BaseException], ...] = (Exception,),
|
|
) -> T:
|
|
last: BaseException | None = None
|
|
for attempt in range(1, max(1, attempts) + 1):
|
|
try:
|
|
return fn()
|
|
except retry_on as exc:
|
|
last = exc
|
|
if attempt >= attempts:
|
|
break
|
|
delay = min(max_delay, base_delay * (2 ** (attempt - 1)))
|
|
sleeper(delay)
|
|
raise RetryError(f"retry exhausted after {attempts} attempts: {last}", attempts, last)
|