mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2025-06-02 09:26:12 +08:00
🐛 Bilibili调度新增回避策略 (#573)
* 🐛 将Bilibili的调度速度降低到60s * ✨ 增加回避策略 * ✨ 降低轮询间隔,增加回避次数,抛出阶段随机刷新 * ♻️ 更清晰的调度逻辑实现 * 🐛 兼容3.10的NamedTuple多继承 * ♻️ 合并重复逻辑 * ♻️ ctx放入fsm * 🐛 测试并调整逻辑 * 🐛 补全类型标注 * ♻️ 添加Condition和State.on_exit/on_enter,以实现自动状态切换 * ✅ 调整测试 * 🐛 私有化命名方法 * 🔊 调整补充日志 * 🐛 添加测试后清理 * ✏️ fix typing typo
This commit is contained in:
parent
9e6a35585a
commit
38f0edcd25
168
nonebot_bison/platform/bilibili/fsm.py
Normal file
168
nonebot_bison/platform/bilibili/fsm.py
Normal file
@ -0,0 +1,168 @@
|
||||
import sys
|
||||
import asyncio
|
||||
import inspect
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from collections.abc import Set as AbstractSet
|
||||
from collections.abc import Callable, Sequence, Awaitable, AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Protocol, TypeAlias, TypedDict, NamedTuple, runtime_checkable
|
||||
|
||||
from nonebot import logger
|
||||
|
||||
|
||||
class StrEnum(str, Enum): ...
|
||||
|
||||
|
||||
TAddon = TypeVar("TAddon", contravariant=True)
|
||||
TState = TypeVar("TState", contravariant=True)
|
||||
TEvent = TypeVar("TEvent", contravariant=True)
|
||||
TFSM = TypeVar("TFSM", bound="FSM", contravariant=True)
|
||||
|
||||
|
||||
class StateError(Exception): ...
|
||||
|
||||
|
||||
ActionReturn: TypeAlias = Any
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SupportStateOnExit(Generic[TAddon], Protocol):
|
||||
async def on_exit(self, addon: TAddon) -> None: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SupportStateOnEnter(Generic[TAddon], Protocol):
|
||||
async def on_enter(self, addon: TAddon) -> None: ...
|
||||
|
||||
|
||||
class Action(Generic[TState, TEvent, TAddon], Protocol):
|
||||
async def __call__(self, from_: TState, event: TEvent, to: TState, addon: TAddon) -> ActionReturn: ...
|
||||
|
||||
|
||||
ConditionFunc = Callable[[TAddon], Awaitable[bool]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Condition(Generic[TAddon]):
|
||||
call: ConditionFunc[TAddon]
|
||||
not_: bool = False
|
||||
|
||||
def __repr__(self):
|
||||
if inspect.isfunction(self.call) or inspect.isclass(self.call):
|
||||
call_str = self.call.__name__
|
||||
else:
|
||||
call_str = repr(self.call)
|
||||
return f"Condition(call={call_str})"
|
||||
|
||||
async def __call__(self, addon: TAddon) -> bool:
|
||||
return (await self.call(addon)) ^ self.not_
|
||||
|
||||
|
||||
# FIXME: Python 3.11+ 才支持 NamedTuple和TypedDict使用多继承添加泛型
|
||||
# 所以什么时候 drop 3.10(?
|
||||
if sys.version_info >= (3, 11) or TYPE_CHECKING:
|
||||
|
||||
class Transition(Generic[TState, TEvent, TAddon], NamedTuple):
|
||||
action: Action[TState, TEvent, TAddon]
|
||||
to: TState
|
||||
conditions: AbstractSet[Condition[TAddon]] | None = None
|
||||
|
||||
class StateGraph(Generic[TState, TEvent, TAddon], TypedDict):
|
||||
transitions: dict[
|
||||
TState,
|
||||
dict[
|
||||
TEvent,
|
||||
Transition[TState, TEvent, TAddon] | Sequence[Transition[TState, TEvent, TAddon]],
|
||||
],
|
||||
]
|
||||
initial: TState
|
||||
|
||||
else:
|
||||
|
||||
class Transition(NamedTuple):
|
||||
action: Action
|
||||
to: Any
|
||||
conditions: AbstractSet[Condition] | None = None
|
||||
|
||||
class StateGraph(TypedDict):
|
||||
transitions: dict[Any, dict[Any, Transition]]
|
||||
initial: Any
|
||||
|
||||
|
||||
class FSM(Generic[TState, TEvent, TAddon]):
|
||||
def __init__(self, graph: StateGraph[TState, TEvent, TAddon], addon: TAddon):
|
||||
self.started = False
|
||||
self.graph = graph
|
||||
self.current_state = graph["initial"]
|
||||
self.machine = self._core()
|
||||
self.addon = addon
|
||||
|
||||
async def _core(self) -> AsyncGenerator[ActionReturn, TEvent]:
|
||||
self.current_state = self.graph["initial"]
|
||||
res = None
|
||||
while True:
|
||||
event = yield res
|
||||
|
||||
if not self.started:
|
||||
raise StateError("FSM not started, please call start() first")
|
||||
|
||||
selected_transition = await self.cherry_pick(event)
|
||||
|
||||
logger.trace(f"exit state: {self.current_state}")
|
||||
if isinstance(self.current_state, SupportStateOnExit):
|
||||
logger.trace(f"do {self.current_state}.on_exit")
|
||||
await self.current_state.on_exit(self.addon)
|
||||
|
||||
logger.trace(f"do action: {selected_transition.action}")
|
||||
res = await selected_transition.action(self.current_state, event, selected_transition.to, self.addon)
|
||||
|
||||
logger.trace(f"enter state: {selected_transition.to}")
|
||||
self.current_state = selected_transition.to
|
||||
|
||||
if isinstance(self.current_state, SupportStateOnEnter):
|
||||
logger.trace(f"do {self.current_state}.on_enter")
|
||||
await self.current_state.on_enter(self.addon)
|
||||
|
||||
async def start(self):
|
||||
await anext(self.machine)
|
||||
self.started = True
|
||||
logger.trace(f"FSM started, initial state: {self.current_state}")
|
||||
|
||||
async def cherry_pick(self, event: TEvent) -> Transition[TState, TEvent, TAddon]:
|
||||
transitions = self.graph["transitions"][self.current_state].get(event)
|
||||
if transitions is None:
|
||||
raise StateError(f"Invalid event {event} in state {self.current_state}")
|
||||
|
||||
if isinstance(transitions, Transition):
|
||||
return transitions
|
||||
elif isinstance(transitions, Sequence):
|
||||
no_conds: list[Transition[TState, TEvent, TAddon]] = []
|
||||
for transition in transitions:
|
||||
if not transition.conditions:
|
||||
no_conds.append(transition)
|
||||
continue
|
||||
|
||||
values = await asyncio.gather(*(condition(self.addon) for condition in transition.conditions))
|
||||
|
||||
if all(values):
|
||||
logger.trace(f"conditions {transition.conditions} passed")
|
||||
return transition
|
||||
else:
|
||||
if no_conds:
|
||||
return no_conds.pop()
|
||||
else:
|
||||
raise StateError(f"Invalid event {event} in state {self.current_state}")
|
||||
else:
|
||||
raise TypeError("Invalid transition type: {transitions}, expected Transition or Sequence[Transition]")
|
||||
|
||||
async def emit(self, event: TEvent):
|
||||
return await self.machine.asend(event)
|
||||
|
||||
async def reset(self):
|
||||
await self.machine.aclose()
|
||||
self.started = False
|
||||
|
||||
del self.machine
|
||||
self.machine = self._core()
|
||||
|
||||
logger.trace("FSM closed")
|
@ -1,16 +1,13 @@
|
||||
import re
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from functools import wraps
|
||||
from enum import Enum, unique
|
||||
from typing import NamedTuple
|
||||
from typing_extensions import Self
|
||||
from typing import TypeVar, NamedTuple
|
||||
from collections.abc import Callable, Awaitable
|
||||
|
||||
from yarl import URL
|
||||
from nonebot import logger
|
||||
from httpx import AsyncClient
|
||||
from httpx import URL as HttpxURL
|
||||
from pydantic import Field, BaseModel, ValidationError
|
||||
from nonebot.compat import type_validate_json, type_validate_python
|
||||
|
||||
@ -19,6 +16,7 @@ from nonebot_bison.compat import model_rebuild
|
||||
from nonebot_bison.utils import text_similarity, decode_unicode_escapes
|
||||
from nonebot_bison.types import Tag, Target, RawPost, ApiError, Category
|
||||
|
||||
from .retry import ApiCode352Error, retry_for_352
|
||||
from .scheduler import BilibiliSite, BililiveSite, BiliBangumiSite
|
||||
from ..platform import NewMessage, StatusChange, CategoryNotSupport, CategoryNotRecognize
|
||||
from .models import (
|
||||
@ -38,38 +36,6 @@ from .models import (
|
||||
LiveRecommendMajor,
|
||||
)
|
||||
|
||||
B = TypeVar("B", bound="Bilibili")
|
||||
MAX_352_RETRY_COUNT = 3
|
||||
|
||||
|
||||
class ApiCode352Error(Exception):
|
||||
def __init__(self, url: HttpxURL) -> None:
|
||||
msg = f"api {url} error"
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
def retry_for_352(func: Callable[[B, Target], Awaitable[list[DynRawPost]]]):
|
||||
retried_times = 0
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(bls: B, *args, **kwargs):
|
||||
nonlocal retried_times
|
||||
try:
|
||||
res = await func(bls, *args, **kwargs)
|
||||
except ApiCode352Error as e:
|
||||
if retried_times < MAX_352_RETRY_COUNT:
|
||||
retried_times += 1
|
||||
logger.warning(f"获取动态列表失败,尝试刷新cookie: {retried_times}/{MAX_352_RETRY_COUNT}")
|
||||
await bls.ctx.refresh_client()
|
||||
return [] # 返回空列表
|
||||
else:
|
||||
raise ApiError(e.args[0])
|
||||
else:
|
||||
retried_times = 0
|
||||
return res
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class _ProcessedText(NamedTuple):
|
||||
title: str
|
||||
|
253
nonebot_bison/platform/bilibili/retry.py
Normal file
253
nonebot_bison/platform/bilibili/retry.py
Normal file
@ -0,0 +1,253 @@
|
||||
import random
|
||||
from enum import Enum
|
||||
from functools import wraps
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from collections.abc import Callable, Awaitable
|
||||
from typing_extensions import override, assert_never
|
||||
from typing import TYPE_CHECKING, Generic, Literal, TypeVar
|
||||
|
||||
from strenum import StrEnum
|
||||
from nonebot.log import logger
|
||||
from httpx import URL as HttpxURL
|
||||
|
||||
from nonebot_bison.types import Target
|
||||
|
||||
from .models import DynRawPost
|
||||
from .fsm import FSM, Condition, StateGraph, Transition, ActionReturn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .platforms import Bilibili
|
||||
|
||||
# 不用 TypeVar 的话,使用装饰器 Pyright 会报错
|
||||
TBilibili = TypeVar("TBilibili", bound="Bilibili")
|
||||
|
||||
|
||||
class ApiCode352Error(Exception):
|
||||
def __init__(self, url: HttpxURL) -> None:
|
||||
msg = f"api {url} error"
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
# see https://docs.python.org/zh-cn/3/howto/enum.html#dataclass-support
|
||||
@dataclass(frozen=True)
|
||||
class StateMixin:
|
||||
state: Literal["NORMAL", "REFRESH", "BACKOFF", "RAISE"]
|
||||
enter_func: Callable[["RetryAddon"], Awaitable[None]] | None = None
|
||||
exit_func: Callable[["RetryAddon"], Awaitable[None]] | None = None
|
||||
|
||||
async def on_enter(self, addon: "RetryAddon"):
|
||||
if self.enter_func:
|
||||
await self.enter_func(addon)
|
||||
|
||||
async def on_exit(self, addon: "RetryAddon"):
|
||||
if self.exit_func:
|
||||
await self.exit_func(addon)
|
||||
|
||||
def __str__(self):
|
||||
return f"<retry state {self.state}>"
|
||||
|
||||
|
||||
async def on_normal_enter(addon: "RetryAddon"):
|
||||
addon.reset_all()
|
||||
|
||||
|
||||
async def on_refresh_enter(addon: "RetryAddon"):
|
||||
addon.refresh_count += 1
|
||||
await addon.refresh_client()
|
||||
logger.warning(f"当前刷新次数: {addon.refresh_count}/{addon.max_refresh_count}")
|
||||
|
||||
|
||||
async def on_raise_enter(addon: "RetryAddon"):
|
||||
if random.random() < 0.1236:
|
||||
await addon.refresh_client()
|
||||
logger.warning("触发随机刷新")
|
||||
|
||||
|
||||
class RetryState(StateMixin, Enum):
|
||||
NROMAL = "NORMAL", on_normal_enter
|
||||
REFRESH = "REFRESH", on_refresh_enter
|
||||
BACKOFF = "BACKOFF"
|
||||
RAISE = "RAISE", on_raise_enter
|
||||
|
||||
def __str__(self):
|
||||
return f"<retry state {self.name}>"
|
||||
|
||||
|
||||
class RetryEvent(StrEnum):
|
||||
REQUEST_AND_SUCCESS = "request_and_success"
|
||||
REQUEST_AND_RAISE = "request_and_raise"
|
||||
IN_BACKOFF_TIME = "in_backoff_time"
|
||||
|
||||
def __str__(self):
|
||||
return f"<retry event {self.name}>"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RetryAddon(Generic[TBilibili]):
|
||||
bilibili_platform: TBilibili | None = None
|
||||
refresh_count: int = 0
|
||||
backoff_count: int = 0
|
||||
backoff_finish_time: datetime | None = None
|
||||
|
||||
@property
|
||||
def max_refresh_count(cls):
|
||||
return 3
|
||||
|
||||
@property
|
||||
def max_backoff_count(self):
|
||||
return 3
|
||||
|
||||
@property
|
||||
def backoff_timedelta(self):
|
||||
return timedelta(minutes=5)
|
||||
|
||||
async def refresh_client(self):
|
||||
if self.bilibili_platform:
|
||||
await self.bilibili_platform.ctx.refresh_client()
|
||||
else:
|
||||
raise RuntimeError("未设置 bilibili_platform")
|
||||
|
||||
def reset_all(self):
|
||||
self.refresh_count = 0
|
||||
self.backoff_count = 0
|
||||
self.backoff_finish_time = None
|
||||
|
||||
def record_backoff_finish_time(self):
|
||||
self.backoff_finish_time = (
|
||||
datetime.now()
|
||||
+ self.backoff_timedelta * self.backoff_count**2
|
||||
# + timedelta(seconds=random.randint(1, 60)) # jitter
|
||||
)
|
||||
logger.trace(f"set backoff finish time: {self.backoff_finish_time}")
|
||||
|
||||
def is_in_backoff_time(self):
|
||||
"""是否在指数回避时间内"""
|
||||
# 指数回避
|
||||
if not self.backoff_finish_time:
|
||||
logger.trace("not have backoff_finish_time")
|
||||
return False
|
||||
|
||||
logger.trace(f"now: {datetime.now()}, backoff_finish_time: {self.backoff_finish_time}")
|
||||
return datetime.now() < self.backoff_finish_time
|
||||
|
||||
|
||||
async def action_log(from_: RetryState, event: RetryEvent, to: RetryState, addon: RetryAddon) -> ActionReturn:
|
||||
logger.debug(f"{from_} -> {to}, by {event}")
|
||||
|
||||
|
||||
async def action_up_to_backoff(from_: RetryState, event: RetryEvent, to: RetryState, addon: RetryAddon) -> ActionReturn:
|
||||
addon.refresh_count = 0
|
||||
addon.backoff_count += 1
|
||||
addon.record_backoff_finish_time()
|
||||
logger.warning(
|
||||
f"当前已回避次数: {addon.backoff_count}/{addon.max_backoff_count}, 本次回避时间至 {addon.backoff_finish_time}"
|
||||
)
|
||||
|
||||
|
||||
async def action_back_to_refresh(
|
||||
from_: RetryState, event: RetryEvent, to: RetryState, addon: RetryAddon
|
||||
) -> ActionReturn:
|
||||
addon.backoff_finish_time = None
|
||||
logger.debug("back to refresh state")
|
||||
|
||||
|
||||
async def is_reach_max_refresh(addon: RetryAddon) -> bool:
|
||||
return addon.refresh_count > addon.max_refresh_count - 1
|
||||
|
||||
|
||||
async def is_reach_max_backoff(addon: RetryAddon) -> bool:
|
||||
return addon.backoff_count > addon.max_backoff_count - 1
|
||||
|
||||
|
||||
async def is_out_backoff_time(addon: RetryAddon) -> bool:
|
||||
return not addon.is_in_backoff_time()
|
||||
|
||||
|
||||
RETRY_GRAPH: StateGraph[RetryState, RetryEvent, RetryAddon] = {
|
||||
"transitions": {
|
||||
RetryState.NROMAL: {
|
||||
RetryEvent.REQUEST_AND_SUCCESS: Transition(action_log, RetryState.NROMAL),
|
||||
RetryEvent.REQUEST_AND_RAISE: Transition(action_log, RetryState.REFRESH),
|
||||
},
|
||||
RetryState.REFRESH: {
|
||||
RetryEvent.REQUEST_AND_SUCCESS: Transition(action_log, RetryState.NROMAL),
|
||||
RetryEvent.REQUEST_AND_RAISE: [
|
||||
Transition(action_log, RetryState.REFRESH),
|
||||
Transition(
|
||||
action_up_to_backoff,
|
||||
RetryState.BACKOFF,
|
||||
{
|
||||
Condition(is_reach_max_refresh),
|
||||
Condition(is_reach_max_backoff, not_=True),
|
||||
},
|
||||
),
|
||||
Transition(
|
||||
action_log,
|
||||
RetryState.RAISE,
|
||||
{
|
||||
Condition(is_reach_max_refresh),
|
||||
Condition(is_reach_max_backoff),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
RetryState.BACKOFF: {
|
||||
RetryEvent.IN_BACKOFF_TIME: [
|
||||
Transition(action_log, RetryState.BACKOFF),
|
||||
Transition(action_back_to_refresh, RetryState.REFRESH, {Condition(is_out_backoff_time)}),
|
||||
],
|
||||
},
|
||||
RetryState.RAISE: {
|
||||
RetryEvent.REQUEST_AND_SUCCESS: Transition(action_log, RetryState.NROMAL),
|
||||
RetryEvent.REQUEST_AND_RAISE: Transition(action_log, RetryState.RAISE),
|
||||
},
|
||||
},
|
||||
"initial": RetryState.NROMAL,
|
||||
}
|
||||
|
||||
|
||||
class RetryFSM(FSM[RetryState, RetryEvent, RetryAddon[TBilibili]]):
|
||||
@override
|
||||
async def start(self, bls: TBilibili):
|
||||
self.addon.bilibili_platform = bls
|
||||
await super().start()
|
||||
|
||||
@override
|
||||
async def reset(self):
|
||||
self.addon.reset_all()
|
||||
await super().reset()
|
||||
|
||||
|
||||
# FIXME: 拿出来是方便测试了,但全局单例会导致所有被装饰的函数共享状态,有待改进
|
||||
_retry_fsm = RetryFSM(RETRY_GRAPH, RetryAddon["Bilibili"]())
|
||||
|
||||
|
||||
def retry_for_352(api_func: Callable[[TBilibili, Target], Awaitable[list[DynRawPost]]]):
|
||||
# _retry_fsm = RetryFSM(RETRY_GRAPH, RetryAddon[TBilibili]())
|
||||
|
||||
@wraps(api_func)
|
||||
async def wrapper(bls: TBilibili, *args, **kwargs) -> list[DynRawPost]:
|
||||
# nonlocal _retry_fsm
|
||||
if not _retry_fsm.started:
|
||||
await _retry_fsm.start(bls)
|
||||
|
||||
match _retry_fsm.current_state:
|
||||
case RetryState.NROMAL | RetryState.REFRESH | RetryState.RAISE:
|
||||
try:
|
||||
res = await api_func(bls, *args, **kwargs)
|
||||
except ApiCode352Error:
|
||||
logger.error("API 352 错误")
|
||||
await _retry_fsm.emit(RetryEvent.REQUEST_AND_RAISE)
|
||||
return []
|
||||
else:
|
||||
await _retry_fsm.emit(RetryEvent.REQUEST_AND_SUCCESS)
|
||||
return res
|
||||
case RetryState.BACKOFF:
|
||||
logger.warning("回避中,不请求")
|
||||
await _retry_fsm.emit(RetryEvent.IN_BACKOFF_TIME)
|
||||
return []
|
||||
case _:
|
||||
assert_never(_retry_fsm.current_state)
|
||||
|
||||
return wrapper
|
@ -1,5 +1,6 @@
|
||||
from random import randint
|
||||
import random
|
||||
from typing_extensions import override
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from httpx import AsyncClient
|
||||
from nonebot import logger, require
|
||||
@ -8,9 +9,14 @@ from playwright.async_api import Cookie
|
||||
from nonebot_bison.types import Target
|
||||
from nonebot_bison.utils import Site, ClientManager, http_client
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .platforms import Bilibili
|
||||
|
||||
require("nonebot_plugin_htmlrender")
|
||||
from nonebot_plugin_htmlrender import get_browser
|
||||
|
||||
B = TypeVar("B", bound="Bilibili")
|
||||
|
||||
|
||||
class BilibiliClientManager(ClientManager):
|
||||
_client: AsyncClient
|
||||
@ -22,7 +28,7 @@ class BilibiliClientManager(ClientManager):
|
||||
async def _get_cookies(self) -> list[Cookie]:
|
||||
browser = await get_browser()
|
||||
async with await browser.new_page() as page:
|
||||
await page.goto(f"https://space.bilibili.com/{randint(1, 1000)}/dynamic")
|
||||
await page.goto(f"https://space.bilibili.com/{random.randint(1, 1000)}/dynamic")
|
||||
await page.wait_for_load_state("load")
|
||||
cookies = await page.context.cookies()
|
||||
|
||||
@ -62,7 +68,7 @@ class BilibiliClientManager(ClientManager):
|
||||
|
||||
class BilibiliSite(Site):
|
||||
name = "bilibili.com"
|
||||
schedule_setting = {"seconds": 30}
|
||||
schedule_setting = {"seconds": 50}
|
||||
schedule_type = "interval"
|
||||
client_mgr = BilibiliClientManager
|
||||
require_browser = True
|
||||
|
230
poetry.lock
generated
230
poetry.lock
generated
@ -1119,13 +1119,13 @@ reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@ -1195,13 +1195,13 @@ reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.111.0"
|
||||
version = "0.111.1"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.111.0-py3-none-any.whl", hash = "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0"},
|
||||
{file = "fastapi-0.111.0.tar.gz", hash = "sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7"},
|
||||
{file = "fastapi-0.111.1-py3-none-any.whl", hash = "sha256:4f51cfa25d72f9fbc3280832e84b32494cf186f50158d364a8765aabf22587bf"},
|
||||
{file = "fastapi-0.111.1.tar.gz", hash = "sha256:ddd1ac34cb1f76c2e2d7f8545a4bcb5463bce4834e81abf0b189e0c359ab2413"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@ -1209,12 +1209,10 @@ email_validator = ">=2.0.0"
|
||||
fastapi-cli = ">=0.0.2"
|
||||
httpx = ">=0.23.0"
|
||||
jinja2 = ">=2.11.2"
|
||||
orjson = ">=3.2.1"
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
python-multipart = ">=0.0.7"
|
||||
starlette = ">=0.37.2,<0.38.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
ujson = ">=4.0.1,<4.0.2 || >4.0.2,<4.1.0 || >4.1.0,<4.2.0 || >4.2.0,<4.3.0 || >4.3.0,<5.0.0 || >5.0.0,<5.1.0 || >5.1.0"
|
||||
uvicorn = {version = ">=0.12.0", extras = ["standard"]}
|
||||
|
||||
[package.extras]
|
||||
@ -1319,6 +1317,25 @@ type = "legacy"
|
||||
url = "https://pypi.org/simple"
|
||||
reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "freezegun"
|
||||
version = "1.5.1"
|
||||
description = "Let your Python tests travel through time"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"},
|
||||
{file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.7"
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.org/simple"
|
||||
reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
version = "1.4.1"
|
||||
@ -2876,71 +2893,6 @@ type = "legacy"
|
||||
url = "https://pypi.org/simple"
|
||||
reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.10.6"
|
||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"},
|
||||
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"},
|
||||
{file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"},
|
||||
{file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"},
|
||||
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"},
|
||||
{file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"},
|
||||
{file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"},
|
||||
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
|
||||
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
|
||||
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"},
|
||||
{file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"},
|
||||
{file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"},
|
||||
{file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"},
|
||||
{file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"},
|
||||
{file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"},
|
||||
{file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"},
|
||||
{file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.org/simple"
|
||||
reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
@ -4218,29 +4170,29 @@ reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"},
|
||||
{file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"},
|
||||
{file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"},
|
||||
{file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"},
|
||||
{file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"},
|
||||
{file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"},
|
||||
{file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"},
|
||||
{file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"},
|
||||
{file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
|
||||
{file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"},
|
||||
{file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"},
|
||||
{file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"},
|
||||
{file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"},
|
||||
{file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"},
|
||||
{file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"},
|
||||
{file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
@ -4685,98 +4637,6 @@ type = "legacy"
|
||||
url = "https://pypi.org/simple"
|
||||
reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "ujson"
|
||||
version = "5.10.0"
|
||||
description = "Ultra fast JSON encoder and decoder for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"},
|
||||
{file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"},
|
||||
]
|
||||
|
||||
[package.source]
|
||||
type = "legacy"
|
||||
url = "https://pypi.org/simple"
|
||||
reference = "offical-source"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.2"
|
||||
@ -5252,4 +5112,4 @@ yaml = []
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<4.0.0"
|
||||
content-hash = "f803840e2fcf6c5731e3bf842cf898125c464ce52ac4fbbb960258c026407110"
|
||||
content-hash = "d685583bbdceb8277e33e71357fb9c9e781c6388911cd09aa5a0153605ae38ae"
|
||||
|
@ -62,6 +62,7 @@ pytest-cov = ">=5.0.0,<6"
|
||||
pytest-mock = "^3.14.0"
|
||||
pytest-xdist = { extras = ["psutil"], version = "^3.6.1" }
|
||||
respx = ">=0.21.1,<0.22"
|
||||
freezegun = "^1.5.1"
|
||||
|
||||
[tool.poetry.group.docker]
|
||||
optional = true
|
||||
@ -126,6 +127,7 @@ plugin_dirs = ["extra_plugins"]
|
||||
builtin_plugins = ["echo"]
|
||||
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "basic"
|
||||
reportShadowedImports = false
|
||||
pythonVersion = "3.10"
|
||||
pythonPlatform = "All"
|
||||
|
@ -1,11 +1,15 @@
|
||||
import random
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import respx
|
||||
import pytest
|
||||
from loguru import logger
|
||||
from nonebug.app import App
|
||||
from httpx import URL, Response
|
||||
from freezegun import freeze_time
|
||||
from pytest_mock import MockerFixture
|
||||
from nonebot.compat import model_dump, type_validate_python
|
||||
|
||||
from .utils import get_json
|
||||
@ -55,12 +59,15 @@ def without_dynamic(app: App):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_for_352(app: App):
|
||||
async def test_retry_for_352(app: App, mocker: MockerFixture):
|
||||
from nonebot_bison.post import Post
|
||||
from nonebot_bison.types import Target, RawPost
|
||||
from nonebot_bison.platform.platform import NewMessage
|
||||
from nonebot_bison.types import Target, RawPost, ApiError
|
||||
from nonebot_bison.platform.bilibili.platforms import ApiCode352Error
|
||||
from nonebot_bison.utils import ClientManager, ProcessContext, http_client
|
||||
from nonebot_bison.platform.bilibili.platforms import MAX_352_RETRY_COUNT, ApiCode352Error, retry_for_352
|
||||
from nonebot_bison.platform.bilibili.retry import RetryAddon, RetryState, _retry_fsm, retry_for_352
|
||||
|
||||
mocker.patch.object(random, "random", return_value=0.0) # 稳定触发RAISE阶段的随缘刷新
|
||||
|
||||
now = time()
|
||||
raw_post_1 = {"id": 1, "text": "p1", "date": now, "tags": ["tag1"], "category": 1}
|
||||
@ -118,18 +125,26 @@ async def test_retry_for_352(app: App):
|
||||
refresh_client_call_count = 0
|
||||
|
||||
async def get_client(self, target: Target | None):
|
||||
logger.debug(f"call get_client: {target}, {datetime.now()}")
|
||||
logger.debug(f"times: {self.get_client_call_count} + 1")
|
||||
self.get_client_call_count += 1
|
||||
return http_client()
|
||||
|
||||
async def get_client_for_static(self):
|
||||
logger.debug(f"call get_client_for_static: {datetime.now()}")
|
||||
logger.debug(f"times: {self.get_client_for_static_call_count} + 1")
|
||||
self.get_client_for_static_call_count += 1
|
||||
return http_client()
|
||||
|
||||
async def get_query_name_client(self):
|
||||
logger.debug(f"call get_query_name_client: {datetime.now()}")
|
||||
logger.debug(f"times: {self.get_query_name_client_call_count} + 1")
|
||||
self.get_query_name_client_call_count += 1
|
||||
return http_client()
|
||||
|
||||
async def refresh_client(self):
|
||||
logger.debug(f"call refresh_client: {datetime.now()}")
|
||||
logger.debug(f"times: {self.refresh_client_call_count} + 1")
|
||||
self.refresh_client_call_count += 1
|
||||
|
||||
fakebili = MockPlatform(ProcessContext(MockClientManager()))
|
||||
@ -141,29 +156,83 @@ async def test_retry_for_352(app: App):
|
||||
assert client_mgr.refresh_client_call_count == 0
|
||||
|
||||
# 无异常
|
||||
res: list[dict[str, Any]] = await fakebili.get_sub_list(Target("1")) # type: ignore
|
||||
res: list[dict[str, Any]] = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert len(res) == 1
|
||||
assert res[0]["id"] == 1
|
||||
assert client_mgr.get_client_call_count == 1
|
||||
assert client_mgr.refresh_client_call_count == 0
|
||||
|
||||
res = await fakebili.get_sub_list(Target("1")) # type: ignore
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert len(res) == 2
|
||||
assert res[0]["id"] == 1
|
||||
assert res[1]["id"] == 2
|
||||
assert client_mgr.get_client_call_count == 2
|
||||
assert client_mgr.refresh_client_call_count == 0
|
||||
|
||||
# 有异常
|
||||
addon = RetryAddon()
|
||||
|
||||
# 异常直到最终报错
|
||||
test_state_list: list[RetryState] = [RetryState.NROMAL] + [RetryState.REFRESH] * addon.max_refresh_count
|
||||
for _ in range(addon.max_backoff_count):
|
||||
test_state_list += [RetryState.BACKOFF] * 2
|
||||
test_state_list += [RetryState.REFRESH] * addon.max_refresh_count
|
||||
test_state_list += [RetryState.RAISE] * 2
|
||||
|
||||
freeze_start = datetime(2024, 6, 19, 0, 0, 0, 0)
|
||||
timedelta_length = addon.backoff_timedelta
|
||||
|
||||
fakebili.set_raise352(True)
|
||||
for i in range(MAX_352_RETRY_COUNT):
|
||||
res1: list[dict[str, Any]] = await fakebili.get_sub_list(Target("1")) # type: ignore
|
||||
assert len(res1) == 0
|
||||
assert client_mgr.get_client_call_count == 3 + i
|
||||
assert client_mgr.refresh_client_call_count == i + 1
|
||||
# 超过最大重试次数,抛出异常
|
||||
with pytest.raises(ApiError):
|
||||
await fakebili.get_sub_list(Target("1"))
|
||||
|
||||
for state in test_state_list:
|
||||
logger.info(f"\n\nnow state should be {state}")
|
||||
assert _retry_fsm.current_state == state
|
||||
|
||||
with freeze_time(freeze_start):
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
|
||||
if state == RetryState.BACKOFF:
|
||||
freeze_start += timedelta_length * (_retry_fsm.addon.backoff_count + 1) ** 2
|
||||
|
||||
assert client_mgr.refresh_client_call_count == 4 * 3 + 3 # refresh + raise
|
||||
assert client_mgr.get_client_call_count == 2 + 4 * 3 + 3 # previous + refresh + raise
|
||||
|
||||
# 重置回正常状态
|
||||
fakebili.set_raise352(False)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert res
|
||||
|
||||
# REFRESH阶段中途正常返回
|
||||
test_state_list2 = [RetryState.NROMAL, RetryState.REFRESH, RetryState.NROMAL]
|
||||
for idx, _ in enumerate(test_state_list2):
|
||||
if idx == len(test_state_list2) - 1:
|
||||
fakebili.set_raise352(False)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert res
|
||||
else:
|
||||
fakebili.set_raise352(True)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
|
||||
fakebili.set_raise352(False)
|
||||
# BACKOFF阶段在回避时间中
|
||||
test_state_list3 = [RetryState.NROMAL] + [RetryState.REFRESH] * addon.max_refresh_count + [RetryState.BACKOFF]
|
||||
for idx, _ in enumerate(test_state_list3):
|
||||
if idx == len(test_state_list3) - 1:
|
||||
fakebili.set_raise352(False)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
else:
|
||||
fakebili.set_raise352(True)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
|
||||
# 测试重置
|
||||
await _retry_fsm.reset()
|
||||
|
||||
await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
|
||||
await _retry_fsm.reset()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
Loading…
x
Reference in New Issue
Block a user