style: 引入 ruff,474 项告警清零
配置见 pyproject.toml:
- line-length=100:贴着现有代码定。若用默认的 88 会重排 122 行,
噪音淹没真正有价值的修复。
- 启用 E/W/F/I/UP/B/C4/SIM/RET/PERF/RUF;**不**启用 PTH。因为本项目的路径
多是"字符串身份"——要塞进 subprocess 命令行、写进 JSON 存档、打印给用户,
全换 pathlib 只是包一层再拆一层,换来 70 处纯风格改动。
- 忽略 RUF001/002/003:中文全角标点是正确写法,不是"歧义字符"。
- 忽略 PERF203:剩下两处都是"逐项容错"语义(逐个牧场存档要立刻上报失败、
收获全部要跳过失败地块)。把 try 提到循环外会毁掉"一项失败不影响其它项"
这个行为,是错的优化。
自动修复 358 项:Dict/List/Optional 换成 PEP 585/604 写法、导入排序、
失效的 noqa、空占位 f-string 等。
手工修复的(这些是真问题,不是风格偏好):
- B904 x9:except 中 raise 补异常链。刻意区分——网络/IO 失败用 from exc 保留
根因;参数解析失败用 from None,因为底层 ValueError 对玩家只是噪音。
- RUF012 x2:StateView 的 plots/animals 原本是**类级共享可变列表**,
改成在 __init__ 里初始化(apply({}) 会提前返回,所以必须显式摆好)。
- F841:TUI 里一个赋值后再没用过的死变量。
- SIM105 x11:try/except/pass 统一改成 contextlib.suppress。
- SIM115:日志句柄改用 with。Popen 已把 fd 复制给子进程,父进程可安全关闭。
- E501 x7:拆超长行;main.py 那处顺手提取变量,避免超长嵌套表达式。
- E741:把 l/r 改成 left_line/right_line。
- 其余:RUF046 / PERF401 / RET504 / RUF005 / UP031 / B007。
ruff format 重排 24 个文件。
验证:97 项测试全绿。
This commit is contained in:
1 parent
c7c8a6308a
commit
fcd4cb4649
25 files changed
+1204
-794
No files matched your search
+132
-105
@@ -27,6 +27,7 @@ TUI 原本直接拿着 ``FarmEngine`` 对象,现在游戏跑在服务器进程
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
@@ -34,12 +35,13 @@ import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections import deque
|
||||
from concurrent.futures import Future, TimeoutError as FutureTimeout
|
||||
from typing import Any, Callable, Deque, Dict, List, Optional
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import Future
|
||||
from concurrent.futures import TimeoutError as FutureTimeout
|
||||
from typing import Any
|
||||
|
||||
from . import config
|
||||
from . import config, net
|
||||
from .engine import GameError
|
||||
from . import net
|
||||
|
||||
DEFAULT_ENDPOINT = "http://127.0.0.1:8765"
|
||||
LOG_KEEP = 200
|
||||
@@ -52,7 +54,7 @@ class PlotView:
|
||||
|
||||
__slots__ = ("_d",)
|
||||
|
||||
def __init__(self, data: Dict[str, Any]) -> None:
|
||||
def __init__(self, data: dict[str, Any]) -> None:
|
||||
self._d = data
|
||||
|
||||
@property
|
||||
@@ -64,7 +66,7 @@ class PlotView:
|
||||
return int(self._d.get("position", self.index + 1))
|
||||
|
||||
@property
|
||||
def crop(self) -> Optional[str]:
|
||||
def crop(self) -> str | None:
|
||||
return self._d.get("crop")
|
||||
|
||||
@property
|
||||
@@ -99,7 +101,7 @@ class PlotView:
|
||||
def stage(self) -> str:
|
||||
return self._d.get("stage", "")
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return dict(self._d)
|
||||
|
||||
|
||||
@@ -108,7 +110,7 @@ class AnimalView:
|
||||
|
||||
__slots__ = ("_d",)
|
||||
|
||||
def __init__(self, data: Dict[str, Any]) -> None:
|
||||
def __init__(self, data: dict[str, Any]) -> None:
|
||||
self._d = data
|
||||
|
||||
@property
|
||||
@@ -155,18 +157,18 @@ class AnimalView:
|
||||
def is_sick(self) -> bool:
|
||||
return bool(self._d.get("is_sick"))
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return dict(self._d)
|
||||
|
||||
|
||||
class StateView:
|
||||
"""整局游戏的只读视图。刻意保留 ``to_dict()``,方便直接落盘或转发。"""
|
||||
|
||||
def __init__(self, data: Optional[Dict[str, Any]] = None) -> None:
|
||||
self._d: Dict[str, Any] = {}
|
||||
def __init__(self, data: dict[str, Any] | None = None) -> None:
|
||||
self._d: dict[str, Any] = {}
|
||||
self.apply(data or {})
|
||||
|
||||
def apply(self, data: Dict[str, Any]) -> None:
|
||||
def apply(self, data: dict[str, Any]) -> None:
|
||||
"""用新数据刷新内部视图。
|
||||
|
||||
服务器会发两种东西:**完整快照**(带 plots/animals)和**精简状态**
|
||||
@@ -210,7 +212,7 @@ class StateView:
|
||||
return int(self._d.get("feed", 0))
|
||||
|
||||
@property
|
||||
def market(self) -> Dict[str, float]:
|
||||
def market(self) -> dict[str, float]:
|
||||
return self._d.get("market", {})
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
@@ -219,21 +221,24 @@ class StateView:
|
||||
def __getitem__(self, key: str) -> Any:
|
||||
return self._d[key]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return dict(self._d)
|
||||
|
||||
# 初始化占位,避免属性访问落空
|
||||
plots: List[PlotView] = []
|
||||
animals: List[AnimalView] = []
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- 客户端
|
||||
class RemoteFarm:
|
||||
"""远程农场的句柄:接口与 :class:`farm.engine.FarmEngine` 保持一致。"""
|
||||
|
||||
def __init__(self, endpoint: str = DEFAULT_ENDPOINT, farm: str = "default",
|
||||
token: Optional[str] = None, name: str = "", timeout: float = 6.0,
|
||||
realtime: bool = True, autostart: bool = True) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
endpoint: str = DEFAULT_ENDPOINT,
|
||||
farm: str = "default",
|
||||
token: str | None = None,
|
||||
name: str = "",
|
||||
timeout: float = 6.0,
|
||||
realtime: bool = True,
|
||||
autostart: bool = True,
|
||||
) -> None:
|
||||
self.endpoint = self._normalize(endpoint)
|
||||
self.farm = farm or "default"
|
||||
self.token = token or ""
|
||||
@@ -243,24 +248,27 @@ class RemoteFarm:
|
||||
|
||||
self.state = StateView()
|
||||
self.connected = False
|
||||
self.mode = "http" # http / ws / poll
|
||||
self.mode = "http" # http / ws / poll
|
||||
self.last_error = ""
|
||||
self.last_message = ""
|
||||
self.server_info: Dict[str, Any] = {}
|
||||
self.on_change: Optional[Callable[[], None]] = None
|
||||
self.server_info: dict[str, Any] = {}
|
||||
self.on_change: Callable[[], None] | None = None
|
||||
|
||||
self._lock = threading.RLock()
|
||||
self._pending: Dict[int, Future] = {}
|
||||
self._pending: dict[int, Future] = {}
|
||||
self._req_id = 0
|
||||
self._stop = threading.Event()
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
self._thread: threading.Thread | None = None
|
||||
self._ws_socket: Any = None
|
||||
self._ws_loop: Any = None
|
||||
self._catalog: Dict[str, Any] = {"crops": [], "animals": [],
|
||||
"feed_price": config.FEED_UNIT_PRICE,
|
||||
"water_capacity": config.WATER_CAPACITY,
|
||||
"ap_cost": config.AP_COST}
|
||||
self._log: Deque[Dict[str, Any]] = deque(maxlen=LOG_KEEP)
|
||||
self._catalog: dict[str, Any] = {
|
||||
"crops": [],
|
||||
"animals": [],
|
||||
"feed_price": config.FEED_UNIT_PRICE,
|
||||
"water_capacity": config.WATER_CAPACITY,
|
||||
"ap_cost": config.AP_COST,
|
||||
}
|
||||
self._log: deque[dict[str, Any]] = deque(maxlen=LOG_KEEP)
|
||||
self._log_seq = 0
|
||||
self.stats = {"http": 0, "ws_actions": 0, "pushes": 0, "errors": 0}
|
||||
|
||||
@@ -274,7 +282,7 @@ class RemoteFarm:
|
||||
def _normalize(endpoint: str) -> str:
|
||||
endpoint = (endpoint or DEFAULT_ENDPOINT).strip().rstrip("/")
|
||||
if endpoint.startswith("tcp://"):
|
||||
endpoint = "http://" + endpoint[len("tcp://"):]
|
||||
endpoint = "http://" + endpoint[len("tcp://") :]
|
||||
if not endpoint.startswith(("http://", "https://")):
|
||||
endpoint = "http://" + endpoint
|
||||
return endpoint
|
||||
@@ -290,8 +298,9 @@ class RemoteFarm:
|
||||
query.update({k: v for k, v in params.items() if v is not None})
|
||||
return f"{self.endpoint}{path}?{urllib.parse.urlencode(query)}"
|
||||
|
||||
def _http(self, method: str, path: str, payload: Optional[Dict[str, Any]] = None,
|
||||
**params: Any) -> Dict[str, Any]:
|
||||
def _http(
|
||||
self, method: str, path: str, payload: dict[str, Any] | None = None, **params: Any
|
||||
) -> dict[str, Any]:
|
||||
url = self.url(path, **params)
|
||||
data = None
|
||||
headers = {"Accept": "application/json"}
|
||||
@@ -313,14 +322,15 @@ class RemoteFarm:
|
||||
self.connected = False
|
||||
raise GameError(
|
||||
"network",
|
||||
f"连不上服务器 {self.endpoint}({exc})。"
|
||||
"请先启动:python main.py serve --daemon",
|
||||
f"连不上服务器 {self.endpoint}({exc})。请先启动:python main.py serve --daemon",
|
||||
status=503,
|
||||
)
|
||||
) from exc
|
||||
try:
|
||||
data_out = json.loads(body) if body else {}
|
||||
except json.JSONDecodeError:
|
||||
raise GameError("bad_response", f"服务器返回了非 JSON 内容(HTTP {status})", 502)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise GameError(
|
||||
"bad_response", f"服务器返回了非 JSON 内容(HTTP {status})", 502
|
||||
) from exc
|
||||
if not isinstance(data_out, dict):
|
||||
raise GameError("bad_response", "服务器返回的 JSON 不是对象", 502)
|
||||
if not data_out.get("ok", True):
|
||||
@@ -352,49 +362,48 @@ class RemoteFarm:
|
||||
raise
|
||||
return False
|
||||
|
||||
def _apply_online(self, state: Dict[str, Any], online: Any) -> None:
|
||||
def _apply_online(self, state: dict[str, Any], online: Any) -> None:
|
||||
"""把"在线人数"合进状态视图:TUI 头部和网页都要显示它。"""
|
||||
if state and isinstance(online, int):
|
||||
state = dict(state)
|
||||
state["online"] = online
|
||||
self._apply(state)
|
||||
|
||||
def snapshot(self) -> Dict[str, Any]:
|
||||
def snapshot(self) -> dict[str, Any]:
|
||||
return self.state.to_dict()
|
||||
|
||||
def catalog(self) -> Dict[str, Any]:
|
||||
def catalog(self) -> dict[str, Any]:
|
||||
return self._catalog
|
||||
|
||||
def recent_log(self, limit: int = 12, since: int = 0) -> List[Dict[str, Any]]:
|
||||
def recent_log(self, limit: int = 12, since: int = 0) -> list[dict[str, Any]]:
|
||||
with self._lock:
|
||||
entries = [e for e in self._log if int(e.get("seq", 0)) > since]
|
||||
return entries[-limit:] if limit > 0 else entries
|
||||
|
||||
def available_actions(self) -> List[Dict[str, Any]]:
|
||||
def available_actions(self) -> list[dict[str, Any]]:
|
||||
data = self._http("GET", "/api/actions")
|
||||
self._apply(data.get("state") or {})
|
||||
return data.get("actions") or []
|
||||
|
||||
def leaderboard(self) -> List[Dict[str, Any]]:
|
||||
def leaderboard(self) -> list[dict[str, Any]]:
|
||||
return self._http("GET", "/api/farms").get("farms") or []
|
||||
|
||||
def world(self) -> Dict[str, Any]:
|
||||
def world(self) -> dict[str, Any]:
|
||||
return self._http("GET", "/api/world")
|
||||
|
||||
def help_text(self) -> str:
|
||||
return self._http("GET", "/api/help").get("rules", "")
|
||||
|
||||
def _apply(self, state: Dict[str, Any]) -> None:
|
||||
def _apply(self, state: dict[str, Any]) -> None:
|
||||
if not state:
|
||||
return
|
||||
self.state.apply(state)
|
||||
if self.on_change is not None:
|
||||
try:
|
||||
# UI 回调抛异常不能影响数据同步
|
||||
with contextlib.suppress(Exception):
|
||||
self.on_change()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _merge_log(self, entries: List[Dict[str, Any]]) -> None:
|
||||
def _merge_log(self, entries: list[dict[str, Any]]) -> None:
|
||||
with self._lock:
|
||||
known = {int(e.get("seq", 0)) for e in self._log}
|
||||
for entry in entries:
|
||||
@@ -403,12 +412,14 @@ class RemoteFarm:
|
||||
self._log.append(entry)
|
||||
known.add(seq)
|
||||
self._log_seq = max(self._log_seq, seq)
|
||||
self._log = deque(sorted(self._log, key=lambda e: int(e.get("seq", 0))),
|
||||
maxlen=LOG_KEEP)
|
||||
self._log = deque(
|
||||
sorted(self._log, key=lambda e: int(e.get("seq", 0))), maxlen=LOG_KEEP
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------ 写接口
|
||||
def _action(self, action: str, params: Optional[Dict[str, Any]] = None,
|
||||
transport: str = "auto") -> Dict[str, Any]:
|
||||
def _action(
|
||||
self, action: str, params: dict[str, Any] | None = None, transport: str = "auto"
|
||||
) -> dict[str, Any]:
|
||||
"""执行一个动作。
|
||||
|
||||
优先走 WebSocket(延迟更低、顺便复用已有连接);只有**通道类**错误
|
||||
@@ -416,8 +427,9 @@ class RemoteFarm:
|
||||
否则"金币不足"会被悄悄重试成两次操作。
|
||||
"""
|
||||
params = params or {}
|
||||
use_ws = (transport == "ws") or (transport == "auto" and self.mode == "ws"
|
||||
and self.connected)
|
||||
use_ws = (transport == "ws") or (
|
||||
transport == "auto" and self.mode == "ws" and self.connected
|
||||
)
|
||||
if use_ws:
|
||||
try:
|
||||
result = self._ws_action(action, params)
|
||||
@@ -436,48 +448,47 @@ class RemoteFarm:
|
||||
return result
|
||||
|
||||
# full=1:让服务器顺手把完整快照带回来,省掉"操作后再查一次"
|
||||
data = self._http("POST", "/api/actions", {"action": action, "params": params},
|
||||
full=1)
|
||||
data = self._http("POST", "/api/actions", {"action": action, "params": params}, full=1)
|
||||
self._apply(data.get("snapshot") or data.get("state") or {})
|
||||
self._merge_log(data.get("log") or [])
|
||||
return data
|
||||
|
||||
# ---- 与本地引擎同名的方法
|
||||
def plant(self, plot: Any, crop: Any) -> Dict[str, Any]:
|
||||
def plant(self, plot: Any, crop: Any) -> dict[str, Any]:
|
||||
return self._action("plant", {"plot": plot, "crop": crop})
|
||||
|
||||
def water(self, plot: Any) -> Dict[str, Any]:
|
||||
def water(self, plot: Any) -> dict[str, Any]:
|
||||
return self._action("water", {"plot": plot})
|
||||
|
||||
def harvest(self, plot: Any) -> Dict[str, Any]:
|
||||
def harvest(self, plot: Any) -> dict[str, Any]:
|
||||
return self._action("harvest", {"plot": plot})
|
||||
|
||||
def harvest_all(self) -> Dict[str, Any]:
|
||||
def harvest_all(self) -> dict[str, Any]:
|
||||
return self._action("harvest_all")
|
||||
|
||||
def buy_animal(self, kind: Any) -> Dict[str, Any]:
|
||||
def buy_animal(self, kind: Any) -> dict[str, Any]:
|
||||
return self._action("buy_animal", {"kind": kind})
|
||||
|
||||
def feed(self, animal_id: Any, quantity: Any = None) -> Dict[str, Any]:
|
||||
params: Dict[str, Any] = {"animal_id": animal_id}
|
||||
def feed(self, animal_id: Any, quantity: Any = None) -> dict[str, Any]:
|
||||
params: dict[str, Any] = {"animal_id": animal_id}
|
||||
if quantity is not None:
|
||||
params["quantity"] = quantity
|
||||
return self._action("feed", params)
|
||||
|
||||
def collect(self, animal_id: Any) -> Dict[str, Any]:
|
||||
def collect(self, animal_id: Any) -> dict[str, Any]:
|
||||
return self._action("collect", {"animal_id": animal_id})
|
||||
|
||||
def buy_feed(self, quantity: Any = config.FEED_BULK) -> Dict[str, Any]:
|
||||
def buy_feed(self, quantity: Any = config.FEED_BULK) -> dict[str, Any]:
|
||||
return self._action("buy_feed", {"quantity": quantity})
|
||||
|
||||
def buy_plot(self) -> Dict[str, Any]:
|
||||
def buy_plot(self) -> dict[str, Any]:
|
||||
return self._action("buy_plot")
|
||||
|
||||
def sleep(self) -> Dict[str, Any]:
|
||||
def sleep(self) -> dict[str, Any]:
|
||||
return self._action("sleep")
|
||||
|
||||
def pause(self, paused: Optional[bool] = None) -> Dict[str, Any]:
|
||||
params: Dict[str, Any] = {}
|
||||
def pause(self, paused: bool | None = None) -> dict[str, Any]:
|
||||
params: dict[str, Any] = {}
|
||||
if paused is not None:
|
||||
params["paused"] = paused
|
||||
data = self._action("pause", params)
|
||||
@@ -490,7 +501,7 @@ class RemoteFarm:
|
||||
self.state.apply(merged)
|
||||
return data
|
||||
|
||||
def reset(self) -> Dict[str, Any]:
|
||||
def reset(self) -> dict[str, Any]:
|
||||
data = self._action("reset")
|
||||
with self._lock:
|
||||
self._log.clear()
|
||||
@@ -498,11 +509,11 @@ class RemoteFarm:
|
||||
self.refresh()
|
||||
return data
|
||||
|
||||
def save(self, path: Optional[str] = None) -> Dict[str, Any]:
|
||||
def save(self, path: str | None = None) -> dict[str, Any]:
|
||||
"""远程存档:位置由服务器决定,``path`` 参数仅为与本地引擎保持同签名。"""
|
||||
return self._action("save")
|
||||
|
||||
def batch(self, items: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
def batch(self, items: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""批量执行(一个请求干多件事),给机器人/AI 用。"""
|
||||
data = self._http("POST", "/api/batch", {"actions": items}, full=1)
|
||||
self._apply(data.get("snapshot") or data.get("state") or {})
|
||||
@@ -514,8 +525,7 @@ class RemoteFarm:
|
||||
if not self.realtime_enabled or self._thread is not None:
|
||||
return
|
||||
self._stop.clear()
|
||||
self._thread = threading.Thread(target=self._realtime_loop, name="farm-ws",
|
||||
daemon=True)
|
||||
self._thread = threading.Thread(target=self._realtime_loop, name="farm-ws", daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
@@ -586,9 +596,13 @@ class RemoteFarm:
|
||||
asyncio.run(self._ws_main(websockets))
|
||||
|
||||
async def _ws_main(self, websockets) -> None:
|
||||
async with websockets.connect(self.ws_url, open_timeout=self.timeout,
|
||||
ping_interval=20, ping_timeout=20,
|
||||
max_size=4 * 1024 * 1024) as socket:
|
||||
async with websockets.connect(
|
||||
self.ws_url,
|
||||
open_timeout=self.timeout,
|
||||
ping_interval=20,
|
||||
ping_timeout=20,
|
||||
max_size=4 * 1024 * 1024,
|
||||
) as socket:
|
||||
self.mode = "ws"
|
||||
self.connected = True
|
||||
self.last_error = ""
|
||||
@@ -650,37 +664,48 @@ class RemoteFarm:
|
||||
return
|
||||
if message.get("ok"):
|
||||
self._merge_log(message.get("log") or [])
|
||||
future.set_result({
|
||||
"ok": True,
|
||||
"message": message.get("message", "完成"),
|
||||
**(message.get("result") or {}),
|
||||
"state": message.get("state"),
|
||||
"snapshot": message.get("snapshot"),
|
||||
"log": message.get("log"),
|
||||
})
|
||||
future.set_result(
|
||||
{
|
||||
"ok": True,
|
||||
"message": message.get("message", "完成"),
|
||||
**(message.get("result") or {}),
|
||||
"state": message.get("state"),
|
||||
"snapshot": message.get("snapshot"),
|
||||
"log": message.get("log"),
|
||||
}
|
||||
)
|
||||
else:
|
||||
future.set_exception(GameError(message.get("error", "error"),
|
||||
message.get("message", "操作失败")))
|
||||
future.set_exception(
|
||||
GameError(message.get("error", "error"), message.get("message", "操作失败"))
|
||||
)
|
||||
return
|
||||
|
||||
if kind == "day":
|
||||
self._merge_log([{
|
||||
"seq": self._log_seq + 1,
|
||||
"day": message.get("day", 0),
|
||||
"kind": "info",
|
||||
"text": f"第 {message.get('day')} 天开始",
|
||||
}] + [{
|
||||
"seq": self._log_seq + 2 + i,
|
||||
"day": message.get("day", 0),
|
||||
"kind": "warn",
|
||||
"text": text,
|
||||
} for i, text in enumerate(message.get("events") or [])])
|
||||
self._merge_log(
|
||||
[
|
||||
{
|
||||
"seq": self._log_seq + 1,
|
||||
"day": message.get("day", 0),
|
||||
"kind": "info",
|
||||
"text": f"第 {message.get('day')} 天开始",
|
||||
}
|
||||
]
|
||||
+ [
|
||||
{
|
||||
"seq": self._log_seq + 2 + i,
|
||||
"day": message.get("day", 0),
|
||||
"kind": "warn",
|
||||
"text": text,
|
||||
}
|
||||
for i, text in enumerate(message.get("events") or [])
|
||||
]
|
||||
)
|
||||
return
|
||||
|
||||
if kind == "error":
|
||||
self.last_error = message.get("message", "")
|
||||
|
||||
def _ws_send(self, payload: Dict[str, Any]) -> None:
|
||||
def _ws_send(self, payload: dict[str, Any]) -> None:
|
||||
"""把消息发到 WS 线程的事件循环里(跨线程投递)。"""
|
||||
socket = getattr(self, "_ws_socket", None)
|
||||
loop = getattr(self, "_ws_loop", None)
|
||||
@@ -688,8 +713,9 @@ class RemoteFarm:
|
||||
raise GameError("ws_closed", "WebSocket 尚未连接")
|
||||
asyncio.run_coroutine_threadsafe(socket.send(json.dumps(payload)), loop)
|
||||
|
||||
def _ws_action(self, action: str, params: Dict[str, Any],
|
||||
timeout: Optional[float] = None) -> Dict[str, Any]:
|
||||
def _ws_action(
|
||||
self, action: str, params: dict[str, Any], timeout: float | None = None
|
||||
) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
self._req_id += 1
|
||||
rid = self._req_id
|
||||
@@ -706,7 +732,8 @@ class RemoteFarm:
|
||||
except FutureTimeout:
|
||||
with self._lock:
|
||||
self._pending.pop(rid, None)
|
||||
raise GameError("timeout", "服务器没有在超时时间内回应")
|
||||
# 超时是正常控制流:FutureTimeout 是内部实现细节,不必挂到异常链上
|
||||
raise GameError("timeout", "服务器没有在超时时间内回应") from None
|
||||
|
||||
|
||||
class RemoteBotClient(RemoteFarm):
|
||||
|
||||
Reference in new issue
Block a user