commit dd1e6ed84607eb82587e2682e11eeaa894c49db9 Author: chakcy <947105045@qq.com> Date: Tue Oct 13 17:29:29 2026 +0800 init: chongming-gateway 0.1.0(网关本体独立包:FastAPI + Zenoh 配置驱动微服务网关) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe89a03 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# chongming-gateway + +chongming 网关本体:基于 **FastAPI + Zenoh** 的配置驱动微服务网关。 + +把宿主项目里的插件(inline 进程内 / external 独立进程)发布的路由统一挂载为 HTTP 端点, +并提供 Worker 服务发现、负载均衡、超时重试、实例定向、MQTT Bridge 与内置登录鉴权。 + +网关只提供**代码**:配置、插件与打包骨架由宿主项目提供(见「运行时约定」)。 + +--- + +## 安装 + +```bash +# 作为依赖(宿主项目,从私有源安装) +uv add chongming-gateway + +# 开发期可编辑安装(在 chongming-core 仓库内) +uv pip install -e utils/gateway +``` + +网关自带完整依赖清单(`fastapi` / `granian` / `zenoh` / `chongming-worker` 等), +宿主项目无需重复声明;需要哪些第三方包,见本包 `pyproject.toml`。 + +## 快速开始 + +宿主项目(cwd)下准备 `config.toml` 与 `config/workers//config.toml`, +并保证插件包(`plugins.`)可导入。之后任选一种启动方式: + +```bash +# 按 config.toml [.server] 启动(三个入口等价,仅默认服务器不同) +uv run serve # uvicorn +uv run granian # granian +uv run gunicorn # gunicorn + uvicorn worker + +# 或把 ASGI 应用交给任意服务器 +uvicorn chongming.app:app --host 0.0.0.0 --port 8000 +``` + +网关按 `config/workers//config.toml` 的 `[workers]` 节逐插件读取加载方式 +(`load_mode = "inline" | "external"`)与副本数,启动时自动发现并挂载路由。 + +## 运行时约定 + +- **配置**:cwd 下 `config.toml`(环境、服务器、安全、MQTT、缓存等)与 + `config/workers/**`(插件注册与路由定义),运行期读取,随宿主项目部署。 +- **插件**:网关按 `plugins.` 约定导入插件包,来源不限——`.mbank` 模块银行、 + 源码目录(开发态 `src/plugins`)或已安装包均可。 +- **工作目录**:日志、上传、数据等相对路径均以 cwd 为基准。 + +## 目录结构 + +```text +src/chongming/ +├── __init__.py # 启动入口(serve / granian / gunicorn)与服务器参数转换 +└── app/ + ├── __init__.py # FastAPI 应用装配(lifespan、中间件、异常处理) + ├── core/ # WorkerManager / RouteGenerator / MQTT Bridge / 日志 / 鉴权 + └── routes/ # 内置管理端点(stats / worker_admin / auth) +``` + +## 开发与发布 + +```bash +# 在 chongming-core 仓库内改代码后,让 venv 用本地源码(联调用) +uv pip install -e utils/gateway + +# 发版:构建 wheel 并推私有源 +uv build # dist/chongming_gateway--py3-none-any.whl +uv run twine upload --repository <私有源> dist/* +``` + +发版后依赖方执行 `uv lock --upgrade-package chongming-gateway` 刷新版本 pin。 +注意 `uv sync` 会用锁文件版本替换可编辑安装——打包/发版前先确认 venv 指向目标版本。 + +## 相关文档 + +- [项目主 README](../../README.md):整体架构、使用与构建部署 +- [Worker 管理与多实例运维](../../docs/worker-management.md):管理端点、扩缩容、实例定向 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d86e390 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,52 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +name = "chongming-gateway" +version = "0.1.0" +description = "chongming 网关:FastAPI + Zenoh 的配置驱动微服务网关(插件路由动态挂载、Worker 服务发现与负载均衡)" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + # chongming 家族工具包 + "chongming-auth", + "chongming-cache", + "chongming-config", + "chongming-jwt", + "chongming-worker", + # Web / 服务器 + "fastapi", + "granian[reload,uvloop]>=2.8.4", + "gunicorn", + "httptools", + "starlette", + "uvicorn", + "uvloop", + # 运行时 + "amqtt", + "eclipse-zenoh", + "orjson", + "pwdlib", + "pydantic", + "sqlite-vfs", +] + +[project.scripts] +gunicorn = "chongming:gunicorn_serve" +granian = "chongming:granian_serve" +serve = "chongming:serve" + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming"] + +[[tool.uv.index]] +url = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" + +[[tool.uv.index]] +url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" + +[dependency-groups] +dev = [ + "twine>=7.0.0", +] diff --git a/src/chongming/__init__.py b/src/chongming/__init__.py new file mode 100644 index 0000000..2d3e8d2 --- /dev/null +++ b/src/chongming/__init__.py @@ -0,0 +1,150 @@ +"""chongming 网关启动入口(uvicorn / granian / gunicorn)。 + +config.toml 中 `[.server]` 的启动参数保持现状(host / port / workers / +reload / access_log / timeout_keep_alive),由 `_to_uvicorn`、`_to_granian` 在 +启动时转换为对应服务器的关键字参数,切换服务器不必改配置格式。 +""" + +from collections.abc import Mapping +from pathlib import Path +from typing import Any + +import uvicorn +from chongming_config import Server, load_config + +from .app.core.logger import get_logger + +# 开发期热重载(reload=true)监听的源码目录(granian 用;uvicorn 走其默认扫描)。 +# 网关自身源码目录用 __file__ 推导:editable 安装时指向仓库内的源码,wheel 安装时 +# 指向 site-packages(打包产物强制禁 reload,该目录只在开发态有意义); +# 插件目录固定为宿主项目的 src/plugins。 +RELOAD_PATHS = [Path(__file__).resolve().parent, Path("src/plugins")] + + +def _server_config_view(server_config: Server) -> Mapping[str, Any]: + """[.server] 的宽松视图:实际 TOML 可能带 Server 之外的键 + (如 timeout_keep_alive,chongming_config.Server 尚未声明)。""" + return server_config + + +def _to_uvicorn(server_config: Server) -> dict: + """把 [.server] 的现状参数转为 uvicorn.run 的关键字参数。 + + uvicorn 的参数名与配置项一致,这里只做缺省兜底。 + """ + cfg = _server_config_view(server_config) + converted: dict = { + "host": cfg.get("host", "0.0.0.0"), + "port": cfg.get("port", 8000), + "reload": cfg.get("reload", False), + } + for key in ("workers", "access_log", "timeout_keep_alive"): + if key in cfg: + converted[key] = cfg[key] + return converted + + +def _to_granian(server_config: Server) -> dict: + """把 [.server] 的现状参数转为 granian.Granian 的关键字参数。 + + 仅两处改名:host → address、access_log → log_access; + timeout_keep_alive 在 granian 无对应项(HTTP/1 keep-alive 只有开关、无空闲 + 超时配置),忽略。 + """ + cfg = _server_config_view(server_config) + converted: dict = { + "address": cfg.get("host", "0.0.0.0"), + "port": cfg.get("port", 8000), + "interface": "asgi", + } + for key in ("reload", "workers"): + if key in cfg: + converted[key] = cfg[key] + if "access_log" in cfg: + converted["log_access"] = cfg["access_log"] + return converted + + +def serve(): + """ + 使用 uvicorn 启动服务 + + 从配置文件中加载服务器配置和日志配置,然后使用 uvicorn 启动应用服务。 + 该函数适用于开发环境或单进程部署场景。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + log_config = config[env]["logging"] + logger = get_logger("app") + logger.info(f"启动服务: {server_config}") + logger.info(f"启动日志: {log_config}") + uvicorn.run("chongming.app:app", **_to_uvicorn(server_config)) + + +def granian_serve(): + """ + 使用 granian 启动服务 + + 从配置文件中加载服务器配置和日志配置,然后使用 granian 启动应用服务。 + 该函数适用于开发环境或单进程部署场景。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + # 延迟导入:未选用 granian 的部署环境不必安装它 + import granian + + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + log_config = config[env]["logging"] + logger = get_logger("app") + logger.info(f"启动服务: {server_config}") + logger.info(f"启动日志: {log_config}") + granian.Granian( + target="chongming.app:app", + **_to_granian(server_config), + reload_paths=RELOAD_PATHS, + ).serve() + + +def gunicorn_serve(): + """ + 使用 gunicorn 启动服务 + + 通过 gunicorn 作为进程管理器启动应用,使用 UvicornWorker 作为 worker 类型, + 支持多进程部署,适用于生产环境。 + + 从配置文件中读取服务器配置,动态设置 gunicorn 的 worker 数量、绑定地址和端口等参数。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + import sys + + from gunicorn.app.wsgiapp import run + + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + + # 构建 gunicorn 命令行参数并启动服务 + sys.argv = [ + "gunicorn", + "chongming.app:app", + "-k", + "uvicorn.workers.UvicornWorker", + "-w", + f"{server_config.get('workers', 4)}", + "-b", + f"{server_config.get('host', '0.0.0.0')}:{server_config.get('port', 8000)}", + ] + run() + + +if __name__ == "__main__": + serve() diff --git a/src/chongming/app/__init__.py b/src/chongming/app/__init__.py new file mode 100644 index 0000000..8f9bf29 --- /dev/null +++ b/src/chongming/app/__init__.py @@ -0,0 +1,354 @@ +""" +chongming FastAPI 应用入口 + +重构为配置驱动的微服务网关: +- WorkerManager: 统一管理 Zenoh worker 存活检测、负载均衡、超时重试、缓存 +- RouteGenerator: 根据配置自动生成 FastAPI 端点 +- 插件工厂函数动态加载 +""" + +import time +import asyncio +import itertools +from contextlib import asynccontextmanager + +from fastapi import FastAPI, Request +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from fastapi.middleware.gzip import GZipMiddleware +from fastapi.middleware.cors import CORSMiddleware + +from chongming_config import get_config +from starlette.types import ASGIApp, Message, Receive, Scope, Send +from .core.logger import get_logger +from sqlite_vfs.contrib.fastapi_static import SVFSStaticFiles +from .core import lifespan_setup, call_stats +from .core.parent_death import guard_parent_death +from .routes import stats_router, worker_admin_router, auth_router + + +config = get_config() +logger = get_logger("app") + +try: + env = config["default"]["env"] +except KeyError: # pragma: no cover + raise ValueError("配置不存在") + +@asynccontextmanager +async def lifespan(app: FastAPI): +# ---- 启动阶段 ---- + # 0. 父进程死亡防护:每个 granian worker 各设一次(PR_SET_PDEATHSIG 不跨 fork + # 继承)——master 被 SIGKILL 时内核直接给 worker 发 SIGTERM,走下面的优雅 + # 停机摘除路由与副本注册,避免留下继续监听端口/挂在服务发现里的孤儿 + guard_parent_death(logger) + + # 1. 本地存储(替代 Nanio S3,省 ~172 MB) + lifespan_setup.setup_storage(app) + + # 1.2 内置 auth 库(users 表 + admin 种子)—— auth 域的基础设施部分: + # 登录/签发内置在网关(管理平面底线),不依赖 auth 插件是否加载; + # 与 auth 插件(用户管理)共用同一张 users 表 + await lifespan_setup.setup_auth_store(app) + + # 2. Zenoh + session = lifespan_setup.setup_zenoh(app) + + # 3. WorkerManager + manager = lifespan_setup.setup_worker_manager(app, session) + + # 3.1 内嵌 amqtt Broker(宿主级分布式锁;需在 WorkerManager 就绪后提供共享 cache) + await lifespan_setup.setup_mqtt_broker(app, manager) + + # 4.1 全局副本数协调:把当前网关登记进在线列表,供按在线网关分摊 quota + manager.register_gateway() + + # 4.2 宿主级 inline worker 领导选举(基于共享 cache 的分布式锁,key 按网关隔离): + # gunicorn 多 worker 下每实例仅一个进程托管 inline worker(避免 N×count 冗余), + # 其余进程只做 HTTP 并经 Zenoh liveliness 发现调用;多实例各选各的 leader。 + is_inline_owner = manager.acquire_inline_owner() + app.state.inline_owner_locked = is_inline_owner + + # 5. 插件加载(混合模式:inline 进程内加载;external 由独立进程运行, + # 其路由经 Zenoh 动态注册——进程编排交给 Docker/K8s,网关不拉起进程) + worker_tasks, custom_routers, inline_types, external_types = ( + await lifespan_setup.load_workers( + app, session, manager, is_inline_owner=is_inline_owner + ) + ) + + # 5.1 动态路由注册表常驻启动:订阅 chongming/routes/**,external 插件的 + # 路由经 Zenoh 注册/摘除,无需网关侧保留插件配置(external_types 仅观测) + await lifespan_setup.setup_route_registry( + app, session, manager, inline_types=inline_types + ) + + # 5.5 契约校验(仅 inline 插件;external 插件在自己进程内校验),失败则启动终止 + lifespan_setup.validate_contracts(app) + + # 6. 注册路由(inline 插件的 TOML 动态路由 + 插件自定义路由) + await lifespan_setup.register_routes( + app, manager, custom_routers, worker_types=inline_types + ) + + # 7. 启动 MQTT Bridge(内嵌 Broker + Worker 桥接) + await lifespan_setup.setup_mqtt(app, manager) + + # 7.1 启动 MQTT Bridge 持有者自愈巡检(跨所有网关进程,崩溃时自动接管重建) + app.state.mqtt_bridge_supervise_task = asyncio.create_task( + lifespan_setup.mqtt_bridge_supervisor(app, manager) + ) + + # 8. 短暂等待,确保 worker 状态同步 + await asyncio.sleep(0.5) + + # 打印 worker 就绪状态 + worker_status = { + wtype: len(workers) + for wtype, workers in manager._workers_cache.items() + } + logger.info(f"Worker 就绪状态: {worker_status}") + + # ---- 应用运行 ---- + yield + + # ---- 关闭阶段 ---- + logger.info("正在关闭应用...") + + # 1. 停止 MQTT Bridge 持有者自愈巡检,先于 Bridge 关停,避免关停期间再次接管 + _sup = getattr(app.state, "mqtt_bridge_supervise_task", None) + if _sup: + _sup.cancel() + try: + await _sup + except (asyncio.CancelledError, Exception): + pass + + # 1.0 停止 MQTT Bridge + await lifespan_setup.shutdown_mqtt(app) + + # 1.5 停止路由注册表订阅(常驻,随应用生命周期启停) + registry = getattr(app.state, "route_registry", None) + if registry is not None: + registry.stop() + + # 2. 清理 inline workers(cleanup_workers 内部按 load_mode 过滤) + await lifespan_setup.cleanup_workers(app) + + # 2.2 关闭内置 auth 库 + auth_db = getattr(app.state, "auth_db", None) + if auth_db is not None: + await auth_db.dispose() + + # 2.1 释放 inline worker 所有权分布式锁(仅持锁进程,避免误删其他进程获得的新锁) + if getattr(app.state, "inline_owner_locked", False): + await manager.release_inline_owner() + + # 3. 取消刷新任务 + refresh_task = getattr(app.state, "refresh_task", None) + if refresh_task: + refresh_task.cancel() + try: + await refresh_task + except asyncio.CancelledError: + pass + + # 4. 取消所有 worker 任务(含 self-heal 接管后拉起的任务) + all_worker_tasks = list(worker_tasks) + manager.extra_worker_tasks() + for task in all_worker_tasks: + task.cancel() + if all_worker_tasks: + await asyncio.gather(*all_worker_tasks, return_exceptions=True) + + # 4.5 取消后台统计/审计任务 + await call_stats.shutdown() + + # 4.6 全局副本数协调:关闭时把当前网关从在线列表移除(其余网关将重新分摊) + manager.unregister_gateway() + + # 5. 关闭 Zenoh 会话 + # 退出阶段等对端应答可能超时(zenoh.ZError: close operation timed out),此时 + # 进程马上就要退出,记录告警即可——不让它把 lifespan 关闭流程带崩、打出一串 + # [ERROR] traceback(granian 停机日志里很吓人,但无实际影响) + try: + session.close() + logger.info("Zenoh 会话已关闭") + except Exception as e: # noqa: BLE001 - zenoh close 超时属退出阶段的正常竞争 + logger.warning("Zenoh 会话关闭超时/异常(退出阶段忽略): %s", e) + + logger.info("应用已关闭") + + +cors = config[env].get("cors", {}) +app = FastAPI( + title=config["default"]["app"]["name"], + version=config["default"]["app"]["version"], + description=config["default"]["app"]["description"], + docs_url="/docs" if config[env]["debug"] else None, + redoc_url="/redoc" if config[env]["debug"] else None, + openapi_url="/openapi.json" if config[env]["debug"] else None, + lifespan=lifespan, +) + +app.add_middleware(CORSMiddleware, **cors) +# GZip 压缩响应(<500B 不压缩;starlette 默认排除 text/event-stream, +# 保证 SSE 流式端点即时性;>=128KB 的响应在独立线程压缩,不阻塞事件循环) +app.add_middleware(GZipMiddleware) + + +# access 日志采样率(logging.access_sample_rate,0.1 = 只记录 10% 的成功请求; +# 错误 >=400 始终全量记录,保证可追溯;1.0/缺省 = 全量) +ACCESS_SAMPLE_RATE = float(config[env].get("logging", {}).get("access_sample_rate", 1.0)) +# 成功请求采样计数器(每进程独立;错误请求不消耗计数) +_access_log_counter = itertools.count() + + +def _should_log_access(status_code: int, sample_rate: float = ACCESS_SAMPLE_RATE) -> bool: + """错误全量;成功按采样率(采样为确定性计数,无随机数开销)""" + if status_code >= 400 or sample_rate >= 1.0: + return True + denom = max(1, int(round(1.0 / sample_rate))) + return next(_access_log_counter) % denom == 0 + + +def _log_access(scope: Scope, status_code: int, start: float) -> None: + """按采样规则写一条访问日志(成功按 access_sample_rate,错误 >=400 全量)""" + if not _should_log_access(status_code): + return + client = scope.get("client") + logger.info( + "%s %s status=%s duration=%.2fms client=%s", + scope.get("method", "-"), scope.get("path", "-"), status_code, + (time.perf_counter() - start) * 1000, client[0] if client else "-", + extra={"status_code": status_code}, + ) + + +class AccessLogMiddleware: + """ + HTTP访问日志中间件(纯 ASGI 实现),记录每个请求的详细信息。 + + 记录的信息包括: + - 请求方法和路径 + - 响应状态码 + - 请求处理时长(毫秒,按慢路径:从进入中间件到响应头产出) + - 客户端IP地址 + + 高吞吐优化: + - 采样:成功请求按 access_sample_rate 记录,错误(>=400)全量 + - 异步:% 参数化消息 + 队列日志(QueueHandler),格式化与文件 I/O + 在后台监听线程执行,主线程仅入队 + - 不用 BaseHTTPMiddleware(@app.middleware("http")):它会为每请求建 anyio + task group 并把响应体经内存流过一手,压测实测高并发下吃掉 30~50% 吞吐; + 这里只包一层 send 截获 response.start,无缓冲、无额外任务 + """ + + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": + await self.app(scope, receive, send) + return + + start = time.perf_counter() + logged = False + + async def send_wrapper(message: Message) -> None: + nonlocal logged + # 响应头产出即记录(流式响应因此只计到首字节,不计整段流) + if message["type"] == "http.response.start" and not logged: + logged = True + _log_access(scope, message["status"], start) + await send(message) + + try: + await self.app(scope, receive, send_wrapper) + except Exception: + # 未产出响应就异常(外层 500):补一条全量错误日志后原样抛出 + if not logged: + _log_access(scope, 500, start) + raise + + +app.add_middleware(AccessLogMiddleware) + + +@app.get("/", summary="根路径") +async def root(): + """ + 根路径端点,返回应用的基本信息。 + + 返回: + dict: 包含应用名称、版本、文档地址和健康检查地址的字典 + """ + return { + "message": f"欢迎使用 {config['default']['app']['name']}", + "version": config["default"]["app"]["version"], + "docs": "/docs" if config[env]["debug"] else None, + "health": f"{config['default']['prefix']}/health", + } + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + """ + 请求验证异常处理器,记录详细的验证错误信息和请求体。 + 帮助快速定位 Pydantic 模型与前端/设备上报数据的不匹配问题。 + """ + body = None + try: + body = await request.body() + body_str = body.decode("utf-8")[:2000] if body else "" + except Exception: + body_str = "" + + errors = exc.errors() + error_summary = [ + f"{' -> '.join(str(loc) for loc in e['loc'])}: {e['msg']}" + for e in errors + ] + logger.error( + f"请求验证失败 [{request.method} {request.url.path}]:\n" + f" 错误: {'; '.join(error_summary)}\n" + f" 请求体: {body_str}" + ) + return JSONResponse( + status_code=422, + content={"detail": errors}, + ) + + +@app.exception_handler(Exception) +async def global_exception_handler(_: Request, exc: Exception): + """ + 全局异常处理器,捕获并处理应用中未捕获的异常。 + + 在调试模式下返回详细的异常信息,在生产模式下返回通用错误提示。 + + 参数: + _: FastAPI请求对象(未使用) + exc: 捕获到的异常对象 + + 返回: + JSONResponse: 包含错误信息的JSON响应,状态码为500 + """ + logger.error(f"全局异常: {exc}") + return JSONResponse( + status_code=500, + content={ + "error": "内部服务器错误", + "detail": str(exc) if config[env]["debug"] else "请查看服务器日志", + }, + ) + + +# 静态文件服务(可选) +if vfs_db_path := config[env].get("file_system", {}).get("path", None): + logger.info("静态文件已存在,将使用静态文件服务") + static_files_handler = SVFSStaticFiles(vfs_db_path=vfs_db_path) + app.mount("/static", static_files_handler, name="static") + +app.include_router(stats_router) +app.include_router(worker_admin_router) +app.include_router(auth_router) diff --git a/src/chongming/app/core/__init__.py b/src/chongming/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chongming/app/core/auth.py b/src/chongming/app/core/auth.py new file mode 100644 index 0000000..2907ec2 --- /dev/null +++ b/src/chongming/app/core/auth.py @@ -0,0 +1,141 @@ +import hashlib +import os +import time +from collections import OrderedDict + +from fastapi import Depends, HTTPException, status, Request +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer +from chongming_jwt import JWT +from chongming_cache import Cache +from chongming_config import get_config +import orjson + + +security_scheme = HTTPBearer(auto_error=False) + +# JWT 解析共享缓存:与 WorkerManager 共用 [cache] 配置段。 +# 默认 sqlite(本机);横向拓展时 config.toml 切 backend = "redis", +# 多网关实例共享同一 Redis 即可全局只解密一次,代码零改动。 +# 注意:缓存存的是"已验证结果",信任模型与 worker 回复缓存一致 +# (缓存基础设施可信;Redis 请配置密码并置于内网)。 +_jwt_cache = Cache.from_config(get_config()["cache"]) +# 条目 TTL(秒):控制存储占用与密钥轮换后的自然清理; +# 安全性由命中时的 exp 复核保证,TTL 不承担过期判断 +_JWT_CACHE_TTL = 60 + +# ── 一级:进程内 JWT 结果缓存(LRU + TTL)──────────────────────────── +# 鉴权依赖是同步依赖(def),每次请求都要走一遍共享缓存——backend=redis 时 +# 即"每请求一次同步网络往返 + FastAPI 线程池 hop"。同一 token 在 TTL 内被 +# 反复使用是常态,故在进程内加一层 L1:命中直接返回,不碰网络。 +# 语义与 L2 完全一致:key 含密钥/算法指纹;命中仍复核 exp(过期=拒绝而非放行); +# 条目按 TTL 惰性清理并限长。CHONGMING_JWT_L1=0 可关闭(便于 A/B 与排障)。 +_JWT_L1_ENABLED = os.environ.get("CHONGMING_JWT_L1", "1") != "0" +_JWT_L1_MAX = 1024 +# 存 orjson 字节而非 dict:命中时反序列化出新副本,保证"调用方修改返回值 +# 不污染缓存"这一 L2 既有契约(L2 同样是 JSON 往返后返回新对象) +_jwt_l1: "OrderedDict[str, tuple[bytes, float, float]]" = OrderedDict() # key → (payload_json, exp, 写入时刻) + + +def _l1_get(key: str): + """命中返回 (payload_json: bytes, exp);条目超过共享缓存 TTL(运行时可调,便于测试/运维)视为过期""" + entry = _jwt_l1.get(key) + if entry is None: + return None + _payload_json, _exp, ts = entry + if time.time() - ts >= _JWT_CACHE_TTL: + _jwt_l1.pop(key, None) + return None + _jwt_l1.move_to_end(key) + return entry + + +def _l1_set(key: str, payload_json: bytes, exp: float) -> None: + _jwt_l1[key] = (payload_json, exp, time.time()) + _jwt_l1.move_to_end(key) + while len(_jwt_l1) > _JWT_L1_MAX: + _jwt_l1.popitem(last=False) + + +def _l1_sweep(now: float) -> None: + """惰性清理:超 TTL 的整条丢弃(token 永不重复时防堆积),只在表较大时调用""" + stale = [k for k, v in _jwt_l1.items() if now - v[2] >= _JWT_CACHE_TTL] + for k in stale: + _jwt_l1.pop(k, None) + + +def _cache_key(token: str, secret: str, algorithm: str) -> str: + """缓存 key:token 内容 + 密钥/算法指纹,不同密钥/算法不串用""" + return "jwt:" + hashlib.sha256(f"{secret}:{algorithm}:{token}".encode()).hexdigest() + + +def _cached_decode(token: str, secret: str, algorithm: str) -> tuple: + """JWT 解密结果缓存((payload, exp)):一级进程内 LRU,二级共享缓存。 + + - 一级命中且未过期 -> 直接返回(零网络、零密码学);过期 -> 直接拒绝 + - 一级未命中 -> 查二级共享缓存;命中且未过期 -> 回填一级后返回 + - 二级也未命中 -> 完整解码(RSA/ECDSA 验证)后写二级 + 一级(TTL=60s/30s) + - decode 失败(无效 token)不缓存,每次都完整验证后拒绝 + """ + key = _cache_key(token, secret, algorithm) + now = time.time() + if _JWT_L1_ENABLED: + hit = _l1_get(key) + if hit is not None: + payload_json, exp, _ts = hit + if not exp or exp >= now: + # 反序列化出新副本:调用方修改不影响缓存条目(与 L2 契约一致) + return orjson.loads(payload_json), exp + raise ValueError("Token 已过期") + if len(_jwt_l1) > _JWT_L1_MAX // 2: # 仅大表时惰性清理,避免常态 O(n) + _l1_sweep(now) + + entry = _jwt_cache.get(key) + if entry is not None: + payload = entry["payload"] + exp = entry.get("exp", 0) + if not exp or exp >= time.time(): + if _JWT_L1_ENABLED: + _l1_set(key, orjson.dumps(payload), exp) + return payload, exp + raise ValueError("Token 已过期") + jwt = JWT(secret, algorithm) + payload_str = jwt.decode(token) + payload = orjson.loads(payload_str.encode("utf-8")) + exp = payload.get("exp", 0) + _jwt_cache.set(key, {"payload": payload, "exp": exp}, expire=_JWT_CACHE_TTL) + if _JWT_L1_ENABLED: + _l1_set(key, orjson.dumps(payload), exp) + return payload, exp + + +def jwt_auth_dependency( + credentials: HTTPAuthorizationCredentials = Depends(security_scheme) +) -> dict: + """JWT 校验依赖,返回解码后的 payload,校验失败抛出 401""" + config = get_config() + secret = config["security"]["secret_key"] + algorithm = config["security"]["algorithm"] + + if not credentials: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Missing authorization header", + headers={"WWW-Authenticate": "Bearer"}, + ) + token = credentials.credentials + try: + payload, _ = _cached_decode(token, secret, algorithm) + # 缓存后端返回的是独立对象(JSON 反序列化后为新副本),调用方修改不影响缓存 + return payload + except Exception as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=f"Invalid token: {str(e)}", + headers={"WWW-Authenticate": "Bearer"}, + ) + +async def store_user_to_request( + request: Request, + user: dict = Depends(jwt_auth_dependency) +) -> None: + request.state.user = user diff --git a/src/chongming/app/core/call_stats.py b/src/chongming/app/core/call_stats.py new file mode 100644 index 0000000..1a64a6e --- /dev/null +++ b/src/chongming/app/core/call_stats.py @@ -0,0 +1,170 @@ +""" +调用统计与审计日志(后台任务执行) + +route_generator 端点在 call_raw 返回后仅做非关键后置操作,全部经 +spawn() 以 fire-and-forget 后台任务执行,端点立即返回,不增加接口 +响应延迟、不占用请求协程资源: + 1. 更新内存调用统计(per worker_type/subject/status 计数 + 延迟聚合 + 最近调用缓冲) + 2. 写审计日志(logs/audit-YYYY-MM-DD.log,复用 logger 按日切分 + 大小轮转设施) + +record_call 内部全兜底:统计或审计任一步失败仅记 warning,绝不影响响应。 +统计为进程内视角(gunicorn 多 worker 下各进程独立聚合)。 + +后台任务随应用生命周期管理:lifespan 关闭阶段调用 shutdown() 统一取消。 +""" +import asyncio +import datetime +import orjson +import random +import time +from collections import deque +from typing import Any, Coroutine, Dict + +from chongming_config import get_config + +from .logger import get_logger + +logger = get_logger("call_stats") +# 审计日志独立文件(audit-YYYY-MM-DD.log),与业务日志分离便于检索 +audit_logger = get_logger("audit") + +# 最近调用环形缓冲上限 +MAX_RECENT = 1000 + +# 审计日志采样率:与 access 日志同源配置(access_sample_rate,0.1 = 只写 10% 的成功请求; +# 错误 status >= 400 始终全量)。压测实测全量审计写盘 8.8MB/4.3 万行,是高吞吐下的隐藏 I/O 大头。 +_audit_config = get_config() +_audit_env = _audit_config["default"]["env"] +_AUDIT_SAMPLE_RATE = float( + _audit_config[_audit_env].get("logging", {}).get("access_sample_rate", 1.0) +) + + +def _should_record_audit(status: int) -> bool: + """审计日志采样判定(与 access 日志同模式)""" + if status >= 400 or _AUDIT_SAMPLE_RATE >= 1.0: + return True + denom = max(1, int(round(1.0 / _AUDIT_SAMPLE_RATE))) + return random.randrange(denom) == 0 + +# 后台任务集合(fire-and-forget,shutdown 时统一取消) +_tasks: set[asyncio.Task] = set() + + +def spawn(coro: Coroutine) -> asyncio.Task: + """以 fire-and-forget 方式创建后台任务并纳入跟踪(shutdown 时取消)""" + task = asyncio.create_task(coro) + _tasks.add(task) + task.add_done_callback(_tasks.discard) + return task + + +async def shutdown() -> None: + """取消所有后台统计/审计任务(应用关闭时调用)""" + tasks = list(_tasks) + if not tasks: + return + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + + +class CallStats: + """内存调用统计:计数 + 延迟聚合 + 最近调用环形缓冲。 + + asyncio 单线程事件循环内访问,无需加锁。 + """ + + def __init__(self, max_recent: int = MAX_RECENT): + self._counts: Dict[tuple, int] = {} + self._latency: Dict[str, Dict[str, float]] = {} + self._recent: deque = deque(maxlen=max_recent) + + def record(self, entry: Dict[str, Any]) -> None: + key = (entry["worker_type"], entry["subject"], entry["status"]) + self._counts[key] = self._counts.get(key, 0) + 1 + + wt = entry["worker_type"] + lat = self._latency.setdefault(wt, {"total": 0.0, "count": 0, "max": 0.0}) + dur = entry.get("duration_ms", 0.0) + lat["total"] += dur + lat["count"] += 1 + if dur > lat["max"]: + lat["max"] = dur + + self._recent.append(entry) + + def summary(self) -> Dict[str, Any]: + """聚合快照:总量、per worker/subject/status 计数、延迟、最近调用""" + calls = [ + {"worker_type": wt, "subject": subj, "status": status, "count": count} + for (wt, subj, status), count in sorted(self._counts.items()) + ] + latency = [ + { + "worker_type": wt, + "count": lat["count"], + "avg_ms": round(lat["total"] / lat["count"], 2) if lat["count"] else 0.0, + "max_ms": round(lat["max"], 2), + } + for wt, lat in sorted(self._latency.items()) + ] + return { + "total_calls": sum(self._counts.values()), + "calls": calls, + "latency": latency, + "recent": list(self._recent)[-50:], + } + + +_stats = CallStats() + + +def summary() -> Dict[str, Any]: + """全局统计快照(供 GET /stats 端点使用)""" + return _stats.summary() + + +def _format_audit(entry: Dict[str, Any]) -> str: + """审计日志单行 key=value 格式,便于 grep 检索""" + ts = entry.get("ts") or time.time() + ts_str = datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + user = entry.get("user") + if isinstance(user, dict): + # JWT payload(auth.py store_user_to_request 注入),orjson 默认紧凑单行输出 + user_str = orjson.dumps(user).decode() + elif isinstance(user, str): + user_str = user + else: + user_str = "-" + parts = [ + "AUDIT", + f"ts={ts_str}", + f"worker={entry.get('worker_type', '-')}", + f"subject={entry.get('subject', '-')}", + f"method={entry.get('method', '-')}", + f"path={entry.get('path', '-')}", + f"user={user_str}", + f"client={entry.get('client') or '-'}", + f"status={entry.get('status', '-')}", + f"duration_ms={entry.get('duration_ms', 0):.3f}", + f"req_bytes={entry.get('req_bytes', 0)}", + f"resp_bytes={entry.get('resp_bytes', 0)}", + ] + return " ".join(parts) + + +async def record_call(entry: Dict[str, Any]) -> None: + """后台执行体:更新内存统计 + 写审计日志。 + + 整体兜底:任一步失败仅记 warning,绝不向端点冒泡。 + """ + try: + _stats.record(entry) + except Exception as e: # pragma: no cover - 防御性兜底 + logger.warning("调用统计记录失败: %s", e) + try: + if _should_record_audit(entry.get("status", 200)): + audit_logger.info(_format_audit(entry)) + except Exception as e: # pragma: no cover - 防御性兜底 + logger.warning("审计日志写入失败: %s", e) diff --git a/src/chongming/app/core/contracts.py b/src/chongming/app/core/contracts.py new file mode 100644 index 0000000..8714938 --- /dev/null +++ b/src/chongming/app/core/contracts.py @@ -0,0 +1,64 @@ +""" +启动契约校验(网关 inline 模式) + +校验逻辑的唯一实现在 chongming_worker.contracts(网关与 worker 进程共享), +本模块只做网关侧的编排:对 inline 插件批量校验、日志汇总、硬失败终止启动。 + +external 插件在自己进程内做同样校验(validate_worker_contracts),网关侧跳过。 +""" +from typing import Any, Dict, List + +from chongming_worker import get_all_rpc_methods +from chongming_worker.contracts import check_plugin, load_plugin_models +from chongming_worker.route_config import load_worker_routes_from_dir + +from .logger import get_logger + +logger = get_logger("contracts") + + +def validate_contracts(plugins: List[Dict[str, Any]]) -> None: + """ + 对每个发现的 inline 插件执行三方一致性校验。 + + 挂载于 lifespan 的 load_workers 与 register_routes 之间,此时插件模块已导入 + (@rpc_method 注册表就绪)且 TOML 路由尚未注册到 FastAPI。 + + Args: + plugins: _discover_plugins() 返回的 inline 插件配置列表 + + Raises: + RuntimeError: 存在硬失败项(HTTP 路由缺失 @rpc_method 注册)时 + """ + registry = get_all_rpc_methods() + hard_errors: List[str] = [] + warnings: List[str] = [] + + for wc in plugins: + worker_type = wc.get("type", "") + if not worker_type: + continue + + toml_routes = load_worker_routes_from_dir(worker_type) + registered = registry.get(worker_type, {}) + model_names = load_plugin_models(worker_type) + hard, warns = check_plugin(worker_type, toml_routes, registered, model_names) + hard_errors.extend(hard) + warnings.extend(warns) + + # 输出结果 + if warnings: + logger.warning("契约校验发现 %d 个警告:", len(warnings)) + for w in warnings: + logger.warning(" ⚠ %s", w) + + if hard_errors: + logger.error("契约校验失败,发现 %d 个硬性错误:", len(hard_errors)) + for e in hard_errors: + logger.error(" ✗ %s", e) + raise RuntimeError( + "启动契约校验失败: " + + "; ".join(hard_errors) + ) + + logger.info("契约校验通过(TOML 路由 ↔ @rpc_method 注册表一致)") diff --git a/src/chongming/app/core/lifespan_setup.py b/src/chongming/app/core/lifespan_setup.py new file mode 100644 index 0000000..f6d347f --- /dev/null +++ b/src/chongming/app/core/lifespan_setup.py @@ -0,0 +1,915 @@ +import os +import inspect +import time +import signal +import tomllib +import glob +import importlib +import multiprocessing +import asyncio +import socket +import sys +from pathlib import Path + +from chongming_cache import Cache, DistributedLock +from fastapi import FastAPI +import zenoh + +from chongming_config import get_config, get_workers_config +from ..core.logger import get_logger +from ..core.parent_death import guard_parent_death +from ..core.route_generator import register_dynamic_routes +from ..core.worker_manager import WorkerManager +from ..core.mqtt_bridge import MQTTBridge + +config = get_config() +logger = get_logger("app") + +# 挂载框架/插件父 logger 的 handler:logger 模块只为具名 logger 挂 handler, +# 子 logger(chongming_worker.runtime、plugins.doubao 等)经 propagate 到父级才可见 +get_logger("chongming_worker") +get_logger("plugins") + +try: + env = config["default"]["env"] +except KeyError: # pragma: no cover + raise ValueError("配置不存在") + +def is_port_in_use(port: int, host: str = "127.0.0.1") -> bool: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(0.5) + return s.connect_ex((host, port)) == 0 + + +def _broker_lock_key() -> str: + """内嵌 Broker 排他锁 key。内嵌 Broker 绑定本机端口,故按宿主隔离 + (多实例共享同一 cache/redis 时,各宿主仍各起各的 Broker)。""" + return f"mqtt_broker:{socket.gethostname()}" + + +def _broker_owner_key() -> str: + """记录内嵌 Broker 子进程实际 PID 的缓存 key(按宿主隔离)。 + + 持有者在启动 Broker 子进程后登记其 PID;前任持有者崩溃时,接管的进程据此 + 识别并终止孤儿 Broker 子进程,从而真正掌握其生命周期。""" + return f"mqtt_broker_owner:{socket.gethostname()}" + + +def _pid_alive(pid) -> bool: + try: + os.kill(int(pid), 0) + return True + except (ProcessLookupError, OSError, ValueError, TypeError): + return False + + +def _terminate_pid(pid): + """尽力终止进程:先 SIGTERM,宽限后仍存活则 SIGKILL。""" + try: + os.kill(int(pid), signal.SIGTERM) + except (ProcessLookupError, OSError, ValueError, TypeError): + return + for _ in range(20): + if not _pid_alive(pid): + return + time.sleep(0.1) + try: + os.kill(int(pid), signal.SIGKILL) + except (ProcessLookupError, OSError, ValueError, TypeError): + pass + + +def _wait_port_free(port: int, host: str = "127.0.0.1", timeout: float = 3.0): + end = time.time() + timeout + while time.time() < end and is_port_in_use(port, host): + time.sleep(0.1) + + +def _run_mqtt_broker(host: str, port: int, username: str | None = None, + password: str | None = None): + """子进程入口:启动 amqtt Broker(配置了账号密码时启用认证)""" + # 父进程死亡防护:宿主 worker 死亡(含被 SIGKILL)时本子进程收 SIGTERM 退出, + # 不留孤儿——即便漏网,下次启动的孤儿接管逻辑也会兜底(见 setup_mqtt_broker) + guard_parent_death() + import asyncio as _asyncio + from amqtt.broker import Broker + + # amqtt 在 broker 进程内无 handler,WARNING 会经 lastResort 直接刷到 stderr: + # - sys_interval=0(禁用 $SYS)被 amqtt 当 falsy 报警 + # - 客户端连上却不发 CONNECT 时每次刷 2 行 "No data from"/"Failed to initialize..." + # 统一压到 ERROR:认证失败本身是 DEBUG 级不受影响,真实故障仍会输出 + import logging as _logging + + _logging.getLogger("amqtt").setLevel(_logging.ERROR) + + # ---- monkey-patch 1: amqtt StreamWriterAdapter.close() 忽略断开的传输层 ---- + from amqtt.adapters import StreamWriterAdapter as _SWA + + async def _patched_close(self): + if self.is_closed: + return + self.is_closed = True + try: + await self._writer.drain() + except (OSError, ConnectionError): + pass + try: + if self._writer.can_write_eof(): + self._writer.write_eof() + except OSError: + pass # 传输层已断开 (Errno 107) + try: + self._writer.close() + except OSError: + pass + with __import__("contextlib").suppress(AttributeError): + await self._writer.wait_closed() + + _SWA.close = _patched_close + + # ---- monkey-patch 2: 协议版本不匹配时不打 traceback(仅 warning) ---- + import amqtt.errors as _amqtt_err + from amqtt.utils import format_client_message as _fmt_client, gen_client_id as _gen_cid + from amqtt.broker import Broker as _Broker + from amqtt.mqtt.protocol.broker_handler import BrokerProtocolHandler as _BPH + + async def _patched_init_client(self, reader, writer, remote_address, remote_port): + """与原始方法逻辑一致,但 MQTTError 使用 warning 而非 exception""" + try: + handler, client_session = await _BPH.init_from_connect( + reader, writer, self.plugins_manager + ) + except _amqtt_err.AMQTTError as exc: + self.logger.warning( + "[MQTT-3.1.0-1] %s: Can't read first packet as CONNECT: %s", + _fmt_client(address=remote_address, port=remote_port), + exc, + ) + raise + except _amqtt_err.MQTTError as exc: + self.logger.warning( + "%s (client 可能使用 MQTT 5.0,amqtt 仅支持 3.1.1)", + str(exc), + ) + try: + await writer.close() + except OSError: + pass + raise + except _amqtt_err.NoDataError as exc: + self.logger.warning( + "No data from %s: %s", + _fmt_client(address=remote_address, port=remote_port), + exc, + ) + raise _amqtt_err.AMQTTError(exc) from exc + + if client_session.clean_session: + if client_session.client_id is not None and client_session.client_id != "": + await self._delete_session(client_session.client_id) + else: + client_session.client_id = _gen_cid() + + return handler, client_session + + _Broker._initialize_client_session = _patched_init_client + + async def _serve(): + # 认证插件:配置了账号密码 → FileAuthPlugin(argon2 密码文件,仅此账号可连); + # 未配置 → 匿名(默认行为) + auth_plugins = {} + passwd_file = None + if username and password: + import tempfile + from pwdlib import PasswordHash + + try: + fd, passwd_file = tempfile.mkstemp(prefix="chongming_mqtt_", suffix=".passwd") + with os.fdopen(fd, "w") as f: + f.write(f"{username}:{PasswordHash.recommended().hash(password)}\n") + auth_plugins = { + "amqtt.plugins.authentication.FileAuthPlugin": { + "password_file": passwd_file, + }, + } + except Exception as e: + logger.warning(f"MQTT 认证初始化失败,回退匿名模式: {e}") + if passwd_file: + os.unlink(passwd_file) + passwd_file = None + else: + auth_plugins = { + "amqtt.plugins.authentication.AnonymousAuthPlugin": {"allow_anonymous": True}, + } + + cfg = { + "listeners": { + "default": { + "type": "tcp", "bind": f"{host}:{port}", "max_connections": 500, + }, + }, + "timeout_disconnect_delay": 2, + "plugins": { + **auth_plugins, + "amqtt.plugins.sys.broker.BrokerSysPlugin": {"sys_interval": 0}, + }, + } + broker = Broker(cfg) + try: + await broker.start() + except Exception: + # 端口已被其他 worker 的 Broker 占用,静默退出 + return + finally: + # 密码文件仅在启动时读取,启动完成后即可删除 + if passwd_file: + try: + os.unlink(passwd_file) + except OSError: + pass + auth_note = f" (认证用户: {username})" if username else " (匿名)" + logger.info("MQTT Broker 已启动 @ %s:%s%s", host, port, auth_note) + try: + while True: + await _asyncio.sleep(1) + except (KeyboardInterrupt, _asyncio.CancelledError): + pass + finally: + await broker.shutdown() + + try: + _asyncio.run(_serve()) + except KeyboardInterrupt: + pass + + +def setup_storage(app: FastAPI): + """初始化本地文件存储(替代 Nanio S3,省 ~172 MB)""" + upload_root = os.path.join(os.getcwd(), "data", "uploads") + os.makedirs(upload_root, exist_ok=True) + app.state.upload_root = upload_root + logger.info(f"本地存储已就绪: {upload_root}") + + +async def setup_auth_store(app: FastAPI) -> None: + """初始化内置 auth 库(users 表 + admin 种子)—— auth 域的基础设施部分。 + + 拆分自 plugins.auth:登录/签发内置在网关(管理平面底线),auth 插件 + 缩容到 0 或全部副本故障时登录与 admin API 仍可用。库位置与 auth 插件 + 读同一份配置(config/workers/auth/config.toml 的 [workers.config]: + data_dir / database),指向同一张 users 表;插件配置缺失(如彻底移除 + auth 插件)时回退默认 ./data/auth.db。 + """ + from chongming_auth import open_database, seed_admin + + workers_conf = {} + auth_conf_path = "config/workers/auth/config.toml" + if os.path.isfile(auth_conf_path): + try: + with open(auth_conf_path, "rb") as f: + workers_conf = tomllib.load(f).get("workers", {}).get("config", {}) + except Exception as e: # noqa: BLE001 - 配置损坏不阻断启动,走默认路径 + logger.warning(f"无法读取 auth 插件配置 {auth_conf_path}: {e}(使用默认 data_dir)") + + db = await open_database(workers_conf) + created = await seed_admin(db) + app.state.auth_db = db + logger.info(f"内置 auth 库已就绪 (admin 种子: {'新建' if created else '已存在'})") + + +def setup_zenoh(app: FastAPI) -> zenoh.Session: + """建立 Zenoh 会话并存入 app.state。""" + zenoh_config = config.get("zenoh", {}) + + zconfig = zenoh.Config() + zconfig.from_file(zenoh_config.get("json5_path", "config/zenoh_config.json5")) + + try: + session = zenoh.open(zconfig) + logger.info("Zenoh 会话已建立 (TCP 自连接模式)") + except Exception as e: + logger.error(f"Zenoh 会话建立失败: {e}") + raise + + app.state.zenoh_session = session + return session + +def setup_worker_manager(app: FastAPI, session: zenoh.Session) -> WorkerManager: + """创建 WorkerManager,启动定期刷新任务,并存入 app.state。""" + cache = Cache.from_config(config["cache"]) + launch_worker_count = str( + config["cache"].get("launch_worker_count", "toml") + ).lower() + manager = WorkerManager(session, cache, launch_worker_count=launch_worker_count) + app.state.worker_manager = manager + + refresh_task = asyncio.create_task(manager.refresh_workers()) + app.state.refresh_task = refresh_task + return manager + +async def setup_route_registry( + app: FastAPI, + session: zenoh.Session, + manager: WorkerManager, + inline_types: list[str] | None = None, +): + """ + 启动动态路由注册表(常驻订阅 chongming/routes/**)。 + + 网关启动即订阅,不依赖网关侧是否登记 external 插件配置:external 插件 + 以独立进程运行(由 Docker/K8s 等编排拉起,网关不负责进程管理),其路由 + 描述符经 Zenoh 动态注册;inline 插件仍在网关进程内加载,两者同时存在 + (混合模式)。盘扫发现的 external 插件条目仅用于启动日志观测。 + + inline_types: 网关进程内加载的插件类型,注册表忽略其描述符以防重复挂载。 + + 注意:external 插件的自定义 APIRouter(如 multipart 上传端点)无法由 + 网关发现(网关不 import 该插件),此类端点需保持 inline 或等 P2 专门通道。 + """ + from ..core.route_registry import RouteRegistry + + prefix = config["default"]["prefix"] + registry = RouteRegistry(app, manager, prefix=prefix) + if inline_types: + registry.set_inline_types(set(inline_types)) + await registry.start(session) + app.state.route_registry = registry + logger.info( + f"路由注册表已启动(prefix={prefix}, 外部插件路由动态注册, " + f"inline 插件: {sorted(inline_types or []) or '无'})" + ) + return registry + + +def _discover_plugins() -> list[dict]: + """ + 发现所有插件配置。 + + 优先从各插件的独立配置文件 config/workers//config.toml 的 [workers] 节读取, + 兼容旧的 config/workers/config.toml 全局配置。 + + Returns: + [{"type": "doubao", "module": "plugins.doubao", "factory": "create_workers", + "cleanup": "cleanup", "count": 3, "config": {...}}, ...] + """ + plugins = [] + + # 1. 自动扫描 config/workers/*/config.toml + for path in sorted(glob.glob("config/workers/*/config.toml")): + plugin_type = os.path.basename(os.path.dirname(path)) + try: + with open(path, "rb") as f: + plugin_conf = tomllib.load(f) + except Exception as e: + logger.warning("无法读取插件配置 %s: %s", path, e) + continue + + workers_section = plugin_conf.get("workers", {}) + if not workers_section: + continue + + plugin_type = workers_section.get("type", plugin_type) + # 插件运行模式(按插件粒度,混合共存): + # "inline"(默认) = 网关进程内加载 + # "external" = 独立进程运行(Docker/K8s 编排),经 Zenoh 注册路由 + load_mode = workers_section.get("load_mode", "inline") + # 插件声明的 MQTT Bridge 额外订阅主题([[workers.mqtt_topics]]) + # 网关级路由配置,不传给插件运行时;worker 目标即本插件类型。 + mqtt_topics = [] + for t in workers_section.get("mqtt_topics", []) or []: + if not isinstance(t, dict) or not t.get("topic"): + continue + t = dict(t) + t.setdefault("worker", plugin_type) + mqtt_topics.append(t) + + # 合并运行时 config: + # 1. plugin_conf 顶层 (worker, worker.registration 等 gen_models 用) + # 2. [workers] 节的注册字段展平 (type, module, factory, cleanup, count) + # — 其中 type 被 chongming_worker.runtime.create_workers 用于匹配 RPC 方法 + # 3. [workers.config] 展平到顶层 (api_key, data_dir, heartbeat 等) + workers_flat = {k: v for k, v in workers_section.items() + if k not in ("config", "mqtt_topics", "load_mode")} + runtime_config = { + **plugin_conf, + **workers_flat, + **workers_section.get("config", {}), + } + + plugins.append({ + "type": plugin_type, + "module": workers_section.get("module", f"plugins.{plugin_type}"), + "factory": workers_section.get("factory", "create_workers"), + "cleanup": workers_section.get("cleanup", "cleanup"), + "count": workers_section.get("count", 1), + "load_mode": load_mode, + "mqtt_topics": mqtt_topics, + "config": runtime_config, + # [workers.config] 展平前的原始段:供 load_workers 按「配置优先来源模式」 + # 引导/覆盖共享缓存,使 inline 插件初始化即拿到缓存值(cache 模式) + "config_section": dict(workers_section.get("config") or {}), + }) + + # 2. 兼容旧写法: config/workers/config.toml(仅加载尚未发现的插件) + old_config_path = config.get("plugins_worker", {}).get("config_path", "config/workers/config.toml") + if os.path.isfile(old_config_path): + existing_types = {p["type"] for p in plugins} + old_conf = get_workers_config(old_config_path).get("workers", []) + for wc in old_conf: + wtype = wc.get("type", "") + if wtype and wtype not in existing_types: + plugins.append({ + "type": wtype, + "module": wc.get("module", ""), + "factory": wc.get("factory", ""), + "cleanup": wc.get("cleanup", ""), + "count": wc.get("count", 1), + "config": wc.get("config", {}), + }) + logger.info("从旧配置加载插件: %s (config/workers/config.toml)", wtype) + + return plugins + + +def _import_plugin_module(module_name: str): + """导入插件模块(`plugins.` 约定)。 + + 生产/打包环境里 plugins 包由模块银行(plugins.mbank)或宿主项目安装提供; + 开发态宿主通常是 src 布局(cwd/src/plugins 就在仓库里),此时首次导入可能 + 还不认识 plugins——这里兜底把 cwd/src 加入 sys.path 重试一次。兜底在本进程 + 的 import 处执行(非启动器),多 worker 各自生效,不依赖 fork/spawn 继承。 + """ + try: + return importlib.import_module(module_name) + except ImportError: + src = Path("src") + if module_name.split(".")[0] != "plugins" or not (src / "plugins").is_dir(): + raise + src_resolved = str(src.resolve()) + if src_resolved not in sys.path: + sys.path.insert(0, src_resolved) + logger.debug("开发态兜底:%s 加入 sys.path(宿主 src 布局)", src_resolved) + return importlib.import_module(module_name) + + +async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerManager, + is_inline_owner: bool = True): + """ + 加载 inline 插件,启动对应的 worker 任务(混合模式)。 + + 自动从 config/workers//config.toml 发现插件;load_mode="external" + 的插件不在网关内加载(由独立进程运行,其路由经常驻 RouteRegistry 动态 + 注册,无需网关侧保留插件配置,盘扫发现的 external 条目仅用于观测)。 + + 同时自动发现插件模块中暴露的 APIRouter(约定:模块顶层名为 `router` 的 + APIRouter 实例),收集后供 register_custom_routes() 统一注册。 + + is_inline_owner: 本进程是否为宿主级 inline worker 持有者。gunicorn 多 worker + 下仅持有者进程真正拉起 inline worker(避免 N×count 冗余副本);非持有者进程 + 仍 import 插件模块以便注册自定义路由/契约校验,但不 create_workers,worker 经 + Zenoh liveliness 被跨进程发现。单进程(uvicorn serve)时为 True,行为不变。 + + 返回: (worker_tasks, custom_routers, inline_types, external_types) + """ + workers_conf = _discover_plugins() + inline_plugins = [wc for wc in workers_conf if wc.get("load_mode", "inline") != "external"] + external_plugins = [wc for wc in workers_conf if wc.get("load_mode", "inline") == "external"] + inline_types = sorted({wc["type"] for wc in inline_plugins}) + external_types = sorted({wc["type"] for wc in external_plugins}) + logger.info( + f"发现 {len(workers_conf)} 个 worker 插件: " + f"inline={inline_types or '无'}, external={external_types or '无'} " + f"(本进程 inline 持有者: {is_inline_owner})" + ) + + for wc in external_plugins: + logger.info( + "插件 %s 为 external 模式,跳过进程内加载(由独立进程经 Zenoh 注册路由)", + wc["type"], + ) + + # 汇总 inline 插件声明的 MQTT Bridge 额外订阅主题,供 setup_mqtt 使用 + # (external 插件的主题由 RouteRegistry 经 __meta__ 动态推送) + app.state.plugin_mqtt_topics = [ + topic + for wc in inline_plugins + for topic in (wc.get("mqtt_topics") or []) + ] + + worker_tasks = [] + custom_routers = [] + for wc in inline_plugins: + module_name = wc["module"] + factory_name = wc["factory"] + # count 以 chongming-cache 为单一事实来源:cache 有值时用 cache 的全局总量, + # 否则用配置值引导并写回 cache(多网关共享同一 cache 时自动对齐)。 + count = wc["count"] + worker_type = wc["type"] + # [workers.config] 单一事实来源:按配置优先来源模式对齐(cache 模式取缓存值、 + # toml 模式以文件为准并覆盖缓存),必须在注册 starter/创建副本之前完成—— + # 插件实例(on_init 重建客户端等)用的就是这份配置。 + worker_config = manager.align_inline_config( + worker_type, wc["config"], wc.get("config_section") + ) + + if not module_name or not factory_name: + logger.warning(f"Worker 配置不完整: {wc}") + continue + + logger.debug("加载插件 %s: config/workers/%s/config.toml (count=%d)", + worker_type, worker_type, count) + + try: + module = _import_plugin_module(module_name) + + # 自动发现插件的自定义 FastAPI 路由(约定:模块暴露 `router` 属性) + plugin_router = getattr(module, "router", None) + if plugin_router is not None: + from fastapi import APIRouter + if isinstance(plugin_router, APIRouter): + custom_routers.append(plugin_router) + logger.info(f"发现插件自定义路由: {module_name}.router") + + # 非持有者进程:不立即拉起 inline worker,但仍在下方(build starter 后) + # 注册自愈启动器;worker 经 liveliness 被本进程发现调用,@rpc_method 与 + # 自定义路由/契约校验依赖的模块 import 已在上文完成不依赖拉起。 + factory = getattr(module, factory_name) + + # 定义"拉起本类型 worker"的协程,供持有者崩溃后的自愈接管复用; + # 用默认参数捕获循环变量,避免闭包迟到绑定。 + def _make_starter(session=session, config=worker_config, cnt=count, + mgr=manager, fac=factory): + async def starter(): + if inspect.iscoroutinefunction(fac): + return await fac(session, config, cnt, worker_manager=mgr) + return fac(session, config, cnt, worker_manager=mgr) + return starter + + starter = _make_starter() + manager.register_worker_starter(worker_type, starter) + + # 非持有者进程:不拉起 inline worker(启动器已注册,持有者崩溃时由 refresh 自愈接管) + if not is_inline_owner: + logger.info( + "本进程非 %s 的 inline 持有者,跳过创建副本(liveliness 发现即可调用)", + worker_type, + ) + continue + + tasks = await starter() + worker_tasks.extend(tasks) + logger.info(f"已加载 Worker 插件: {module_name}.{factory_name} (副本数: {count})") + except Exception as e: + import traceback + logger.error( + f"加载 Worker 插件失败 {module_name}: {e}\n" + f" 配置: type={worker_type}, count={count}\n" + f" 堆栈:\n{traceback.format_exc()}" + ) + + app.state.worker_tasks = worker_tasks + app.state.inline_types = inline_types + app.state.external_types = external_types + return worker_tasks, custom_routers, inline_types, external_types + +async def register_routes( + app: FastAPI, + manager: WorkerManager, + custom_routers: list | None = None, + worker_types: list[str] | None = None, +): + """注册动态路由和插件自定义路由。 + + worker_types: 仅注册这些插件类型的 TOML 动态路由(混合模式下只注册 + inline 插件;external 插件路由由 RouteRegistry 动态挂载)。 + """ + prefix = config["default"]["prefix"] + register_dynamic_routes(app, manager, prefix=prefix, worker_types=worker_types) + # 注册插件自定义路由(如 FORM-DATA 文件上传等 TOML 无法描述的端点) + # 统一加上全局 prefix(如 /api/v1),与 TOML 动态路由保持一致 + if custom_routers: + for r in custom_routers: + app.include_router(r, prefix=prefix) + logger.info(f"已注册 {len(custom_routers)} 个插件自定义路由(prefix={prefix})") + + +def validate_contracts(app: FastAPI | None = None): + """ + 契约校验(TOML 路由 ↔ @rpc_method 注册表 ↔ 生成模型),仅覆盖 inline 插件。 + + 在 load_workers 之后、register_routes 之前调用:此时插件模块已导入 + (注册表就绪)。HTTP 路由缺失对应 @rpc_method 时抛 RuntimeError 终止启动, + 其余不一致以警告日志汇总。 + + external 插件在自己进程内做同样校验(validate_worker_contracts),网关侧跳过。 + """ + from ..core.contracts import validate_contracts as _validate + + plugins = _discover_plugins() + inline_plugins = [p for p in plugins if p.get("load_mode", "inline") != "external"] + _validate(inline_plugins) + +async def setup_mqtt_broker(app: FastAPI, worker_manager: WorkerManager): + """ + 启动内嵌 amqtt Broker 子进程(宿主级分布式锁确保多 worker/多实例仅本宿主启动一个)。 + + 由配置 [.mqtt].use_embedded_broker 控制: + true (默认)→ 网关启动 amqtt Broker 子进程,host/port 为监听地址 + false → 使用外部 MQTT Broker,网关仅运行 Bridge 客户端,跳过本步 + 配置了 username/password 时 Broker 启用认证,否则保持匿名。 + + 排他锁基于 chongming-cache 的 DistributedLock,key 按宿主隔离(内嵌 Broker 绑定 + 本机端口)。持有者在缓存登记其 Broker 子进程 PID;前任持有者崩溃/被强杀而留下 + 孤儿 Broker 子进程时,接管的进程借此 PID 识别并终止之,再启动自己托管的子进程, + 从而真正掌握 Broker 生命周期(可关停、可接管),而非留下不可管控的残留下端点。 + """ + env_config = config.get(env, {}) + mqtt_config = env_config.get("mqtt", {}) + if not mqtt_config.get("enabled", True): + logger.info("MQTT 已禁用") + return None + if not mqtt_config.get("use_embedded_broker", True): + logger.info( + f"use_embedded_broker=false:使用外部 MQTT Broker " + f"({mqtt_config.get('host', 'localhost')}:{mqtt_config.get('port', 1883)})," + f"跳过内嵌 amqtt Broker 启动" + ) + return None + if worker_manager.cache is None: + logger.warning( + "MQTT 内嵌 Broker 启用但未配置共享 cache([cache]),无法获取排他锁;" + "请配置 redis 或共享卷 sqlite,或改用外部 Broker" + ) + return None + + host = mqtt_config.get("host", "localhost") + port = mqtt_config.get("port", 1883) + username = mqtt_config.get("username") or None + password = mqtt_config.get("password") or None + + lock = DistributedLock(worker_manager.cache, _broker_lock_key()) + if not lock.acquire(): + # 非持有者:等待持有者把 Broker 拉起并监听端口 + app.state.mqtt_broker_lock_acquired = False + app.state.mqtt_broker_lock = None + for _ in range(50): + if is_port_in_use(port, host): + logger.info(f"MQTT Broker 已在 {host}:{port} 运行(由其他进程托管)") + return None + time.sleep(0.1) + logger.warning(f"等待内嵌 MQTT Broker 端口 {port} 就绪超时") + return None + + # 本进程赢得宿主锁:持有租约续期,取得 Broker 托管权 + lock.start() + app.state.mqtt_broker_lock = lock + app.state.mqtt_broker_lock_acquired = True + + # 端口已被监听:优先识别并终止前任残留的孤儿 Broker 子进程,以真正接管其 + # 生命周期;否则视为不可托管的既有进程,复用(本进程不持有句柄)。 + try: + prev_pid = int(worker_manager.cache.get(_broker_owner_key()) or 0) + except (TypeError, ValueError): + prev_pid = None + if is_port_in_use(port, host): + if prev_pid and _pid_alive(prev_pid) and prev_pid != os.getpid(): + logger.warning( + f"[mqtt-broker] 检出孤儿 Broker 子进程 PID {prev_pid},终止并接管重启" + ) + _terminate_pid(prev_pid) + _wait_port_free(port, host) + if is_port_in_use(port, host): + logger.info( + f"MQTT Broker 端口 {port} 在终止孤儿后仍被占用,属不可托管进程,复用" + ) + return None + else: + logger.info(f"MQTT Broker 已在 {host}:{port} 运行,复用(不可托管)") + return None + + proc = multiprocessing.Process( + target=_run_mqtt_broker, args=(host, port, username, password), daemon=False + ) + proc.start() + app.state.mqtt_broker_process = proc + worker_manager.cache.set(_broker_owner_key(), str(proc.pid)) + auth_note = f",认证用户 {username}" if username else "(匿名)" + logger.info(f"MQTT Broker 子进程已启动并登记托管 (PID: {proc.pid}){auth_note}") + for _ in range(50): + if is_port_in_use(port, host): + logger.info(f"MQTT Broker 已监听 {port} 端口") + return None + if not proc.is_alive(): + raise RuntimeError("MQTT Broker 进程异常退出") + time.sleep(0.1) + raise RuntimeError("MQTT Broker 端口未就绪") + + +async def shutdown_mqtt_broker(app: FastAPI): + """终止本进程托管的 MQTT Broker 子进程,并释放宿主级分布式排他锁、清理登记。""" + proc = getattr(app.state, "mqtt_broker_process", None) + if proc and proc.is_alive(): + proc.terminate() + proc.join(timeout=5) + if proc.is_alive(): + proc.kill() + logger.info("MQTT Broker 子进程已终止") + lock = getattr(app.state, "mqtt_broker_lock", None) + if lock is not None: + if proc: # 仅真正托管了子进程的进程才清理孤儿登记,避免误删他人登记 + try: + lock._cache.delete(_broker_owner_key()) + except Exception as e: # noqa: BLE001 + logger.debug(f"清理 MQTT Broker 登记失败: {e}") + await lock.stop() + lock.release() + app.state.mqtt_broker_lock = None + app.state.mqtt_broker_lock_acquired = False + + +async def _start_mqtt(app: FastAPI, worker_manager: WorkerManager, + bridge_lock) -> MQTTBridge | None: + """在已持有分布式锁的前提下构建并启动一个 MQTT Bridge 客户端。 + + 该逻辑同时被 setup_mqtt(启动时)与 mqtt_bridge_supervisor(持有者自愈接管时) + 复用。假定调用方已 acquire() 到 "mqtt_bridge" 分布式锁。 + """ + env_config = config.get(env, {}) + mqtt_config = env_config.get("mqtt", {}) + host = mqtt_config.get("host", "localhost") + port = mqtt_config.get("port", 1883) + username = mqtt_config.get("username") or None + password = mqtt_config.get("password") or None + custom_topics = list(mqtt_config.get("custom_topics", []) or []) + custom_topics += list(getattr(app.state, "plugin_mqtt_topics", []) or []) + + bridge = MQTTBridge( + worker_manager=worker_manager, + host=host, + port=port, + custom_topics=custom_topics, + username=username, + password=password, + ) + app.state.mqtt_bridge = bridge + await bridge.start() + bridge_lock.start() # 后台续约租约,防止持有方宕机/长暂停导致锁被错误抢走 + app.state.mqtt_bridge_lock = bridge_lock + # 把 Bridge 挂接给常驻路由注册表(动态主题 + 补推已到达的元信息) + registry = getattr(app.state, "route_registry", None) + if registry is not None: + registry.attach_bridge(bridge) + broker_mode = "内嵌 amqtt" if mqtt_config.get("use_embedded_broker", True) else "外部" + auth_note = f",认证用户 {username}" if username else "(匿名)" + logger.info(f"MQTT Bridge 已启动({broker_mode} Broker {host}:{port}{auth_note})") + return bridge + + +async def setup_mqtt(app: FastAPI, worker_manager: WorkerManager) -> MQTTBridge | None: + """ + 启动 MQTT Bridge(纯客户端模式)。 + + 从 config.toml 的环境段读取 host/port/enabled;若 enabled 为 false 则跳过。 + Broker 来源由 use_embedded_broker 控制: + true (默认)= 内嵌 amqtt Broker(lifespan 第 3 步已启动子进程) + false = 外部 MQTT Broker(host/port 指向外部地址,网关不启动 Broker) + 订阅主题 = 全局 [.mqtt].custom_topics(可选兜底) + + 各插件在其 config/workers//config.toml 中声明的 [[workers.mqtt_topics]]。 + 使用基于共享 cache 的分布式锁确保多进程/多网关中全局只有一个 Bridge 消费。 + """ + env_config = config.get(env, {}) + mqtt_config = env_config.get("mqtt", {}) + if not mqtt_config.get("enabled", True): + logger.info("MQTT 已禁用") + return None + + # 排他锁:基于共享缓存(redis / 共享卷 diskcache),保证全局只有一个 Bridge 消费。 + # 相比原文件锁,可跨多台主机收敛——多实例共享同一个外部 Broker 时, + # 避免一条 MQTT 请求被 N 台网关上各自消费、各回一份响应。 + bridge_lock = DistributedLock(worker_manager.cache, "mqtt_bridge") # pyright: ignore[reportArgumentType] + if not bridge_lock.acquire(): + logger.info( + "MQTT Bridge 已由其他网关实例持有(分布式锁),本网关跳过," + "以规避共享 Broker 下单请求多响应" + ) + return None + try: + return await _start_mqtt(app, worker_manager, bridge_lock) + except Exception as e: # noqa: BLE001 + logger.error(f"MQTT Bridge 启动失败: {e}") + await bridge_lock.stop() + bridge_lock.release() + app.state.mqtt_bridge = None + app.state.mqtt_bridge_lock = None + return None + + +async def mqtt_bridge_supervisor(app: FastAPI, worker_manager: WorkerManager): + """MQTT Bridge 持有者崩溃自愈巡检(跨所有网关进程运行)。 + + 与 inline 持有者同源的问题:仅靠 gunicorn 复活死掉 slot 无法 failover——新进程 + 可能在旧锁租约未过期时抢锁失败而照样跳过。本巡检在每个网关进程周期性运行: + + - 本进程已持有一存活 Bridge → 正常托管,跳过。 + - 否则尝试获得 "mqtt_bridge" 分布式锁(set_nx 保证全局唯一胜出,无分裂); + 若锁仍被存活持有者把持 → 跳过;若空闲(原持有者租约过期=已死/失联)→ 接管。 + - 接管时按需拉起内嵌 Broker(setup_mqtt_broker 幂等:端口被占则复用现有进程), + 再重建 Bridge 客户端。 + + 运行周期经 [.mqtt].bridge_supervise_interval 配置,默认 15s。 + """ + env_config = config.get(env, {}) + mqtt_config = env_config.get("mqtt", {}) + if not mqtt_config.get("enabled", True): + return # MQTT 禁用:无需巡检 + interval = mqtt_config.get("bridge_supervise_interval", 15) or 15 + while True: + await asyncio.sleep(interval) + try: + bridge = getattr(app.state, "mqtt_bridge", None) + # 本进程持有一存活 Bridge:正常托管 + if bridge is not None and getattr(bridge, "is_running", True): + continue + + newly_acquired = False + lock = getattr(app.state, "mqtt_bridge_lock", None) + if lock is None: + # 非持有者:仅当锁空闲(原持有者租约过期)时才能抢得 + if worker_manager.cache is None: + continue + candidate = DistributedLock(worker_manager.cache, "mqtt_bridge") + if not candidate.acquire(): + continue # 仍被存活持有者把持 + candidate.start() + app.state.mqtt_bridge_lock = candidate + lock = candidate + newly_acquired = True + + logger.warning("[mqtt-bridge] 检出持有者失效,接管重建 Broker+Bridge") + # 按需拉起内嵌 Broker(幂等;外部 Broker 时 setup_mqtt_broker 内直接返回) + try: + await setup_mqtt_broker(app, worker_manager) + except Exception as e: # noqa: BLE001 + logger.error(f"[mqtt-bridge] 接管时拉起 Broker 失败: {e}") + try: + await _start_mqtt(app, worker_manager, lock) + logger.info("[mqtt-bridge] 接管完成,Bridge 已重建") + except Exception as e: # noqa: BLE001 + logger.error(f"[mqtt-bridge] 接管时重建 Bridge 失败: {e}") + app.state.mqtt_bridge = None + if newly_acquired: + await lock.stop() + lock.release() + app.state.mqtt_bridge_lock = None + except Exception as e: # noqa: BLE001 + logger.error(f"MQTT Bridge 自愈巡检失败: {e}") + + +async def shutdown_mqtt(app: FastAPI): + """停止 MQTT Bridge 和 Broker 子进程(仅锁持有者释放锁)""" + bridge: MQTTBridge | None = getattr(app.state, "mqtt_bridge", None) + if bridge and bridge.is_running: + await bridge.stop() + # 停止续约并受保护释放分布式锁(仅本网关仍持有时删除) + bridge_lock = getattr(app.state, "mqtt_bridge_lock", None) + if bridge_lock is not None: + await bridge_lock.stop() + bridge_lock.release() + await shutdown_mqtt_broker(app) + + +async def cleanup_workers(app: FastAPI): + """调用每个 inline worker 配置中定义的 cleanup 函数。 + + 仅 inline worker 持有者进程执行:非持有者进程未初始化插件(未调用 create_workers, + on_init 未执行),cleanup/on_cleanup 无资源可清,且可能引用未创建的对象而误报错。 + """ + # app.state.inline_owner_locked 恒为 bool(lifespan 4.2 设置),缺省按持有处理 + if not getattr(app.state, "inline_owner_locked", True): + logger.info("本进程非 inline worker 持有者,跳过插件 cleanup") + return + workers_conf = _discover_plugins() + + for wc in workers_conf: + if wc.get("load_mode", "inline") == "external": + continue + module_name = wc.get("module", "") + cleanup_name = wc.get("cleanup", "") + worker_type = wc.get("type", "") + + if not module_name or not cleanup_name: + logger.warning(f"Worker 配置缺少 cleanup: {wc}") + continue + + try: + module = _import_plugin_module(module_name) + cleanup = getattr(module, cleanup_name) + pid = os.getpid() + worker_id = f"worker/{worker_type}/worker-i-{pid}" + if inspect.iscoroutinefunction(cleanup): + await cleanup(worker_id) + else: + cleanup(worker_id) + except Exception as e: + logger.error(f"cleanup 调用失败 {module_name}: {e}") + \ No newline at end of file diff --git a/src/chongming/app/core/logger.py b/src/chongming/app/core/logger.py new file mode 100644 index 0000000..0357351 --- /dev/null +++ b/src/chongming/app/core/logger.py @@ -0,0 +1,421 @@ +import re +import os +import atexit +import queue +import logging +import datetime +from pathlib import Path +from typing import List, Optional +from logging.handlers import RotatingFileHandler, QueueHandler, QueueListener + +from chongming_config import get_config + + +config = get_config() +env = config["default"]["env"] +server_config = config[env]["server"] +log_config = config[env]["logging"] + + +class ColoredFormatter(logging.Formatter): + """ + 为 HTTP 状态码添加颜色的日志格式化器 + + 根据 HTTP 状态码的首位数字为日志消息中的状态码添加不同颜色: + - 2xx: 绿色(成功) + - 3xx: 青色(重定向) + - 4xx: 黄色(客户端错误) + - 5xx: 红色(服务器错误) + + Attributes: + COLORS: 状态码首位数字到 ANSI 颜色代码的映射字典 + RESET: ANSI 重置颜色代码 + """ + + COLORS = { + "2": "\033[32m", # 绿色 2xx + "3": "\033[36m", # 青色 3xx + "4": "\033[33m", # 黄色 4xx + "5": "\033[31m", # 红色 5xx + } + RESET = "\033[0m" + + def format(self, record): + """ + 格式化日志记录,为状态码添加颜色 + + 如果日志记录包含 status_code 属性,则查找消息中的 "status=数字" 模式 + 并为其添加对应的颜色代码。格式化完成后恢复原始消息以避免影响其他 handler。 + + Args: + record: 日志记录对象,应包含 msg 属性和可选的 status_code 属性 + + Returns: + str: 格式化后的日志消息字符串,状态码部分可能包含 ANSI 颜色代码 + """ + # 保存原始消息 + original_msg = record.msg + # 如果日志记录包含 status_code 属性,则为其添加颜色 + status_code = getattr(record, "status_code", None) + if status_code and isinstance(status_code, int): + first_digit = str(status_code)[0] + color = self.COLORS.get(first_digit, "") + colored_status = f"{color}{status_code}{self.RESET}" + # 替换消息中的 "status=数字" 为带颜色的版本 + record.msg = re.sub( + rf"status={status_code}\b", f"status={colored_status}", original_msg + ) + result = super().format(record) + # 恢复原始消息,避免影响其他 handler + record.msg = original_msg + return result + + +class DailyRotatingFileHandler(RotatingFileHandler): + """ + 按自然日切分 + 大小轮转的文件处理器 + + 日志文件名形如 {log_name}-2026-08-19.log:进程启动时按当天日期命名, + 跨天后写入时自动切换到新日期文件,避免长时间运行的进程把跨天日志 + 全部堆积到启动当天的文件里。同时保留 RotatingFileHandler 的大小轮转 + (单文件超限追加 .1/.2 后缀)。 + + 历史文件(跨天文件与大小备份)按修改时间保留最近 backup_count 个, + 超出部分在切换日期的瞬间清理。 + """ + + def __init__( + self, + log_dir: str, + log_name: str, + max_bytes: int, + backup_count: int, + encoding: str = "utf-8", + ): + self.log_dir = Path(log_dir) + self.log_name = log_name + self._current_date = datetime.date.today() + super().__init__( + str(self.log_dir / f"{log_name}-{self._current_date}.log"), + encoding=encoding, + maxBytes=max_bytes, + backupCount=backup_count, + ) + + def _build_filename(self, day: datetime.date) -> str: + return str(self.log_dir / f"{self.log_name}-{day}.log") + + def _cleanup_old_files(self) -> None: + """保留最近 backup_count 个日志文件,删除更早的历史文件""" + # 按 (日期, 轮转序号) 排序,不依赖 mtime(同日备份可能同时落盘): + # 日期越大越新;同日内主文件(.log)最新,.N 备份按 N 递减更旧 + pattern = re.compile( + rf"^{re.escape(self.log_name)}-(\d{{4}}-\d{{2}}-\d{{2}})\.log(?:\.(\d+))?$" + ) + + def sort_key(path: Path): + match = pattern.match(path.name) + if not match: + return (datetime.date.min, 0) + return ( + datetime.date.fromisoformat(match.group(1)), + -int(match.group(2) or 0), + ) + + files = sorted( + self.log_dir.glob(f"{self.log_name}-*.log*"), + key=sort_key, + reverse=True, + ) + for old in files[self.backupCount :]: + try: + old.unlink() + except OSError: + pass + + def emit(self, record): + # 跨天:关闭旧文件流,切换到新日期文件,并清理过期历史文件 + today = datetime.date.today() + if today != self._current_date: + self._current_date = today + self.close() + self.baseFilename = self._build_filename(today) + self.stream = self._open() + self._cleanup_old_files() + super().emit(record) + + +class LoggerNameFilter(logging.Filter): + """ + 按 log_name 路由日志记录的过滤器 + + 只放行本 log_name 自身及其子 logger 的记录(如 chongming_worker.runtime + 归入 chongming_worker),保证共享 QueueListener 下每个 handler 只处理 + 属于自己的记录:控制台每行只打印一次,各日志文件按名隔离。 + """ + + def __init__(self, log_name: str): + super().__init__() + self._name = log_name + self._prefix = log_name + "." + + def filter(self, record): + return record.name == self._name or record.name.startswith(self._prefix) + + +class ExcludeWatchFilesFilter(logging.Filter): + """ + 日志过滤器,用于排除第三方库的日志输出 + + 过滤掉来自 watchfiles、aiosqlite、apscheduler、sqlalchemy 等库的日志记录, + 避免这些库的详细日志污染应用日志输出。 + """ + + def __init__(self): + """ + 初始化过滤器,设置需要过滤的日志名称前缀列表 + """ + super().__init__() + self._filter_starts_with_tulpe = ( + "watchfiles", + "aiosqlite", + "apscheduler", + "sqlalchemy", + "sqlitedict", + "sqlalchemy.engine.Engine", + ) + + def filter(self, record): + """ + 判断是否应该保留该日志记录 + + Args: + record: 日志记录对象,包含 name 属性表示日志来源 + + Returns: + bool: 如果日志名称不以过滤列表中的任何前缀开头则返回 True(保留), + 否则返回 False(过滤掉) + """ + return not record.name.startswith(self._filter_starts_with_tulpe) + + +# ---- 异步日志队列(QueueHandler + QueueListener)---- +# 主线程/事件循环只做"入队"(LogRecord 创建 + put,µs 级),文件/控制台 I/O +# 由后台监听线程执行,日志记录不再阻塞业务协程。 +# 队列满时丢弃新日志(日志系统故障不影响业务);错误日志(>=400)不受采样影响。 + +_LOG_QUEUE_SIZE = 10000 + + +class _AsyncQueueHandler(QueueHandler): + """非阻塞入队 + 惰性格式化。 + + - 队列满时丢弃(而不是抛 queue.Full 打断业务) + - 不预格式化:% 样式消息的格式化推迟到监听线程(formatter 执行), + 主线程只承担 LogRecord 创建与入队 + - queue 经模块全局读取:gunicorn preload/fork 后自动指向子进程的新队列 + """ + + @property + def queue(self): + return _log_queue + + def __init__(self): + # queue 由 property 动态提供(fork 后自动指向新队列)。 + # 只初始化 Handler 基类(level/filters 等),不走 QueueHandler.__init__ + # 的 self.queue = queue 赋值(会与 property 冲突) + logging.Handler.__init__(self) + + def enqueue(self, record): + try: + self.queue.put_nowait(record) + except queue.Full: + pass # 队列满:丢弃该日志 + + def prepare(self, record): + return record # 格式化留给监听线程的 formatter + + +class _RegistryListener(QueueListener): + """handler 可动态注册的 QueueListener(每个 log_name 有独立 file handler)""" + + def __init__(self, q: queue.Queue): + super().__init__(q) + self.handlers: list = [] # 可变列表,运行期可追加 + + def register(self, *handlers) -> None: + self.handlers.extend(handlers) + + +_log_queue: queue.Queue = queue.Queue(maxsize=_LOG_QUEUE_SIZE) +_listener: Optional[_RegistryListener] = None +# 全部真实 handler(文件/控制台),供 fork 后重建监听线程 +_listener_handlers: List[logging.Handler] = [] + + +def _start_listener() -> None: + global _listener + _listener = _RegistryListener(_log_queue) + _listener.register(*_listener_handlers) + _listener.start() + + +def _reopen_handler_stream(handler: logging.Handler) -> None: + """fork 后在子进程里重新打开文件 handler 的流(控制台等非文件 handler 不动)。 + + 子进程继承的是父进程的 BufferedWriter:内部锁/owner 状态在 fork 瞬间可能正被 + 父进程的监听线程占用(跨 fork 复制不可靠),子进程续写会**每条日志**都报 + "reentrant call inside <_io.BufferedWriter>",文件日志整体失效;同时父子 fd + 共享同一文件偏移,交错写还会互相踩。关闭继承流、在子进程里重新 open 即避开。 + """ + open_stream = getattr(handler, "_open", None) + if not callable(open_stream): + return # 非文件 handler(控制台):继承的 stderr 是刻意共享的 + try: + if handler.stream is not None: + handler.stream.close() + except Exception: # noqa: BLE001 - 继承流状态可能已损坏,关不掉就丢弃引用 + pass + try: + handler.stream = open_stream() + except Exception: # noqa: BLE001 + handler.stream = None + + +def _after_fork_child() -> None: + """gunicorn/granian preload/fork 场景:线程不跨 fork,子进程重建队列与监听线程。 + + 队列重建避免 fork 时锁状态不可用导致的死锁;文件 handler 的流也要在子进程里 + 重开(见 _reopen_handler_stream),否则子进程的文件日志会持续报 reentrant。 + 旧队列中的记录在 fork 瞬间丢失(启动窗口内可接受)。 + """ + global _log_queue + _log_queue = queue.Queue(maxsize=_LOG_QUEUE_SIZE) + for handler in _listener_handlers: + _reopen_handler_stream(handler) + _start_listener() + + +if hasattr(os, "register_at_fork"): + os.register_at_fork(after_in_child=_after_fork_child) + + +def shutdown_logging() -> None: + """停止监听线程并冲刷剩余日志(进程退出 / 应用关闭时调用),幂等。 + + QueueListener.stop() 停止后会将 _thread 置 None,重复调用需跳过; + 幂等保证 atexit 与显式调用共存。 + """ + global _listener + if _listener is not None: + if _listener._thread is not None: + _listener.stop() # flush=True:处理完队列中已有记录再停止 + _listener = None + for h in _listener_handlers: + try: + h.close() + except Exception: # pragma: no cover - 关闭失败不影响退出 + pass + + +atexit.register(shutdown_logging) + + +_configured_loggers = set() +# 每个进程只打印一次日志配置信息(setup_logging 每个 log_name 都会调用一次) +_config_info_printed = False + + +def setup_logging(logger: logging.Logger, log_name: str = "app"): + """ + 配置日志系统,包括文件和控制台输出 + + 为指定的 logger 配置以下功能: + 1. 设置日志级别和格式 + 2. 创建按自然日切分的轮转文件处理器(跨天自动切换文件,支持文件大小限制和备份数量控制) + 3. 创建带颜色格式化的控制台处理器 + 4. 应用过滤器排除第三方库日志 + + 该函数具有防重复配置机制,同一个 log_name 只会配置一次。 + + Args: + logger: 需要配置的 logging.Logger 实例 + log_name: 日志名称标识,用于区分不同的日志配置和生成日志文件名,默认为 "app" + + Returns: + None + + Raises: + 无显式抛出异常,但可能因文件系统权限问题导致目录创建或文件写入失败 + """ + if log_name in _configured_loggers: + return + _configured_loggers.add(log_name) + log_level = getattr(logging, log_config["level"], logging.INFO) + log_format = log_config["format"] + # 创建文件夹 + log_file_path = Path(log_config["file"]) + if log_file_path != Path("."): + log_file_path.mkdir(parents=True, exist_ok=True) + + # 配置根日志记录器 + logger.setLevel(log_level) + + # 创建格式化器 + plain_formatter = logging.Formatter(log_format) + color_formatter = ColoredFormatter(log_format) + + # 创建按自然日切分的轮转文件处理器(跨天自动切文件,单文件超限再按大小轮转) + max_bytes = log_config.get("max_size", 10 * 1024 * 1024) # 默认 10MB + backup_count = log_config.get("backup_count", 5) # 默认保留 5 个历史文件 + + file_handler = DailyRotatingFileHandler( + log_config["file"], + log_name, + max_bytes=max_bytes, + backup_count=backup_count, + ) + file_handler.setFormatter(plain_formatter) + file_handler.addFilter(ExcludeWatchFilesFilter()) + file_handler.addFilter(LoggerNameFilter(log_name)) + + # 创建控制台处理器并添加过滤器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(color_formatter) + console_handler.addFilter(ExcludeWatchFilesFilter()) + console_handler.addFilter(LoggerNameFilter(log_name)) + + # 真实 handler(文件/控制台)挂到后台监听线程;logger 上只挂队列 handler, + # 日志调用在主线程仅入队,I/O 由监听线程执行 + _listener_handlers.extend([file_handler, console_handler]) + if _listener is None: + _start_listener() + else: + _listener.register(file_handler, console_handler) + logger.addHandler(_AsyncQueueHandler()) + + global _config_info_printed + if not _config_info_printed: + _config_info_printed = True + print( + f"日志系统已配置 - 级别: {logging.getLevelName(log_level)}, 文件: {log_config['file']} " + f"(最大:{max_bytes / 1024 / 1024:.1f}MB, 备份:{backup_count})" + ) + + +def get_logger(name: str) -> logging.Logger: + """ + 获取配置好的日志记录器实例 + + 根据名称获取或创建 logger 实例,并自动调用 setup_logging 进行配置。 + 如果该名称的 logger 已经配置过,则直接返回已配置的实例。 + + Args: + name: 日志记录器名称,通常使用模块名如 __name__ + + Returns: + logging.Logger: 已配置完成的日志记录器实例,可直接用于记录日志 + """ + logger = logging.getLogger(name) + setup_logging(logger, name) + return logger diff --git a/src/chongming/app/core/mqtt_bridge.py b/src/chongming/app/core/mqtt_bridge.py new file mode 100644 index 0000000..25d1f03 --- /dev/null +++ b/src/chongming/app/core/mqtt_bridge.py @@ -0,0 +1,271 @@ +"""MQTT Bridge — 纯客户端模式,Broker 由子进程管理""" +import asyncio +import orjson +import re +from typing import Optional +from urllib.parse import quote + +from amqtt.client import MQTTClient +from amqtt.mqtt.constants import QOS_1 +from .logger import get_logger + +logger = get_logger("mqtt") +MQTT_TOPIC_PREFIX = "chongming" +MQTT_SUBSCRIBE_TOPIC = f"{MQTT_TOPIC_PREFIX}/+/+" + +# 已按"非信封载荷"路由过告警的主题(每主题只告警一次,避免设备 5s 心跳刷屏) +_warned_legacy_topics: set = set() + + +def _compile_topic(topic: str) -> re.Pattern: + """ + 将 MQTT 主题模板(支持 + / # 通配符)编译为正则表达式。 + + - ``+`` 匹配单级(编译为捕获组 ``([^/]+)``) + - ``#`` 匹配多级(编译为捕获组 ``(.*)``,须位于末尾) + 捕获组按出现顺序对应,供 {1} {2}... 占位符替换使用。 + """ + parts = topic.split("/") + regex = "^" + for i, part in enumerate(parts): + if i > 0: + regex += "/" + if part == "#": + regex += "(.*)" + elif part == "+": + regex += "([^/]+)" + else: + regex += re.escape(part) + regex += "$" + return re.compile(regex) + + +class MQTTBridge: + def __init__(self, worker_manager, host="localhost", port=1883, custom_topics=None, + username=None, password=None): + self._wm = worker_manager + self._host = host + self._port = port + self._username = username + self._password = password + self._client: Optional[MQTTClient] = None + self._lt: Optional[asyncio.Task] = None + self._running = False + # custom_topics: [{"topic","worker","subject"?,"response_topic"?}, ...] + # 混合模式双来源: + # _static_topics — inline 插件,构造时由网关从配置收集 + # _dynamic_topics — external 插件,运行时由 RouteRegistry 推送 + self._static_topics = custom_topics or [] + self._dynamic_topics: list = [] + self._compiled: list = [] + self._rebuild_compiled() + self._subscribed_custom: set = set() + + def _rebuild_compiled(self): + """合并静态/动态主题并重编译匹配正则。""" + merged = self._static_topics + self._dynamic_topics + self._compiled = [ + (ct, _compile_topic(ct.get("topic", ""))) + for ct in merged if ct.get("topic") + ] + + async def start(self): + if self._running: + return + try: + await self._sc() + except Exception as e: + logger.warning(f"MQTT 不可用: {e}") + return + self._running = True + logger.info(f"MQTT Bridge 已连接 {self._host}:{self._port}") + + async def stop(self): + if not self._running: + return + self._running = False + await self._sc_stop() + + def _broker_uri(self) -> str: + """构建 Broker URI;凭据经百分号编码(amqtt 连接时自动 unquote)。""" + if self._username: + user = quote(self._username, safe="") + pwd = quote(self._password or "", safe="") + return f"mqtt://{user}:{pwd}@{self._host}:{self._port}/" + return f"mqtt://{self._host}:{self._port}/" + + async def _sc(self): + self._client = MQTTClient(config={"auto_reconnect": False}) + await self._client.connect(self._broker_uri()) + topics = [(MQTT_SUBSCRIBE_TOPIC, QOS_1)] + topics += [(ct["topic"], QOS_1) for ct, _ in self._compiled] + await self._client.subscribe(topics) + self._subscribed_custom = {ct["topic"] for ct, _ in self._compiled} + logger.info(f"已订阅 {MQTT_SUBSCRIBE_TOPIC}" + + (f" 及 {len(self._compiled)} 个自定义主题" if self._compiled else "")) + self._lt = asyncio.create_task(self._loop()) + + async def update_dynamic_topics(self, custom_topics: list): + """ + 动态更新 external 插件的自定义订阅主题(RouteRegistry 推送)。 + + 与构造时的 inline 静态主题合并后增量重订阅(退订已移除主题、订阅新增主题), + 运行中调用即生效。 + """ + self._dynamic_topics = list(custom_topics or []) + self._rebuild_compiled() + new_topics = {ct["topic"] for ct, _ in self._compiled} + if not (self._running and self._client): + self._subscribed_custom = new_topics + return + removed = self._subscribed_custom - new_topics + added = new_topics - self._subscribed_custom + if removed: + await self._client.unsubscribe(list(removed)) + if added: + await self._client.subscribe([(t, QOS_1) for t in added]) + self._subscribed_custom = new_topics + if added or removed: + logger.info( + f"自定义订阅主题更新: +{len(added)} -{len(removed)}" + f"(当前 {len(new_topics)} 个)" + ) + + async def _sc_stop(self): + # 先断开连接,让 deliver_message() 解锁 + if self._client: + try: + await self._client.disconnect() + except Exception: + pass + self._client = None + # 再取消消息循环 + if self._lt and not self._lt.done(): + self._lt.cancel() + try: + await self._lt + except asyncio.CancelledError: + pass + + async def _loop(self): + while self._running: + try: + m = await self._client.deliver_message() # type: ignore + if m is None: + break + await self._handle(m) + except asyncio.CancelledError: + break + except Exception as e: + if self._running: + logger.error(f"消息异常: {e}") + break + + async def _handle(self, m): + t: str = m.topic + p = m.data # type: ignore + try: + pl = orjson.loads(p) + except orjson.JSONDecodeError: + return + + # 实例定向控制字段:与 HTTP 的 X-Chongming-Instance 头同义,指定请求发往的 + # worker 进程实例。必须在下面归一化之前取出——归一化只保留 meta/data 两键, + # 之后控制字段就不在载荷里了。 + target_instance: Optional[str] = None + if isinstance(pl, dict): + ti = pl.pop("target_instance", None) + if isinstance(ti, str) and ti.strip(): + target_instance = ti.strip() + + # 信封协议: {"meta": {...}, "data": {...}} + # 兼容非信封载荷(如第三方设备原始报文): 整包视为业务数据、meta 置空, + # 与 worker 运行时 runtime.py 的容错语义一致(method 默认 GET)—— + # 若在此丢弃,盒子心跳等设备报文将永远到不了 Worker。 + if not isinstance(pl, dict) or ("meta" not in pl and "data" not in pl): + data = pl if isinstance(pl, dict) else {} + pl = {"meta": {}, "data": data} + if t not in _warned_legacy_topics: + _warned_legacy_topics.add(t) + logger.warning( + "非信封格式载荷(缺少 meta/data)按原始报文路由 [%s]: " + "整包视为 data,建议设备侧改用 {'meta': {...}, 'data': {...}} 格式", + t, + ) + else: + meta = pl.get("meta") or {} + data = pl.get("data") or {} + if not isinstance(data, dict): + data = {} + pl = {"meta": meta, "data": data} + + # 1. 优先匹配显式配置的自定义订阅主题(顺序即优先级) + for ct, compiled in self._compiled: + matched = compiled.match(t) + if matched: + await self._route_custom(ct, matched, pl, t, target_instance) + return + + # 2. 内置接口 chongming/{worker}/{operation} + parts = t.split("/") + if len(parts) < 3: + return + wt, op = parts[1], parts[2] + subj = f"{wt}.{op}" + rt = f"{MQTT_TOPIC_PREFIX}/{wt}/{op}/response" + await self._route(wt, subj, pl, rt, target_instance) + + def _fill(self, template: str, matched: re.Match) -> str: + """用匹配到的通配符值填充模板中的 {1} {2}... 占位符""" + if "{" not in template: + return template + for i, g in enumerate(matched.groups(), start=1): + template = template.replace("{" + str(i) + "}", g) + return template + + async def _route_custom(self, ct: dict, matched: re.Match, pl: dict, topic: str, + target_instance: Optional[str] = None): + worker = ct.get("worker", "") + # subject 默认 ".",与内置 <插件>.<操作> 约定一致 + subject_tpl = ct.get("subject") + if not subject_tpl: + last = ct["topic"].split("/")[-1] + subject_tpl = f"{worker}.{last}" if last not in ("+", "#") else worker + subject = self._fill(subject_tpl, matched) + # response_topic 默认 "<实际匹配主题>/response" + rt_tpl = ct.get("response_topic") + rt = self._fill(rt_tpl, matched) if rt_tpl else f"{topic}/response" + await self._route(worker, subject, pl, rt, target_instance) + + async def _route(self, wt: str, subj: str, pl: dict, rt: str, + target_instance: Optional[str] = None): + """把一次 MQTT 请求转成 worker RPC。 + + target_instance 为实例定向目标(来自载荷顶层的 target_instance 控制字段, + 已在 _handle 归一化前摘除);缺省时跨实例随机均衡,行为与以往一致。 + """ + logger.info(f"MQTT -> {subj}") + meta = pl.get("meta") or {} + try: + r = await self._wm.call(worker_type=wt, subject=subj, payload=pl, + timeout=float(meta.get("timeout", 30)), + cacheable=bool(meta.get("cacheable", False)), + instance_id=target_instance or None) + if r is not None: + await self._pub(rt, r) + except RuntimeError as e: + await self._pub(rt, {"error": str(e)}) + except Exception as e: + await self._pub(rt, {"error": str(e)}) + + async def _pub(self, topic, data): + try: + # data 均为 JSON 可序列化类型(worker 回复解析结果 / {"error": ...}), + # orjson.dumps 直接产出 UTF-8 字节,免去 str -> encode 两步 + await self._client.publish(topic, orjson.dumps(data), QOS_1) # type: ignore + except Exception as e: + logger.error(f"发布失败 [{topic}]: {e}") + + @property + def is_running(self): + return self._running diff --git a/src/chongming/app/core/parent_death.py b/src/chongming/app/core/parent_death.py new file mode 100644 index 0000000..f19388f --- /dev/null +++ b/src/chongming/app/core/parent_death.py @@ -0,0 +1,98 @@ +"""父进程死亡防护(Linux PR_SET_PDEATHSIG)——防"上游被 SIGKILL 后子进程成孤儿"。 + +进程树:onefile 引导进程 → granian master → N 个 worker(fork)+ 1 个 MQTT Broker +子进程。上游被 SIGKILL 时子进程会变孤儿:继续监听端口(SO_REUSEPORT)、继续在 +服务发现里挂着,其它网关派发到这种"半死副本"每次要白吃一整轮超时(worker_manager +的 `超时 (>2s),尝试重试` 日志);SIGTERM/Ctrl-C 路径则由 granian 优雅停机覆盖。 + +PR_SET_PDEATHSIG 让内核在父进程死亡时(含 SIGKILL)给本进程发信号,走既有的 +优雅停机摘除注册。注意三点(man prctl): + + 1. 该值**不跨 fork 继承**(内核为子进程清零,本机实测确认)——所以每个该"随父 + 而死"的进程都要自己设置:master 在 main.py 设一次(随引导进程/终端), + worker 在 lifespan 设一次(随 master),Broker 子进程随其宿主 worker; + 2. 这里的"父"是**创建本进程的那个线程**,不是整个进程——granian 由主线程 fork + worker、worker 由事件循环所在线程拉起 Broker,语义都正确; + 3. 设完必须立刻查 getppid(),父在设置前就已死的话信号不会再来(竞态),只能 + 自行退出。 + +仅 Linux 生效;`CHONGMING_NO_PARENT_GUARD=1` 可整体关闭(由外层编排负责清理时)。 +""" + +import os +import signal +import sys + +# 关闭开关:某些容器编排(PID 1 / cgroup 清理)自己负责回收时不需要本机制 +GUARD_OFF_ENV = "CHONGMING_NO_PARENT_GUARD" + +_PR_SET_PDEATHSIG = 1 +_PR_GET_PDEATHSIG = 2 + +# 模块级缓存 libc 句柄(跨 fork 可安全复用;是否已设置不看缓存,看内核实际状态) +_libc = None + + +def _load_libc(): + global _libc + if _libc is not None: + return _libc + import ctypes + + for name in ("libc.so.6", None): + try: + _libc = ctypes.CDLL(name, use_errno=True) + return _libc + except OSError: + continue + return None + + +def _current_pdeathsig(libc) -> int | None: + """读本进程当前的父死信号值(0 = 未设置)。""" + import ctypes + + buf = ctypes.c_int(0) + if libc.prctl(_PR_GET_PDEATHSIG, ctypes.byref(buf)) != 0: + return None + return buf.value + + +def guard_parent_death(logger=None) -> bool: + """设置父进程死亡信号为 SIGTERM(幂等)。返回是否生效。 + + 生效后:父进程(含被 SIGKILL)死亡 → 内核给本进程发 SIGTERM → 触发既有 + 优雅停机(摘除路由/注册、关闭会话)。非 Linux、关闭开关打开、或 libc 不可用 + 时返回 False,不影响启动。 + + 幂等性按**内核实际状态**判断(PR_GET_PDEATHSIG),不用模块级标志位—— + master 先调用后 fork 出的 worker 会继承模块状态,用标志位会让 worker 误判为 + "已设置"而直接跳过(PDEATHSIG 恰好在 fork 时被内核清零)。 + """ + if os.environ.get(GUARD_OFF_ENV) == "1": + return False + if not sys.platform.startswith("linux"): + return False + + libc = _load_libc() + if libc is None: + if logger: + logger.warning("父进程死亡防护不可用:未能加载 libc") + return False + + if _current_pdeathsig(libc) != int(signal.SIGTERM): + if libc.prctl(_PR_SET_PDEATHSIG, signal.SIGTERM) != 0: + import ctypes + + if logger: + logger.warning("设置父进程死亡信号失败 (prctl errno=%s)", ctypes.get_errno()) + return False + if logger: + logger.info("父进程死亡防护已启用(父进程退出时收 SIGTERM 走优雅停机)") + + # 竞态守卫:父在 prctl 生效前已死 → 信号不会再来,自行退出(走 SIGTERM 停机路径) + if os.getppid() == 1: + if logger: + logger.warning("父进程已不存在(ppid=1),本进程自行退出") + os.kill(os.getpid(), signal.SIGTERM) + return True diff --git a/src/chongming/app/core/route_generator.py b/src/chongming/app/core/route_generator.py new file mode 100644 index 0000000..e9b2236 --- /dev/null +++ b/src/chongming/app/core/route_generator.py @@ -0,0 +1,846 @@ +""" +动态路由生成器 + +根据配置文件中的 worker 路由定义,动态生成 FastAPI 端点。 +为每个端点生成带有正确 Pydantic 类型注解的处理函数,确保 OpenAPI 文档 +正确反映参数类型、响应结构,并注册到 components/schemas。 +""" +from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple +from inspect import Parameter, Signature +import asyncio +import time + +import orjson +from fastapi import FastAPI, HTTPException, APIRouter, Query, Path as FastAPIPath, Request, Depends +from fastapi.responses import StreamingResponse, Response +from pydantic import create_model, Field, BaseModel + +from chongming_worker.route_config import WORKER_CONFIG_DIR, load_worker_routes_from_dir + +from .worker_manager import WorkerManager, StreamIdleTimeoutError +from .logger import get_logger +from .auth import store_user_to_request +from .call_stats import spawn, record_call + +logger = get_logger("route_generator") + +# 实例定向请求头:给定后该请求固定发往指定 worker 进程实例(同一实例下的多个副本 +# 之间仍随机均衡),不参与跨实例的随机负载均衡。取值 = 实例身份(外部进程启动参数 +# --instance / CHONGMING_WORKER_INSTANCE;inline 副本为网关自身实例 id)。 +# 未携带该头时行为与以往完全一致(全类型随机均衡)。 +INSTANCE_HEADER = "X-Chongming-Instance" + + +def _instance_from_request(request: Request) -> Optional[str]: + """从请求头读取定向实例 id;空串/未携带视为不定向。""" + value = request.headers.get(INSTANCE_HEADER) + if value: + value = value.strip() + return value or None + + +# 简单类型映射 +TYPE_MAP: Dict[str, Any] = { + "float": float, + "int": int, + "str": str, + "bool": bool, + "any": Any, +} + + +def _parse_python_type(type_str: str) -> Any: + """解析配置中的类型字符串为 Python 类型对象""" + type_str = type_str.strip() + if type_str.startswith("list[") or type_str.startswith("List["): + inner = type_str[5:-1].strip() + inner_type = _parse_python_type(inner) + return List[inner_type] # type: ignore + if type_str.startswith("dict[") or type_str.startswith("Dict["): + inner = type_str[5:-1].strip() + parts = [p.strip() for p in inner.split(",", 1)] + if len(parts) == 2: + key_type = _parse_python_type(parts[0]) + val_type = _parse_python_type(parts[1]) + return Dict[key_type, val_type] # type: ignore + return Dict[str, Any] # type: ignore + return TYPE_MAP.get(type_str, str) + +def generate_request_model( + param_spec: Dict[str, str], model_name: str = "AutoRequest" +) -> type[BaseModel]: + """生成 Pydantic 请求体模型""" + fields: Dict[str, Any] = {} + for param_name, param_type_str in param_spec.items(): + py_type = _parse_python_type(param_type_str) + fields[param_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + +def generate_response_model( + response_spec: Dict[str, Any], model_name: str = "AutoResponse" +) -> Optional[type[BaseModel]]: + """生成 Pydantic 响应模型""" + if not response_spec: + return None + fields: Dict[str, Any] = {} + for field_name, field_spec in response_spec.items(): + if isinstance(field_spec, dict): + py_type = _parse_python_type(field_spec.get("type", "str")) + required = field_spec.get("required", True) + default_val = field_spec.get("default", ...) + if required: + fields[field_name] = (py_type, Field(...)) + else: + fields[field_name] = (py_type, Field(default=default_val if default_val is not ... else None)) + else: + py_type = _parse_python_type(str(field_spec)) + fields[field_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + +class RouteConfig: + """单个路由配置模型""" + + def __init__(self, data: Dict[str, Any]): + self.subject: str = data.get("subject", "") + self.path: str = data.get("path", "") + self.method: str = data.get("method", "GET").upper() + self.params: List[str] = data.get("params", []) + self.response_model: Dict[str, Any] = data.get("response_model", {}) + self.summary: str = data.get("summary", "") + self.docstring: str = data.get("docstring", "") + self.timeout: float = data.get("timeout", 3.0) + self.cacheable: bool = data.get("cacheable", False) + self.auth_required: bool = data.get("auth_required", True) + self.internal: bool = data.get("internal", False) + # validate=false 时网关不解析/校验请求体,原样透传(校验职责下沉 worker, + # 失败经 reply_err 以 503 暴露);仅对 POST/PUT/PATCH 非流式端点生效 + self.validate: bool = data.get("validate", True) + # 流式端点:以 SSE(text/event-stream)逐块输出 + self.stream: bool = data.get("stream", False) + # 流式空闲超时(秒):连续未收到数据块的最大间隔(timeout 为流总寿命上限) + self.stream_idle_timeout: float = data.get("stream_idle_timeout", 10.0) + + def parse_params(self) -> Dict[str, str]: + """解析 params 列表为 {name: type} 字典""" + result: Dict[str, str] = {} + for param in self.params: + if ":" in param: + name, type_str = param.split(":", 1) + result[name.strip()] = type_str.strip() + else: + result[param.strip()] = "str" + return result + + def extract_path_params(self) -> List[str]: + """从路径中提取路径参数,如 /add/{a}/{b} -> ['a', 'b']""" + return [ + seg[1:-1] for seg in self.path.split("/") + if seg.startswith("{") and seg.endswith("}") + ] + + +def register_route_group( + app: FastAPI, + worker_manager: WorkerManager, + worker_type: str, + routes: List[Dict[str, Any]], + prefix: str = "", + extra_schemas: Optional[Dict[str, type[BaseModel]]] = None, + raw_body_models: Optional[Dict[Tuple[str, str], str]] = None, + response_models: Optional[Dict[Tuple[str, str], str]] = None, +) -> List[Any]: + """ + 为一个 worker_type 的一组路由描述符创建并挂载 APIRouter。 + + 与 register_dynamic_routes(盘扫路径)共用同一注册逻辑, + process 模式下 RouteRegistry 也调用本函数做动态挂载。 + + Args: + app: FastAPI 应用 + worker_manager: WorkerManager 实例 + worker_type: worker 类型(OpenAPI 分组标签) + routes: 路由描述符列表(TOML 兼容格式,与 load_worker_routes_from_dir 输出一致) + prefix: 全局路由前缀(如 /api/v1) + extra_schemas: 额外 schema 收集 dict(GET 请求模型注入 OpenAPI 用) + raw_body_models: (path, method) -> 请求模型名 的收集 dict + (validate=false 端点:请求体文档经 requestBody $ref 注入,见 _inject_schemas_to_openapi) + response_models: (path, method) -> 响应模型名 的收集 dict + (端点注册不传 response_model 以砍掉注册期模型反射;响应文档经 + responses[200].content $ref 手动注入,见 _inject_schemas_to_openapi) + + Returns: + List[Any]: 本次新增到 app.router.routes 的 APIRoute 对象列表 + (调用方可记录引用,用于动态移除) + """ + router = APIRouter(prefix=prefix) + for route_conf in routes: + _register_single_route(router, worker_manager, worker_type, route_conf, extra_schemas, raw_body_models, response_models) + + # raw_body_models / response_models 记录的 (path, method) 为端点声明路径(不含 prefix), + # 而 OpenAPI paths 键为最终挂载路径(prefix + path),这里统一补前缀 + if prefix: + for key in list((raw_body_models or {}).keys()): + path, method = key + raw_body_models[(prefix + path, method)] = raw_body_models.pop(key) + for key in list((response_models or {}).keys()): + path, method = key + response_models[(prefix + path, method)] = response_models.pop(key) + + before = set(id(r) for r in app.router.routes) + app.include_router(router) + added = [r for r in app.router.routes if id(r) not in before] + logger.info( + f"已挂载 worker '{worker_type}' 的路由组: {len(routes)} 个描述符 -> " + f"{len(added)} 条 HTTP 路由 (prefix={prefix or '/'})" + ) + return added + + +def register_dynamic_routes( + app: FastAPI, + worker_manager: WorkerManager, + prefix: str = "", + worker_types: Optional[List[str]] = None, +): + """ + 根据 config/workers/ 目录结构注册所有动态路由。 + + worker_types: 白名单(混合模式下只注册 inline 插件; + None = 全部注册,兼容纯 inline 部署)。 + """ + from pathlib import Path + + workers_base = Path(WORKER_CONFIG_DIR) + if not workers_base.is_dir(): + logger.info(f"Worker 配置目录不存在: {WORKER_CONFIG_DIR}") + return + + extra_schemas: Dict[str, type[BaseModel]] = {} + # validate=false 端点的 (path, method) -> 请求模型名(OpenAPI requestBody 注入用) + raw_body_models: Dict[Tuple[str, str], str] = {} + # 非流式端点的 (path, method) -> 响应模型名(OpenAPI responses[200] $ref 注入用) + response_models: Dict[Tuple[str, str], str] = {} + + # 扫描所有 worker 子目录(每个子目录名 = worker_type) + for worker_dir in sorted(workers_base.iterdir()): + if not worker_dir.is_dir(): + continue + worker_type = worker_dir.name + if worker_types is not None and worker_type not in worker_types: + continue + routes = load_worker_routes_from_dir(worker_type) + if not routes: + continue + + register_route_group(app, worker_manager, worker_type, routes, prefix, extra_schemas, raw_body_models, response_models) + + # 将额外 schema 注册到 OpenAPI 的 components/schemas + if extra_schemas: + _inject_schemas_to_openapi(app, lambda: extra_schemas, lambda: raw_body_models, lambda: response_models) + + logger.info(f"动态路由注册完成,worker 配置目录: {WORKER_CONFIG_DIR}") + + +def _inject_schemas_to_openapi(app: FastAPI, schemas_provider, raw_body_provider=None, response_provider=None): + """ + 将自定义 Pydantic 模型注册到 FastAPI 的 OpenAPI components/schemas 中。 + + 通过重写 app.openapi() 方法注入,因为 FastAPI 在生命周期内只生成一次 + OpenAPI schema,之后会被缓存。 + + Args: + schemas_provider: 无参 callable,返回 {name: BaseModel} 字典。 + 盘扫路径传静态 dict;process 模式传 RouteRegistry 的动态 dict, + 使路由表变化后重新生成的 OpenAPI 能反映最新 schema。 + raw_body_provider: 可选无参 callable,返回 {(path, method): model_name} 字典。 + validate=false 端点未声明 body 参数(FastAPI 不会自动生成 requestBody), + 这里按 (path, method) 补注入 requestBody $ref,保持 OpenAPI 请求体文档完整。 + response_provider: 可选无参 callable,返回 {(path, method): model_name} 字典。 + 端点注册未传 response_model(见 _register_single_route:砍掉注册期模型反射), + 这里按 (path, method) 补注入 responses[200] 的 $ref,保持响应文档完整。 + """ + original_openapi = app.openapi + + def patched_openapi() -> dict[str, Any]: + if app.openapi_schema is not None: + return app.openapi_schema + schema = original_openapi() + if schema is None: + return {"openapi": "3.1.0", "info": {"title": "Chongming", "version": "1.0.0"}, "paths": {}} + # 注入我们的 schema + schemas_dict = schema.setdefault("components", {}).setdefault("schemas", {}) + for name, model in schemas_provider().items(): + if name not in schemas_dict: + schemas_dict[name] = model.model_json_schema() + # validate=false 端点:补注入 requestBody(请求体文档,模型已注册到 components) + if raw_body_provider is not None: + for (path, method), model_name in raw_body_provider().items(): + op = schema.get("paths", {}).get(path, {}).get(method.lower()) + if op is not None: + op["requestBody"] = { + "required": True, + "content": { + "application/json": { + "schema": {"$ref": f"#/components/schemas/{model_name}"} + } + }, + } + # 响应模型:补注入 responses[200] 的 $ref(保留 description,仅加 content) + if response_provider is not None: + for (path, method), model_name in response_provider().items(): + op = schema.get("paths", {}).get(path, {}).get(method.lower()) + if op is not None: + op.setdefault("responses", {}).setdefault("200", {}) + op["responses"]["200"].setdefault( + "content", {} + ).setdefault("application/json", {})["schema"] = { + "$ref": f"#/components/schemas/{model_name}" + } + app.openapi_schema = schema + return schema + + app.openapi = patched_openapi + + +def _spawn_call_record( + request: Request, + worker_type: str, + subject: str, + rc: RouteConfig, + status: int, + duration_ms: float, + req_bytes: int = 0, + resp_bytes: int = 0, +) -> None: + """ + 以后台任务记录一次调用(统计 + 审计),非关键后置操作。 + + 通过 spawn() 以 fire-and-forget 方式执行,端点立即返回, + 不增加接口响应延迟;后台失败由 record_call 内部兜底。 + """ + entry = { + "ts": time.time(), + "worker_type": worker_type, + "subject": subject, + "method": rc.method, + "path": request.url.path, + "user": getattr(request.state, "user", None), + "client": request.client.host if request.client else None, + "status": status, + "duration_ms": round(duration_ms, 3), + "req_bytes": req_bytes, + "resp_bytes": resp_bytes, + } + spawn(record_call(entry)) + + +def _make_body_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str, RequestModel: type[BaseModel]): + """创建用于 Body 请求(POST/PUT/PATCH)的端点函数""" + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + async def endpoint( + request: Request, + body: RequestModel, # type: ignore + ) -> Any: + start: Optional[float] = None + payload_bytes = b"" + try: + # 信封协议: {"meta": {...}, "data": {...}} + meta = {"method": http_method} + user_info = getattr(request.state, "user", None) + if user_info: + meta["user_info"] = user_info + payload = {"meta": meta, "data": body.model_dump()} + payload_bytes = orjson.dumps(payload) + start = time.perf_counter() + result = await worker_manager.call_raw( + worker_type=worker_type, + subject=subject, + payload=payload_bytes, + timeout=timeout, + cacheable=cacheable, + instance_id=_instance_from_request(request), + ) + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 200, duration_ms, + req_bytes=len(payload_bytes), resp_bytes=len(result), + ) + # 回复为 worker 原始字节,零解析直接转发(response_model 仅用于 OpenAPI 文档) + return Response(content=result, media_type="application/json") + except RuntimeError as e: + duration_ms = (time.perf_counter() - start) * 1000 if start else 0.0 + _spawn_call_record( + request, worker_type, subject, rc, 503, duration_ms, + req_bytes=len(payload_bytes), + ) + logger.warning("路由 %s [%s] 调用失败: %s", path, worker_type, e) + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + duration_ms = (time.perf_counter() - start) * 1000 if start else 0.0 + _spawn_call_record( + request, worker_type, subject, rc, 500, duration_ms, + req_bytes=len(payload_bytes), + ) + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + return endpoint + + +def _make_raw_body_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str): + """ + 创建 validate=false 的 body 端点:请求体原样透传(网关零解析/校验)。 + + 信封结构不变({"meta": ..., "data": <原始 JSON 值>}),通过字节拼接构造, + 网关既不解析 body 也不转义;参数校验/类型转换职责下沉到 worker + (失败经 reply_err 通道以 503 暴露)。空/空白 body 兜底为 {}。 + 请求模型(params TOML 生成)仅用于 OpenAPI 文档(extra_schemas + requestBody 注入)。 + """ + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + async def endpoint(request: Request) -> Any: + start: Optional[float] = None + payload = b"" + try: + meta = {"method": http_method} + user_info = getattr(request.state, "user", None) + if user_info: + meta["user_info"] = user_info + raw = await request.body() + data_part = raw.strip() or b"{}" + payload = b'{"meta":' + orjson.dumps(meta) + b',"data":' + data_part + b'}' + start = time.perf_counter() + result = await worker_manager.call_raw( + worker_type=worker_type, + subject=subject, + payload=payload, + timeout=timeout, + cacheable=cacheable, + instance_id=_instance_from_request(request), + ) + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 200, duration_ms, + req_bytes=len(payload), resp_bytes=len(result), + ) + return Response(content=result, media_type="application/json") + except RuntimeError as e: + duration_ms = (time.perf_counter() - start) * 1000 if start else 0.0 + _spawn_call_record( + request, worker_type, subject, rc, 503, duration_ms, + req_bytes=len(payload), + ) + logger.warning("路由 %s [%s] 调用失败: %s", path, worker_type, e) + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + duration_ms = (time.perf_counter() - start) * 1000 if start else 0.0 + _spawn_call_record( + request, worker_type, subject, rc, 500, duration_ms, + req_bytes=len(payload), + ) + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + return endpoint + + +def _make_query_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str, path_params: List[str], query_params: Dict[str, str]): + """ + 创建用于 Query 请求(GET/DELETE)的端点函数。 + + 通过设置函数 __signature__ 和 __annotations__ 为每个参数赋予正确的 Pydantic 类型, + 使 FastAPI 能在 OpenAPI schema 中生成正确的 type / format 信息。 + 请求模型 schema 通过 extra_schemas 机制单独注入到 components/schemas。 + """ + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + # 合并路径参数和查询参数 + param_spec_combined: Dict[str, str] = {} + param_spec_combined.update({k: query_params.get(k, "str") for k in path_params if k in query_params}) + param_spec_combined.update(query_params) + + # 无参数情况 + if not param_spec_combined: + async def _simple_endpoint(request: Request) -> Any: + # 信封协议: {"meta": {...}, "data": {}} + meta = {"method": http_method} + user_info = getattr(request.state, "user", None) + if user_info: + meta["user_info"] = user_info + payload_bytes = orjson.dumps({"meta": meta, "data": {}}) + start = time.perf_counter() + try: + result = await worker_manager.call_raw( + worker_type=worker_type, + subject=subject, + payload=payload_bytes, + timeout=timeout, + cacheable=cacheable, + instance_id=_instance_from_request(request), + ) + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 200, duration_ms, + req_bytes=len(payload_bytes), resp_bytes=len(result), + ) + return Response(content=result, media_type="application/json") + except RuntimeError as e: + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 503, duration_ms, + req_bytes=len(payload_bytes), + ) + logger.warning("路由 %s [%s] 调用失败: %s", path, worker_type, e) + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 500, duration_ms, + req_bytes=len(payload_bytes), + ) + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + return _simple_endpoint + + # 构造参数名列表(用于从 kwargs 中提取) + param_names = list(param_spec_combined.keys()) + + async def endpoint(request: Request, **kwargs: Any) -> Any: + # 信封协议: {"meta": {...}, "data": {...}} + meta = {"method": http_method} + user_info = getattr(request.state, "user", None) + if user_info: + meta["user_info"] = user_info + payload_bytes = orjson.dumps({"meta": meta, "data": {k: kwargs[k] for k in param_names}}) + start = time.perf_counter() + try: + result = await worker_manager.call_raw( + worker_type=worker_type, + subject=subject, + payload=payload_bytes, + timeout=timeout, + cacheable=cacheable, + instance_id=_instance_from_request(request), + ) + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 200, duration_ms, + req_bytes=len(payload_bytes), resp_bytes=len(result), + ) + return Response(content=result, media_type="application/json") + except RuntimeError as e: + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 503, duration_ms, + req_bytes=len(payload_bytes), + ) + logger.warning("路由 %s [%s] 调用失败: %s", path, worker_type, e) + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 500, duration_ms, + req_bytes=len(payload_bytes), + ) + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + # 构建带类型注解的 Parameter 列表 + params: List[Parameter] = [ + Parameter("request", Parameter.POSITIONAL_OR_KEYWORD, annotation=Request) + ] + annotations: Dict[str, Any] = {} + for name, type_str in param_spec_combined.items(): + py_type = _parse_python_type(type_str) + annotations[name] = py_type + if name in path_params: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=FastAPIPath(...), annotation=py_type)) + else: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=Query(...), annotation=py_type)) + + # 设置 __annotations__(FastAPI 通过 get_type_hints 读取) + endpoint.__annotations__ = annotations + # 设置 __signature__(FastAPI 优先于此获取参数列表) + endpoint.__signature__ = Signature(params, return_annotation=Dict[str, Any]) # type: ignore + + return endpoint + + +# 流式端点响应头:禁止缓存;通知反向代理(nginx)关闭缓冲保证 SSE 即时性 +_STREAM_HEADERS = { + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", +} + + +def _make_stream_endpoint( + worker_manager: WorkerManager, + rc: RouteConfig, + worker_type: str, + path_params: List[str] = (), + query_params: Optional[Dict[str, str]] = None, + RequestModel: Optional[type[BaseModel]] = None, +): + """ + 创建流式端点(TOML 中 stream = true):以 SSE(text/event-stream)逐块输出。 + + 请求参数形态与普通端点一致(GET 走 query/path 参数,POST/PUT/PATCH 走 body 模型)。 + 错误语义: + - 初始错误(无可用 worker 等)-> HTTP 503 + - 流中错误(Worker 返回 {"error": ...} 收尾块 / 空闲超时)-> SSE error 事件 + (此时 HTTP 200 已提交,无法改状态码) + 不校验响应模型:流式输出是逐块 JSON dict。 + """ + path = rc.path + subject = rc.subject + http_method = rc.method + timeout = rc.timeout + idle_timeout = rc.stream_idle_timeout + param_names = list((query_params or {}).keys()) + + def build_payload(request: Request, body=None, kwargs=None) -> Dict[str, Any]: + """构造信封协议 payload(与普通端点一致)""" + meta = {"method": http_method} + user_info = getattr(request.state, "user", None) + if user_info: + meta["user_info"] = user_info + if body is not None: + data = body.model_dump() + else: + data = {k: kwargs[k] for k in param_names} + return {"meta": meta, "data": data} + + def _sse_frame(data: Dict[str, Any]) -> str: + return f"data: {orjson.dumps(data).decode('utf-8')}\n\n" + + async def _sse_body(gen, start: float, request: Request, req_bytes: int) -> AsyncGenerator[str, None]: + """SSE 分帧;流中异常转为 SSE error 事件(HTTP 200 已提交)。 + + 流结束(正常/空闲超时/中断/客户端断开)后在 finally 中后台记录 + 调用统计与审计,时长覆盖整个流。 + """ + status = 200 + try: + async for chunk in gen: + yield f"data: {chunk.decode('utf-8')}\n\n" + except StreamIdleTimeoutError as e: + status = 504 + logger.warning("路由 %s 流式空闲超时: %s", path, e) + yield _sse_frame({"error": str(e)}) + except asyncio.CancelledError: + status = 499 # 客户端断开,流未完成 + raise + except GeneratorExit: + status = 499 # 服务端主动关闭流(如连接回收),流未完成 + raise + except Exception as e: + status = 500 + logger.error("路由 %s 流式中断: %s", path, e) + yield _sse_frame({"error": "内部服务器错误"}) + finally: + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, status, duration_ms, + req_bytes=req_bytes, + ) + + async def _start_stream(request: Request, payload: Dict[str, Any]) -> StreamingResponse: + req_bytes = len(orjson.dumps(payload)) + start = time.perf_counter() + try: + gen = await worker_manager.call_stream( + worker_type=worker_type, + subject=subject, + payload=payload, + timeout=timeout, + idle_timeout=idle_timeout, + instance_id=_instance_from_request(request), + ) + except RuntimeError as e: + duration_ms = (time.perf_counter() - start) * 1000 + _spawn_call_record( + request, worker_type, subject, rc, 503, duration_ms, + req_bytes=req_bytes, + ) + logger.warning("路由 %s [%s] 流式调用失败: %s", path, worker_type, e) + raise HTTPException(status_code=503, detail=str(e)) + return StreamingResponse( + _sse_body(gen, start, request, req_bytes), + media_type="text/event-stream", + headers=_STREAM_HEADERS, + ) + + if RequestModel is not None: + async def endpoint(request: Request, body: RequestModel) -> StreamingResponse: # type: ignore + return await _start_stream(request, build_payload(request, body=body)) + return endpoint + + # GET/DELETE 形态 + if not param_names: + async def _simple_endpoint(request: Request) -> StreamingResponse: + return await _start_stream(request, build_payload(request, kwargs=None)) + return _simple_endpoint + + async def endpoint(request: Request, **kwargs: Any) -> StreamingResponse: + return await _start_stream(request, build_payload(request, kwargs=kwargs)) + + # 附加类型注解与签名(与 _make_query_endpoint 一致),保证 OpenAPI 参数文档正确 + params: List[Parameter] = [ + Parameter("request", Parameter.POSITIONAL_OR_KEYWORD, annotation=Request) + ] + annotations: Dict[str, Any] = {} + for name, type_str in (query_params or {}).items(): + py_type = _parse_python_type(type_str) + annotations[name] = py_type + if name in path_params: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=FastAPIPath(...), annotation=py_type)) + else: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=Query(...), annotation=py_type)) + + endpoint.__annotations__ = annotations + endpoint.__signature__ = Signature(params, return_annotation=StreamingResponse) # type: ignore + + return endpoint + + +def _register_single_route( + router: APIRouter, + worker_manager: WorkerManager, + worker_type: str, + route_conf: Dict[str, Any], + extra_schemas: Optional[Dict[str, type[BaseModel]]] = None, + raw_body_models: Optional[Dict[Tuple[str, str], str]] = None, + response_models: Optional[Dict[Tuple[str, str], str]] = None, +): + """注册单个路由,生成正确的函数签名以便 OpenAPI 正确生成文档""" + rc = RouteConfig(route_conf) + + # MQTT 端点:不注册 HTTP 路由,仅通过 MQTT Bridge 调用 + if rc.method == "MQTT": + logger.info( + f"MQTT 端点: subject={rc.subject} (仅 MQTT,不注册 HTTP 路由)" + ) + return + + # internal 端点:不注册 HTTP 路由,仅保留 Zenoh RPC 内部调用能力 + if rc.internal: + logger.info( + f"跳过 internal 路由注册: {rc.method} {rc.path} -> worker {worker_type}.{rc.subject}" + ) + return + + param_spec = rc.parse_params() + path_params = rc.extract_path_params() + query_params = {k: v for k, v in param_spec.items() if k not in path_params} + + # 生成干净的模型名称(替换路径中的 / 和 {} 为 _) + # 模型名称包含 HTTP method 以区分同名路径的不同调用类型 + clean_path = rc.path.replace("/", "_").replace("{", "").replace("}", "").strip("_") + if not clean_path: + clean_path = "root" + method_tag = rc.method.upper() + response_model_name = f"{worker_type}_{clean_path}_{method_tag}_Response" + request_model_name = f"{worker_type}_{clean_path}_{method_tag}_Request" + + # 生成响应模型 + if rc.stream: + # 流式端点:逐块 SSE 输出,不校验响应模型(OpenAPI 不声明响应 schema) + ResponseModel = None + if rc.method in ("POST", "PUT", "PATCH"): + # body 形态:RequestModel 传给端点做请求体校验 + RequestModel = generate_request_model( + param_spec, request_model_name + ) + endpoint = _make_stream_endpoint( + worker_manager, rc, worker_type, path_params, query_params, RequestModel + ) + else: + # GET/DELETE 形态:query/path 参数走签名注解; + # RequestModel 仅注册到 extra_schemas 供 OpenAPI 展示(与普通 GET 一致) + if param_spec: + RequestModel = generate_request_model( + param_spec, request_model_name + ) + if extra_schemas is not None and request_model_name not in extra_schemas: + extra_schemas[request_model_name] = RequestModel + endpoint = _make_stream_endpoint( + worker_manager, rc, worker_type, path_params, query_params, None + ) + elif rc.method in ("POST", "PUT", "PATCH"): + ResponseModel = generate_response_model( + rc.response_model, response_model_name + ) + # 响应模型仅注册到 components/schemas + responses[200] $ref 注入, + # 不传给 add_api_route:端点返回 Response 对象本就走透传(零运行时校验), + # 省掉 FastAPI 注册期对 response_model 的反射解析(external 插件频繁 + # 挂载/摘除时每次重建都会重复这一开销) + if ResponseModel is not None: + if extra_schemas is not None and response_model_name not in extra_schemas: + extra_schemas[response_model_name] = ResponseModel + if response_models is not None: + response_models[(rc.path, rc.method)] = response_model_name + RequestModel = generate_request_model( + param_spec, request_model_name + ) + if rc.validate: + endpoint = _make_body_endpoint(worker_manager, rc, worker_type, RequestModel) + else: + # validate=false:请求体原样透传(网关零解析/校验,校验下沉 worker)。 + # RequestModel 仅用于 OpenAPI 请求体文档: + # 经 extra_schemas 注册 components/schemas + requestBody $ref 注入 + if extra_schemas is not None and request_model_name not in extra_schemas: + extra_schemas[request_model_name] = RequestModel + if raw_body_models is not None: + raw_body_models[(rc.path, rc.method)] = request_model_name + endpoint = _make_raw_body_endpoint(worker_manager, rc, worker_type) + else: + ResponseModel = generate_response_model( + rc.response_model, response_model_name + ) + # 同上:响应模型走 extra_schemas + responses $ref 注入,不传 add_api_route + if ResponseModel is not None: + if extra_schemas is not None and response_model_name not in extra_schemas: + extra_schemas[response_model_name] = ResponseModel + if response_models is not None: + response_models[(rc.path, rc.method)] = response_model_name + # 对 GET 端点也生成 Request 模型并注册到 extra_schemas + if param_spec: + RequestModel = generate_request_model( + param_spec, request_model_name + ) + if extra_schemas is not None and request_model_name not in extra_schemas: + extra_schemas[request_model_name] = RequestModel + endpoint = _make_query_endpoint(worker_manager, rc, worker_type, path_params, query_params) + + dependencies = [] + if rc.auth_required: + dependencies.append(Depends(store_user_to_request)) + + # 注册路由 - 使用 worker_type 作为 OpenAPI 分组标签 + # 每个 worker 类型自动成为一个独立的分组,显示在 Swagger UI 中 + # 不传 response_model:端点返回 Response 对象走透传,运行时本就零校验; + # 响应文档由 response_models 收集 + _inject_schemas_to_openapi 补 $ref + router.add_api_route( + path=rc.path, + endpoint=endpoint, + methods=[rc.method], + summary=rc.summary, + description=rc.docstring, + tags=[worker_type], + include_in_schema=True, + dependencies=dependencies, + ) + logger.info(f"已注册动态路由: {rc.method} {rc.path} -> worker {worker_type}.{rc.subject} | tags=[{worker_type}]") diff --git a/src/chongming/app/core/route_registry.py b/src/chongming/app/core/route_registry.py new file mode 100644 index 0000000..7ef3553 --- /dev/null +++ b/src/chongming/app/core/route_registry.py @@ -0,0 +1,271 @@ +""" +动态路由注册表(process 模式) + +网关订阅 `chongming/routes/**` 命名空间,独立 worker 进程上线时 put 路由描述符、 +下线时 delete,网关据此动态挂载/摘除 FastAPI 路由并同步 MQTT 自定义订阅主题。 + +协议(描述符 schema_version=1): + chongming/routes//__meta__ 插件级描述: {"schema_version":1,"mqtt_topics":[...]} + chongming/routes// 端点描述(TOML 兼容 dict,与 RouteConfig 对位): + {"schema_version":1, "type","subject","method","path","params", + "response_model","summary","timeout","cacheable","auth_required","internal"} + +特性: + - SampleKind.PUT -> upsert;DELETE -> remove + - 同一 route_id 内容相同(hash)视为多副本重复发布,忽略;不同 -> 告警并保留先到者 + - type 级 2 秒防抖:worker 重启风暴合并为一次路由重建 + - 迟到一致性:worker 侧周期性重发布描述符(RoutePublisher 由调用方周期调用), + 网关后启动或样本丢失时,路由在一个发布周期内自动补齐 +""" +import asyncio +import hashlib +import orjson +from typing import Any, Dict, List, Optional, Tuple + +import zenoh + +from chongming_worker.routes_publisher import META_KEY, ROUTES_PREFIX, SCHEMA_VERSION + +from .logger import get_logger +from .route_generator import register_route_group, _inject_schemas_to_openapi + +logger = get_logger("route_registry") + +# worker 重启风暴防抖窗口(秒) +DEBOUNCE_SECONDS = 2.0 + + +def _descriptor_hash(descriptor: Dict[str, Any]) -> str: + """描述符内容 hash(多副本幂等去重用)""" + payload = orjson.dumps(descriptor, option=orjson.OPT_SORT_KEYS) + return hashlib.sha1(payload).hexdigest() + + +class RouteRegistry: + """process 模式下的网关侧动态路由注册表。""" + + def __init__( + self, + app, + worker_manager, + prefix: str = "", + ): + self._app = app + self._wm = worker_manager + self._prefix = prefix + self._loop: Optional[asyncio.AbstractEventLoop] = None + + # worker_type -> route_id -> descriptor + self._groups: Dict[str, Dict[str, Dict[str, Any]]] = {} + # worker_type -> 插件级描述(mqtt_topics 等) + self._meta: Dict[str, Dict[str, Any]] = {} + # worker_type -> 已挂载的 APIRoute 对象(用于动态摘除) + self._route_objs: Dict[str, List[Any]] = {} + # OpenAPI 额外 schema(GET 请求模型),随路由表变化 + self._schemas: Dict[str, Any] = {} + # validate=false 端点的 (path, method) -> 请求模型名(OpenAPI requestBody 注入用) + self._raw_body_models: Dict[Tuple[str, str], str] = {} + # 非流式端点的 (path, method) -> 响应模型名(OpenAPI responses[200] $ref 注入用) + self._response_models: Dict[Tuple[str, str], str] = {} + # worker_type -> 防抖任务 + self._debounce: Dict[str, asyncio.Task] = {} + + self._subscriber: Optional[zenoh.Subscriber] = None + self._mqtt_bridge = None + # 网关进程内加载(inline)的插件类型:忽略其描述符,防止与盘扫注册重复挂载 + self._inline_types: set = set() + + # 将动态 schema 注入 OpenAPI(provider 实时读取本注册表) + _inject_schemas_to_openapi( + app, + lambda: self._schemas, + lambda: self._raw_body_models, + lambda: self._response_models, + ) + + def set_inline_types(self, types: set): + """设置 inline 插件类型集合(混合模式防双注册)。""" + self._inline_types = set(types) + + async def start(self, session: zenoh.Session): + """订阅 chongming/routes/**,开始接收路由描述符。""" + self._loop = asyncio.get_running_loop() + self._subscriber = session.declare_subscriber( + f"{ROUTES_PREFIX}/**", self._on_sample + ) + logger.info(f"路由注册表已就绪: 订阅 {ROUTES_PREFIX}/**") + + def stop(self): + """停止订阅(shutdown 时调用)。""" + if self._subscriber is not None: + self._subscriber.undeclare() + self._subscriber = None + for task in self._debounce.values(): + task.cancel() + + def attach_bridge(self, mqtt_bridge): + """挂接 MQTT Bridge:后续路由表变化会推送 external 插件的动态订阅主题。""" + self._mqtt_bridge = mqtt_bridge + # Bridge 晚于路由到达时,立即补推当前已收集的主题 + topics = self._collect_mqtt_topics() + if topics: + self._loop.create_task(mqtt_bridge.update_dynamic_topics(topics)) + + @property + def route_types(self) -> List[str]: + """当前已注册路由的 worker 类型列表(观测用)。""" + return sorted(self._groups.keys()) + + def ready_summary(self) -> str: + """路由就绪状态摘要(日志/观测用)。""" + parts = [] + for wtype in sorted(self._groups): + http = sum( + 1 for d in self._groups[wtype].values() + if d.get("method") not in ("MQTT",) and not d.get("internal") + ) + parts.append(f"{wtype}={len(self._groups[wtype])} 端点/{http} HTTP") + return ", ".join(parts) if parts else "(空)" + + # ---- zenoh 回调线程 -> 事件循环 ---- + + def _on_sample(self, sample: zenoh.Sample): + """zenoh 后台线程回调:转交事件循环处理。""" + if self._loop is None: + return + key = str(sample.key_expr) + kind = sample.kind + payload = bytes(sample.payload) if sample.payload else b"" + asyncio.run_coroutine_threadsafe( + self._handle_sample(key, kind, payload), self._loop + ) + + async def _handle_sample(self, key: str, kind: zenoh.SampleKind, payload: bytes): + """解析样本并更新内存路由表(在事件循环内执行)。""" + parts = key.split("/") + if len(parts) < 4: + return + wtype, route_id = parts[2], parts[3] + + if wtype in self._inline_types: + # 该插件由网关进程内加载(盘扫注册路由),忽略外部描述符防双注册 + return + + if kind == zenoh.SampleKind.DELETE: + self._remove(wtype, route_id) + return + + try: + descriptor = orjson.loads(payload) + except orjson.JSONDecodeError: + logger.warning("丢弃非法路由描述符 [%s]: JSON 解析失败", key) + return + if not isinstance(descriptor, dict): + logger.warning("丢弃非法路由描述符 [%s]: 非 dict", key) + return + + if route_id == META_KEY: + self._upsert_meta(wtype, descriptor) + else: + self._upsert_route(wtype, route_id, descriptor) + + def _upsert_meta(self, wtype: str, descriptor: Dict[str, Any]): + old = self._meta.get(wtype) + new_hash = _descriptor_hash(descriptor) + if old is not None and _descriptor_hash(old) == new_hash: + return + self._meta[wtype] = descriptor + self._schedule_rebuild(wtype) + + def _upsert_route(self, wtype: str, route_id: str, descriptor: Dict[str, Any]): + group = self._groups.setdefault(wtype, {}) + existing = group.get(route_id) + if existing is not None: + if _descriptor_hash(existing) == _descriptor_hash(descriptor): + # 多副本重复发布同一描述符,幂等忽略 + return + logger.warning( + "路由描述符冲突 [%s/%s]: 已存在版本保留,忽略新版本", + wtype, route_id, + ) + return + # 补全描述符字段(发布端缺省时的兜底) + descriptor.setdefault("type", wtype) + descriptor.setdefault("schema_version", SCHEMA_VERSION) + group[route_id] = descriptor + logger.info( + "收到路由注册: %s %s %s (subject=%s)", + descriptor.get("method", "?"), + descriptor.get("path", "?"), + f"[{wtype}]", + descriptor.get("subject", "?"), + ) + self._schedule_rebuild(wtype) + + def _remove(self, wtype: str, route_id: str): + if route_id == META_KEY: + if self._meta.pop(wtype, None) is not None: + self._schedule_rebuild(wtype) + return + group = self._groups.get(wtype) + if group and group.pop(route_id, None) is not None: + logger.info("收到路由摘除: [%s] %s", wtype, route_id) + self._schedule_rebuild(wtype) + + def _schedule_rebuild(self, wtype: str): + """type 级防抖:合并短时间内的多次变更。""" + prev = self._debounce.get(wtype) + if prev is not None and not prev.done(): + prev.cancel() + self._debounce[wtype] = asyncio.create_task(self._debounced_rebuild(wtype)) + + async def _debounced_rebuild(self, wtype: str): + try: + await asyncio.sleep(DEBOUNCE_SECONDS) + except asyncio.CancelledError: + return + self._debounce.pop(wtype, None) + await self._rebuild(wtype) + + async def _rebuild(self, wtype: str): + """整组重建该 worker_type 的 HTTP 路由 + 同步 MQTT 自定义主题。""" + routes = list(self._groups.get(wtype, {}).values()) + routes.sort(key=lambda r: (r.get("path", ""), r.get("method", ""))) + + # 1. 摘除旧路由(按对象引用从 router.routes 过滤) + for r in self._route_objs.pop(wtype, []): + if r in self._app.router.routes: + self._app.router.routes.remove(r) + + # 2. 挂载新路由(MQTT / internal 端点不注册 HTTP,但保留在路由表中) + http_routes = [ + r for r in routes + if r.get("method") not in ("MQTT",) and not r.get("internal") + ] + if http_routes: + self._route_objs[wtype] = register_route_group( + self._app, self._wm, wtype, http_routes, self._prefix, + self._schemas, self._raw_body_models, self._response_models, + ) + else: + logger.info("[%s] 无 HTTP 路由可挂载(%d 个描述符,全部为 MQTT/internal)", + wtype, len(routes)) + + # 路由表变化后 OpenAPI 缓存失效,下一次访问时重建 + self._app.openapi_schema = None + + # 3. 同步 external 插件的 MQTT 动态订阅主题 + if self._mqtt_bridge is not None: + await self._mqtt_bridge.update_dynamic_topics(self._collect_mqtt_topics()) + + def _collect_mqtt_topics(self) -> List[Dict[str, Any]]: + """汇总所有插件 __meta__ 声明的自定义订阅主题。""" + topics: List[Dict[str, Any]] = [] + for wtype in sorted(self._meta): + meta = self._meta[wtype] + for t in meta.get("mqtt_topics") or []: + if isinstance(t, dict) and t.get("topic"): + topic = dict(t) + topic.setdefault("worker", wtype) + topics.append(topic) + return topics diff --git a/src/chongming/app/core/static_files.py b/src/chongming/app/core/static_files.py new file mode 100644 index 0000000..0880cbe --- /dev/null +++ b/src/chongming/app/core/static_files.py @@ -0,0 +1,100 @@ +from fastapi import HTTPException +from fastapi.responses import Response +from fastapi.staticfiles import StaticFiles +from sqlite_vfs.core import SQLiteVFS + + +class SVFSStaticFiles(StaticFiles): + """ + 基于 SQLite VFS 的静态文件服务类 + + 该类继承自 FastAPI 的 StaticFiles,使用 SQLite 虚拟文件系统来存储和提供静态文件。 + 支持单页应用(SPA)的路由回退机制,当请求的文件不存在时,会自动返回 index.html。 + """ + + def __init__(self, directory, vfs_db_path="static.svfs"): + """ + 初始化 SVFSStaticFiles 实例 + + 参数: + directory (str): 静态文件的根目录路径 + vfs_db_path (str): SQLite VFS 数据库文件路径,默认为 "static.svfs" + + 返回值: + 无 + """ + vfs_db_path = vfs_db_path + self._vfs = SQLiteVFS(vfs_db_path, compress=False) + super().__init__(directory=directory) + + async def get_response(self, path: str, scope): + """ + 根据请求路径获取对应的静态文件响应 + + 该方法处理静态文件的请求逻辑,包括: + 1. 路径标准化处理 + 2. 文件存在性检查 + 3. SPA 路由回退支持 + 4. 目录索引处理 + 5. MIME 类型自动识别 + + 参数: + path (str): 请求的文件路径 + scope: ASGI 作用域对象,包含请求上下文信息 + + 返回值: + Response: FastAPI 响应对象,包含文件内容和正确的 Content-Type + + 异常: + HTTPException: 当文件不存在且无法回退到 index.html 时,抛出 404 错误 + """ + # 统一路径格式:以 / 开头,并将 Windows 反斜杠转为正斜杠 + normalized_path = "/" + path.replace("\\", "/") + + # 获取文件信息 + file_info = self._vfs.get_file_info(normalized_path) + + # 处理文件不存在的情况,尝试 SPA 回退机制 + if file_info is None: + # SPA 回退:如果不是静态资源扩展名,尝试返回 index.html + if not any( + normalized_path.endswith(ext) + for ext in [ + ".js", + ".css", + ".png", + ".jpg", + ".ico", + ".svg", + ".webp", + ".json", + ] + ): + index_info = self._vfs.get_file_info("/index.html") + if index_info: + content = self._vfs.read_file("/index.html") + return Response(content=content, media_type="text/html") + raise HTTPException(status_code=404, detail="File not found") + + # 处理目录请求,返回目录下的 index.html + if file_info["is_directory"]: + index_info = self._vfs.get_file_info(f"{normalized_path}/index.html") + if index_info: + content = self._vfs.read_file(f"{normalized_path}/index.html") + return Response(content=content, media_type="text/html") + raise HTTPException(status_code=404, detail="Directory index not found") + + # 读取文件内容并确定 MIME 类型 + content = self._vfs.read_file(normalized_path) + content_type = file_info.get("content_type") or "application/octet-stream" + # 简单根据扩展名补充 MIME 类型(可选) + if normalized_path.endswith(".js"): + content_type = "application/javascript" + elif normalized_path.endswith(".css"): + content_type = "text/css" + elif normalized_path.endswith(".html"): + content_type = "text/html" + elif normalized_path.endswith(".ico"): + content_type = "image/x-icon" + + return Response(content=content, media_type=content_type) diff --git a/src/chongming/app/core/worker_manager.py b/src/chongming/app/core/worker_manager.py new file mode 100644 index 0000000..dda940f --- /dev/null +++ b/src/chongming/app/core/worker_manager.py @@ -0,0 +1,1439 @@ +import asyncio +import concurrent.futures +import hashlib +import os +import random +import socket +import time +import orjson +from collections import OrderedDict +from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, List, Optional +import zenoh +from chongming_cache import Cache, DistributedLock +from .logger import get_logger + +logger = get_logger("worker_manager") + +# ── 插件运行时配置(chongming-cache 单一事实来源)── +# 键与 worker SDK(chongming_worker.runtime._configs_cache_key)一致,全局唯一一份 +# [workers.config];读写规则见 WorkerManager.align_inline_config。 +_CONFIGS_KEY = "worker:cm:configs:{type}" + +# ── 全局副本数(chongming-cache 单一事实来源)── +# 多网关共用同一 cache(单实例=本地 diskcache,多实例=共享卷/redis), +# 「count」语义 = 该类 worker 的**全局目标总量**,各网关按在线数量分摊。 +_SCALE_GATEWAYS_KEY = "worker:cm:gateways" # 在线网关列表:[[id, 最近心跳 ts], ...](JSON) + +# 在线网关租约(秒):网关心跳每 refresh_interval(15s) 更新一次自身 ts,超过 LEASE 未刷新 +# 视为离线的死网关,在下一次读写时被清理。容忍多次漏跳,且 crash/kill -9 不残留。 +_GATEWAY_LEASE = 60 + +_SENTINEL = object() # 哨兵值,用于标记"No reply received" + +# Zenoh RPC 专用线程池:隔离于 asyncio.to_thread 的全局默认池 +# (默认池上限 min(32, cpu+4),高并发下会被其他阻塞调用挤占导致排队)。 +# zenoh session.get 是同步阻塞调用,每个进行中的查询占用一个线程; +# wait_for 超时后线程仍阻塞在 zenoh 内部直到其自身超时返回,故池上限即并发上限。 +# 按业务并发量调整(256 ≈ 256 个并发 Zenoh 查询);线程 daemon=True,进程退出不等待。 +_ZENOH_EXECUTOR = concurrent.futures.ThreadPoolExecutor( + max_workers=256, thread_name_prefix="zenoh-rpc" +) + + +class StreamIdleTimeoutError(TimeoutError): + """流式调用空闲超时:在 idle_timeout 秒内未收到任何数据块""" + + +def _next_reply(iterator): + """线程安全地获取迭代器的下一个元素。 + + 用 for 循环 + 哨兵代替直接 next(),避免 StopIteration 在 + asyncio.to_thread 中传播的问题(与 call() 的 _get_first 同模式)。 + 迭代耗尽时返回 _SENTINEL。 + """ + for item in iterator: + return item + return _SENTINEL + + +class _MemLRU: + """极简进程内一级缓存:LRU + TTL。 + + 仅度热路径(cacheable RPC 命中)/JWT 等高频只读复用。单事件循环内读写、 + OrderedDict 实现,O(1) 无锁;作为 diskcache/redis 二级共享缓存的进程级第一级, + 命中时完全绕过网络/磁盘与 Zenoh,降低热点请求延迟与后端压力。 + maxsize 达到上限时淘汰最久未用(LRU)条目;TTL 削防陈旧数据长期驻留。 + """ + + def __init__(self, maxsize: int = 2048, ttl: float = 60.0): + self._d: "OrderedDict[str, tuple]" = OrderedDict() + self._maxsize = maxsize + self._ttl = ttl + + def get(self, key: str): + item = self._d.get(key) + if item is None: + return None + val, expire = item + if expire < time.monotonic(): + self._d.pop(key, None) + return None + self._d.move_to_end(key) # 更新为最近使用 + return val + + def set(self, key: str, val) -> None: + self._d[key] = (val, time.monotonic() + self._ttl) + self._d.move_to_end(key) + if len(self._d) > self._maxsize: + self._d.popitem(last=False) + + +# ── 进程内直调(inline 快路径)───────────────────────────────────────────── +# inline 副本由本进程托管(与网关同事件循环),业务请求却仍要绕 +# "zenoh 查询 → router → queryable 回调 → 跨线程回本 loop"。分派逻辑已统一到 +# chongming_worker.dispatch,故此处可直接 await 同一个 dispatcher,省掉整圈往返。 +# 语义不变:信封/错误通道/超时/重试/换副本/实例定向/in-flight 与 drain 全部沿用原逻辑; +# 非本地副本(external 插件、w>1 时非托管进程)与旧版 worker 包自动回退 zenoh。 +_RPC_LOCAL = os.environ.get("CHONGMING_RPC_LOCAL", "1") != "0" +try: # 旧版 chongming-worker(<0.5)无 dispatch 模块:保持纯 zenoh 行为 + from chongming_worker.dispatch import get_dispatcher as _get_local_dispatcher +except Exception: # noqa: BLE001 + _get_local_dispatcher = None + + +def _local_dispatcher_for(key_expr: str) -> Optional[Any]: + """'worker//' → 本进程托管该副本的 dispatcher;非本地/不可用返回 None""" + if not _RPC_LOCAL or _get_local_dispatcher is None: + return None + parts = key_expr.split("/") + if len(parts) != 3 or parts[0] != "worker": + return None + return _get_local_dispatcher(parts[1], parts[2]) + + +def _err_text(payload: Any) -> str: + """把错误载荷归一为文本(兼容 zenoh ZBytes 与直调的 bytes)""" + if payload is None: + return "unknown error" + if isinstance(payload, (bytes, bytearray)): + return bytes(payload).decode("utf-8", "replace") or "unknown error" + return str(payload) or "unknown error" + + +class WorkerManager: + + """ + 统一 Worker 管理器 + + 封装 Zenoh 会话,维护每个 worker 类型的存活节点列表, + 提供负载均衡、超时重试、缓存等能力的统一 RPC 调用入口。 + + 支持两种 worker 注册方式: + 1. 分布式:通过 Zenoh liveliness 自动发现 + 2. 本地:通过 register_worker() 直接注册(用于同进程 worker) + """ + + def __init__(self, session: zenoh.Session, cache: Optional[Cache] = None, + launch_worker_count: str = "toml"): + self.session = session + self.cache = cache + # RPC 缓存进程内一级(LRU+TTL),兜底并加速 cacheable 命中; + # 二级为共享 cache(diskcache/redis),I/O 移出事件循环走 executor。 + self._rpc_mem_cache = _MemLRU() + # 启动时全局副本数的优先来源:"toml"=用 toml 的 count 覆盖 cache(经典模式); + # "cache"=以 cache 为单一事实来源。对应 config.toml [cache].launch_worker_count。 + self._launch_worker_count = launch_worker_count + self._workers_cache: Dict[str, List[str]] = {} # type -> list of worker key_exprs + self._local_workers: Dict[str, List[str]] = {} # type -> list of locally registered workers + # ── 实例级定向(把请求固定到某个进程实例而非随机负载均衡)── + # 副本经 liveliness 只暴露 worker//,实例身份取自副本 admin status + # 的 instance_id(Python/Rust 运行时均返回),故定向索引按需查询并缓存: + # _instance_index[type][instance_id] = [worker//, ...] + # _instance_unknown[type] = [已查询但身份不可得的副本 key, ...] + # 只缓存已查询过的副本;每次按需刷新时整表重建并对新副本补查询、对离线副本剪枝。 + self._instance_index: Dict[str, Dict[str, List[str]]] = {} + self._instance_unknown: Dict[str, List[str]] = {} + self._instance_locks: Dict[str, asyncio.Lock] = {} + self._lock = asyncio.Lock() + self._refresh_interval = 15 # seconds + # 当前网关心跳标识:优先环境变量(多实例需唯一,尤其同宿主多网关须显式指定), + # 否则回退 hostname(稳定,重启不变——避免用 PID 导致每次重启标识变化、旧条目 + # 残留撑大在线列表,进而让配额计算失真把副本全排空)。用于全局副本数按在线网关分摊。 + self._gateway_id: str = ( + os.environ.get("CHONGMING_GATEWAY_ID") or socket.gethostname() + ) + # inline worker 领导选举持有的分布式锁及持有标记(acquire_inline_owner 时设置) + self._inline_owner_lock: Optional[DistributedLock] = None + self._inline_owner: bool = False + # inline worker 自愈接管:类型 -> 启动协程(load_workers 注册);_started_extra + # 记录接管后拉起的任务,供关停时取消。 + self._worker_starters: Dict[str, Callable[[], Awaitable[List[Any]]]] = {} + self._started_extra: List[Any] = [] + # 超时/无回复副本的冷静期(key_expr -> 到期 monotonic 时间)。 + # 半死副本(如被 SIGKILL 的实例留下的孤儿 worker)只要还在服务发现里, + # 每次被随机选中都会白吃一整轮超时(默认 2s);冷静期让选副本跨请求避开它, + # 把代价从"反复吃"降为"最多吃一次",TTL 到期自动恢复。 + self._suspect_until: Dict[str, float] = {} + + @property + def config_mode(self) -> str: + """本网关 inline 插件的配置优先来源模式。 + + "toml"(默认)= 以 config/workers//config.toml 为准,启动时覆盖缓存; + "cache" = 以 chongming-cache 为准(跨重启保持),toml 仅作引导。 + 对应 config.toml 的 [cache].launch_worker_count,与 worker 侧 + chongming_worker.runtime._resolve_mode 取值一致。 + """ + return "cache" if str(self._launch_worker_count).lower() == "cache" else "toml" + + def align_inline_config( + self, + worker_type: str, + worker_config: Dict[str, Any], + config_section: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """inline 插件初始化前,按配置优先来源模式对齐其 [workers.config](网关侧兜底)。 + + worker SDK 的 Plugin.create_workers 会做同样的事(权威实现,见 + chongming_worker.runtime.resolve_startup_config);网关侧再兜一层,是为了让 + 网关(随本仓库发布)与 worker 包版本解耦:worker 包仍是旧版时,旧版 on_init + 只拿到 config/workers//config.toml 展平出来的配置,会出现“前端显示缓存值、 + 实际跑 toml”。规则与 SDK 一致: + + - "cache":缓存里有该段就用它覆盖传入配置(toml 仅作缓存缺失时的引导并写回, + 之后以缓存为准、跨重启保持); + - "toml" :以 config.toml 为准,把该段覆盖写入缓存(前端读缓存 → 随之显示文件值); + - 未配置缓存后端(cache 为 None)时保持传入配置不动。 + + Args: + worker_type: 插件类型(如 aibox_sophon_adapter) + worker_config: _discover_plugins 展平出来的运行时配置 + config_section: 该插件 config.toml 的 [workers.config] 原始段(引导/覆盖缓存用) + """ + if self.cache is None: + return worker_config + key = _CONFIGS_KEY.format(type=worker_type) + section = config_section if isinstance(config_section, dict) else None + if self.config_mode == "cache": + cached = self.cache.get(key) + if isinstance(cached, dict): + logger.info( + "%s [workers.config] 取自缓存(cache 模式,%d 键)", + worker_type, len(cached), + ) + return {**worker_config, **cached} + if section: + self.cache[key] = dict(section) # 缓存缺失:用 toml 引导并写回 + logger.info("%s [workers.config] 缓存缺失,已用 config.toml 引导", worker_type) + return worker_config + if section is not None: + self.cache[key] = dict(section) # 以文件为准:覆盖缓存,前端随之显示文件值 + logger.info("%s [workers.config] 以 config.toml 为准并覆盖缓存(toml 模式)", worker_type) + return worker_config + + # ── 全局副本数协调(chongming-cache 单一事实来源)──────────── + # count/quota 的读写与分摊已收归 worker 层(chongming_worker.runtime): + # 网关不再直写这些缓存键,仅维护「在线网关列表」(register/heartbeat/unregister)。 + + def _read_gateways_raw(self) -> List[List]: + """读取在线网关原始列表 [[id, ts], ...]。""" + if self.cache is None: + return [] + return list(self.cache.get(_SCALE_GATEWAYS_KEY) or []) + + def _write_gateways(self, gws: List[List]) -> None: + """写回在线网关列表(去重,按 id 稳定)。""" + if self.cache is None: + return + dedup = {} + for gw_id, ts in gws: + dedup.setdefault(gw_id, ts) # 保序去重 + self.cache[_SCALE_GATEWAYS_KEY] = [[gw_id, ts] for gw_id, ts in dedup.items()] + + def _prune_gateways(self, gws: List[List]) -> List[List]: + """剔除超过租约未心跳的死网关,写回清理结果。 + + 兼容旧版残留:旧格式为纯字符串 id 列表(无心跳 ts),视为失效,仅保留 + [[id, ts], ...] 两元组且 ts 在租约内的在线网关。 + """ + deadline = time.time() - _GATEWAY_LEASE + alive: List[List] = [] + for entry in gws: + if not isinstance(entry, (list, tuple)) or len(entry) != 2: + continue # 旧格式/畸形条目视为失效,一并清理 + gw_id, ts = entry + if isinstance(ts, (int, float)) and isinstance(gw_id, str) and ts >= deadline: + alive.append([gw_id, ts]) + if len(alive) != len(gws): + self._write_gateways(alive) + return alive + + def _gateways(self) -> List[str]: + """读取在线网关 id 列表(先按心跳租约清理僵尸条目,杜绝死网关残留)。""" + gws = self._prune_gateways(self._read_gateways_raw()) + return [gw_id for gw_id, _ in gws] + + def register_gateway(self) -> None: + """把当前网关登记进在线列表(带当前心跳时间戳),参与全局分摊。启动时调用一次。""" + if self.cache is None: + return + gws = self._prune_gateways(self._read_gateways_raw()) + gws = [[gw_id, ts] for gw_id, ts in gws if gw_id != self._gateway_id] + gws.append([self._gateway_id, time.time()]) + self._write_gateways(gws) + + def heartbeat_gateway(self) -> None: + """周期性地刷新自身心跳 ts,保持在线;同时清理死网关。由 refresh_workers 循环调用。""" + if self.cache is None: + return + gws = self._prune_gateways(self._read_gateways_raw()) + gws = [[gw_id, ts] for gw_id, ts in gws if gw_id != self._gateway_id] + gws.append([self._gateway_id, time.time()]) + self._write_gateways(gws) + + def unregister_gateway(self) -> None: + """关闭时从在线列表移除当前网关(并清理死网关)。""" + if self.cache is None: + return + gws = self._prune_gateways(self._read_gateways_raw()) + gws = [[gw_id, ts] for gw_id, ts in gws if gw_id != self._gateway_id] + self._write_gateways(gws) + + # ── inline worker 领导选举(gunicorn 多 worker 兼容)──────────────── + # gunicorn `-w N` 会 fork N 个网关进程,各自跑 lifespan 并拉起 inline worker, + # 副本数变成 N×count(副本数量超预期)。用基于共享 cache 的分布式锁做领导选举: + # 每个网关(gateway_id 隔离的实例/宿主)只有一个进程托管其 inline worker,其余 + # 进程只做 HTTP、经 Zenoh liveliness 照常发现调用。 + # 与 MQTT Bridge 同一把锁模式:经共享 cache 后端(redis/共享卷 diskcache)可跨 + # 机器收敛;key 含 gateway_id,多实例各选各的 leader、互不抢占,不破坏分摊语义。 + + def acquire_inline_owner(self) -> bool: + """尝试成为本网关 inline worker 持有者(cache 支撑的分布式锁)。 + + 成功(全局唯一持有者)返回 True,并在后台续约租约;持锁进程崩溃后租约过期, + gunicorn 复活的进程可重新获得(failover)。未配置共享 cache 时单进程视为持有者。 + """ + if self.cache is None: + self._inline_owner = True # 无共享 cache:单进程即持有者 + return True + lock = DistributedLock(self.cache, f"inline_owner:{self._gateway_id}") + if not lock.acquire(): + self._inline_owner = False + return False + lock.start() + self._inline_owner_lock = lock + self._inline_owner = True + logger.info( + "[LOCK] inline owner: 本进程获得持有权 (gw=%s)", self._gateway_id + ) + return True + + async def release_inline_owner(self) -> None: + """释放 inline worker 持有权分布式锁(受保护:仅仍由本进程持有时才删除)。""" + lock = self._inline_owner_lock + if lock is not None: + await lock.stop() + lock.release() + self._inline_owner_lock = None + self._inline_owner = False + + def is_inline_owner(self) -> bool: + """返回本进程是否持有 inline worker 所有权(用于管理/诊断)。""" + return bool(self._inline_owner) + + def register_worker_starter(self, worker_type: str, starter) -> None: + """为某 inline 类型注册一个"拉起 worker"的协程(load_workers 调用), + 供持有者崩溃后的自愈接管使用。starter: async () -> List[Task] + """ + self._worker_starters[worker_type] = starter + + def extra_worker_tasks(self) -> List[Any]: + """返回因自愈接管而拉起的 worker 任务(供网关关停时取消)。""" + return list(self._started_extra) + + async def _maybe_heal_inline(self) -> None: + """持有者进程崩溃后的自愈接管。 + + 若本进程非持有者,却存在「已配置 inline 类型但没有无线活副本」的情况, + 说明原持有者已失效:尝试经分布式锁抢得领导权成为新持有者,并拉起这些 worker。 + 分布式锁保证同一时刻只有一个进程胜出,因此不会出现双写/分裂(split-brain)。 + + 背景:gunicorn 仅复活死掉的 worker 槽位不足以 failover——槽位重启可能在旧锁 + 租约尚未过期时发生,新进程抢锁失败而照样跳过 create_workers。本函数随 refresh + 每周期(~15s)重试,租约过期后即可接管,形成兜底。 + """ + # 已是持有者:正常托管,不走接管路径(避免与常规池任务重复 init) + if self._inline_owner or not self._worker_starters: + return + missing = [ + wtype + for wtype in self._worker_starters + if not self._local_workers.get(wtype) + and not self._workers_cache.get(wtype) + ] + if not missing: + return + # 仍需保证同一时刻仅一个进程成为新持有者 + if not self.acquire_inline_owner(): + return + logger.warning( + "[heal] 检出持有者进程失效,本进程接管并拉起 inline workers: %s", missing + ) + for wtype in missing: + try: + tasks = await self._worker_starters[wtype]() + self._started_extra.extend(tasks or []) + logger.info("[heal] 已拉起 %s (tasks=%d)", wtype, len(tasks or [])) + except Exception as e: # noqa: BLE001 + logger.error("[heal] 拉起 %s 失败: %s", wtype, e) + + def register_worker(self, worker_type: str, worker_id: str): + """ + 直接注册一个本地 worker(同进程内使用) + 这样无需等待 liveliness 发现,立即可用。 + + 同步执行(仅修改内存字典),确保调用后立即可被 get_worker 发现。 + """ + key_expr = f"worker/{worker_type}/{worker_id}" + # 同步直接写入 _local_workers,无需异步锁 + # 因为 _local_workers 和 _workers_cache 在调用 call() 时通过 await get_worker() 读取 + self._local_workers.setdefault(worker_type, []).append(key_expr) + logger.debug(f"本地 worker 已注册: {key_expr}") + + def unregister_worker(self, worker_type: str, worker_id: str): + """取消注册一个本地 worker""" + key_expr = f"worker/{worker_type}/{worker_id}" + if worker_type in self._local_workers: + try: + self._local_workers[worker_type].remove(key_expr) + except ValueError: + pass + # 同步从存活缓存剔除:已关闭/被 drain 的 inline 副本若仍留在 _workers_cache, + # 会在下次 liveliness 刷新(15s)前被 list_workers 误判为 external 并残留显示。 + cache = self._workers_cache.get(worker_type) + if cache and key_expr in cache: + self._workers_cache[worker_type] = [k for k in cache if k != key_expr] + + async def refresh_workers(self): + """定期刷新存活 worker 列表(合并 Zenoh liveliness + 本地注册)""" + while True: + try: + # 1. 从 Zenoh liveliness 获取分布式 workers + replies = self.session.liveliness().get("worker/*/*") + new_cache: Dict[str, List[str]] = {} + for reply in replies: + if reply.ok: + key = str(reply.ok.key_expr) # e.g. "worker/example/worker-0" + parts = key.split("/") + if len(parts) >= 3: + worker_type = parts[1] + new_cache.setdefault(worker_type, []).append(key) + + # 2. 合并本地注册的 workers + for wtype, workers in self._local_workers.items(): + existing = set(new_cache.get(wtype, [])) + for w in workers: + if w not in existing: + new_cache.setdefault(wtype, []).append(w) + + async with self._lock: + self._workers_cache = new_cache + logger.debug(f"Worker 存活列表更新: {self._workers_cache}") + # 3. 网关心跳:刷新自身在线时间戳并清理死网关,保持在线列表准确 + try: + self.heartbeat_gateway() + except Exception as e: # noqa: BLE001 + logger.error(f"网关心跳失败: {e}") + # 4. inline 持有者崩溃自愈接管(见 _maybe_heal_inline) + try: + await self._maybe_heal_inline() + except Exception as e: # noqa: BLE001 + logger.error(f"inline 自愈检查失败: {e}") + except Exception as e: + logger.error(f"刷新 worker 列表失败: {e}") + await asyncio.sleep(self._refresh_interval) + + def _all_workers(self, worker_type: str) -> List[str]: + """该类型当前全部候选副本(liveliness 列表优先,启动初期回退本地注册表)。""" + workers = list(self._workers_cache.get(worker_type, [])) + if not workers: + workers = list(self._local_workers.get(worker_type, [])) + return workers + + # ── 超时副本冷静期 ────────────────────────────────────────────────────── + # 半死副本(父进程被 SIGKILL 留下的孤儿、或事件循环卡死的副本)还在服务发现里: + # 随机选中它就白吃一整轮超时。这里记住"最近失败过的副本"并短时避开, + # 跨请求生效;全部候选都在冷静期时退回全集(宁可慢,也不能无副本可用)。 + + _SUSPECT_TTL = 30.0 # 冷静期时长(秒);到期自动恢复为正常候选 + + def mark_worker_suspect(self, worker_expr: str, ttl: Optional[float] = None) -> None: + """把超时/无回复的副本列入冷静期(选副本时优先跳过)。""" + now = time.monotonic() + self._suspect_until[worker_expr] = now + (self._SUSPECT_TTL if ttl is None else ttl) + if len(self._suspect_until) > 32: # 顺手清理过期项,防长期运行膨胀 + self._suspect_until = { + k: v for k, v in self._suspect_until.items() if v > now + } + + def _filter_suspects(self, workers: List[str]) -> List[str]: + """过滤掉冷静期内的副本;全在冷静期时退回原列表。""" + if not self._suspect_until: + return workers + now = time.monotonic() + healthy = [w for w in workers if self._suspect_until.get(w, 0.0) <= now] + return healthy or workers + + async def _query_instance_id(self, worker_type: str, worker_key: str) -> Optional[str]: + """问单个副本要它的实例身份(admin status 的 instance_id)。 + + 复用单副本管理协议 worker///__admin__;查询失败或副本运行旧版 + 运行时(回复无 instance_id)返回 None,由调用方归入 unknown 组。 + """ + wid = worker_key.split("/")[2] if "/" in worker_key else worker_key + try: + reply = await self._send_admin_cmd(worker_type, wid, "query", timeout=2.0) + except Exception as e: # noqa: BLE001 - 单副本查询失败不影响其它副本 + logger.warning("[instance] %s/%s 身份查询失败: %s", worker_type, wid, e) + return None + iid = reply.get("instance_id") if isinstance(reply, dict) else None + return str(iid) if iid else None + + async def _refresh_instance_map(self, worker_type: str) -> Dict[str, List[str]]: + """重建某类型的「实例 → 副本」索引,并按需补查询新副本。 + + 只对从未查询过的副本发一次 admin status(本地注册的 inline 副本直接返回 + 网关自身实例,零查询);已查询过的副本沿用缓存,因此常规路径是纯内存查询, + 仅新上线副本会引入一次额外 RPC。离线副本在重建时被剪枝。 + """ + workers = self._all_workers(worker_type) + known = self._instance_index.get(worker_type, {}) + unknown = set(self._instance_unknown.get(worker_type, [])) + # 反查已有归属:副本 -> 实例(副本 key 唯一,一个副本只属一个实例) + owner = {w: iid for iid, ws in known.items() for w in ws} + + index: Dict[str, List[str]] = {} + still_unknown: List[str] = [] + queried = owner.keys() | unknown + for w in workers: + if w in queried: + iid = owner.get(w) + else: + # 本地注册(inline)副本天然属于本网关实例,无需查询 + iid = self._gateway_id if w in self._local_workers.get(worker_type, []) else None + if iid is None: + iid = await self._query_instance_id(worker_type, w) + if iid is None: + still_unknown.append(w) + if iid: + index.setdefault(iid, []).append(w) + + self._instance_index[worker_type] = index + self._instance_unknown[worker_type] = still_unknown + return index + + async def _instance_workers(self, worker_type: str, instance_id: str) -> List[str]: + """取某实例下该类型的在线副本 key 列表(无则空表)。""" + index = self._instance_index.get(worker_type) + if index is None: + # 同类型并发请求只让一个跑刷新,其余等结果,避免重复查询同一批副本 + lock = self._instance_locks.setdefault(worker_type, asyncio.Lock()) + async with lock: + index = self._instance_index.get(worker_type) + if index is None: + index = await self._refresh_instance_map(worker_type) + return index.get(instance_id, []) + + async def get_worker( + self, worker_type: str, instance_id: Optional[str] = None + ) -> Optional[str]: + """获取一个可用的 worker 地址。 + + instance_id 为 None 时按简单随机负载均衡挑选(默认,行为不变); + 指定实例时只在该实例的在线副本中挑选,把请求固定到那个进程实例。 + 实例无在线副本(或该实例在旧版运行时下无法识别身份)返回 None。 + + 读路径无锁:_workers_cache 由 refresh_workers 整表原子替换, + _local_workers 由同步方法修改,单事件循环内读取安全。 + """ + if instance_id: + return await self._pick_from_instance(worker_type, instance_id) + workers = self._filter_suspects(self._all_workers(worker_type)) + return random.choice(workers) if workers else None + + async def _pick_from_instance( + self, worker_type: str, instance_id: str + ) -> Optional[str]: + """在指定实例的在线副本中随机挑一个(定向路径,永不跨实例回退)。 + + 定向的语义就是"必须打在同一个进程实例上",因此实例离线时返回 None 让 + 调用方报错,而不是悄悄换成别的实例——后者会让粘滞状态类的插件跑在错误 + 副本上,产生难查的数据错乱。 + """ + candidates = self._filter_suspects( + await self._instance_workers(worker_type, instance_id) + ) + if candidates: + return random.choice(candidates) + logger.warning( + "[instance] 目标实例 %s 下无 %s 的在线副本(已识别实例: %s;%d 个副本读不出身份)", + instance_id, worker_type, sorted(self._instance_index.get(worker_type, {})), + len(self._instance_unknown.get(worker_type, [])), + ) + return None + + async def _pick_other_worker( + self, worker_type: str, exclude: List[str], + instance_id: Optional[str] = None, + ) -> Optional[str]: + """ + 选择一个不同于已尝试列表的 worker。 + + Args: + worker_type: Worker 类型 + exclude: 已尝试(需排除)的 worker key_expr 列表 + instance_id: 定向实例;给定时只在同实例内换副本,绝不跨实例 + + Returns: + 可用的 worker key_expr,若无其他可用则返回 None + """ + if instance_id: + candidates = self._filter_suspects([ + w for w in await self._instance_workers(worker_type, instance_id) + if w not in exclude + ]) + return random.choice(candidates) if candidates else None + workers = self._workers_cache.get(worker_type, []) + candidates = self._filter_suspects([w for w in workers if w not in exclude]) + if candidates: + return random.choice(candidates) + # 也检查本地注册表 + local = self._local_workers.get(worker_type, []) + local_candidates = self._filter_suspects([w for w in local if w not in exclude]) + if local_candidates: + return random.choice(local_candidates) + return None + + def is_success_response(self, result: dict) -> bool: + """根据业务约定判断响应是否成功。 + 假设成功响应没有 'error' 字段,或 'code' 为 0。 + """ + if "error" in result and result["error"]: + return False + if "code" in result and result["code"] != 0: + return False + return True + + async def _no_worker_message( + self, worker_type: str, instance_id: Optional[str] = None + ) -> str: + """ + 无可用 Worker 时的统一诊断文案。 + + 供 call() / call_stream() 共用,保证两类调用错误信息一致;定向(instance_id) + 调用额外列出当前已识别实例,便于区分"实例名写错"与"实例离线"。 + """ + all_known = list(self._workers_cache.keys()) + local_known = list(self._local_workers.keys()) + all_types = sorted(set(all_known + local_known)) + msg = f"类型 '{worker_type}' 没有可用 Worker。" + if instance_id: + msg = ( + f"类型 '{worker_type}' 的实例 '{instance_id}' 没有在线副本(定向调用不" + f"跨实例回退)。已识别实例: " + f"{sorted(self._instance_index.get(worker_type, {})) or '(无)'}。" + f"请检查: 1) 该实例 id 是否写错(取值见 GET " + f"/admin/workers/{worker_type}/instances 或副本启动时的 --instance)" + f" 2) 该实例进程是否在线 3) 该实例的 worker 是否运行旧版运行时" + f"(不支持实例身份上报)" + ) + else: + msg += ( + f"当前已知的 Worker 类型: {all_types if all_types else '(无)'}。" + f"请检查: 1) config/workers/config.toml 是否配置了该类型 " + f"2) 插件初始化是否成功(查看启动日志中的 '加载 Worker 插件失败' 错误)" + f"3) Worker 进程是否正常运行" + ) + return msg + + async def call_stream( + self, + worker_type: str, + subject: str, + payload: Dict[str, Any], + timeout: float = 300.0, + idle_timeout: float = 10.0, + instance_id: Optional[str] = None, + ) -> AsyncGenerator[bytes, None]: + """ + 流式调用:消费 Worker 的多回复流,逐块产出 JSON 字节。 + + 与 call() 的区别: + - 不合并回复(ConsolidationMode.NONE),逐块 yield,实现流式传输 + - 不重试(流一旦开始输出无法安全重放,中途失败只能重建) + - 超时模型: timeout = 流总寿命上限(zenoh query deadline,硬上限, + 流式端点须配大如 300s);idle_timeout = 连续无数据块的最大间隔 + - 错误约定: 块内含 "error" 键(Worker 收尾错误块)或 reply.err + (zenoh 层错误)均为流的终止信号,转发后结束 + + Args: + worker_type: Worker 类型标识(如 "example") + subject: 调用的主题(如 "stream_chat") + payload: 请求参数(信封格式,由路由层构造) + timeout: 流总寿命上限(秒),需大于业务最长生成时长 + idle_timeout: 空闲超时(秒),超时抛 StreamIdleTimeoutError + instance_id: 定向进程实例(None=随机负载均衡);流本身不可重试, + 故实例离线时直接报错,不会中途改派到别的实例 + + Yields: + bytes: 每个回复块的 JSON 字节 + + Raises: + RuntimeError: 无可用 worker(含定向实例无在线副本) + StreamIdleTimeoutError: 空闲超时 + """ + # 急切解析 worker:无可用时立即抛错,让路由层映射 HTTP 503 + # (异步生成器函数体在首次迭代才执行,故这里用普通 async 函数返回内部生成器) + worker_expr = await self.get_worker(worker_type, instance_id=instance_id) + if not worker_expr: + raise RuntimeError(await self._no_worker_message(worker_type, instance_id)) + + full_key = f"{worker_expr}/{subject}" + + # 进程内直调(inline 快路径):与 zenoh 路径同语义(逐块产出、错误块终止、无重试) + local = _local_dispatcher_for(worker_expr) + if local is not None and local.has_subject(subject): + async def _local_stream(): + agen = local.invoke_stream(subject, orjson.dumps(payload)) + try: + while True: + try: + data = await asyncio.wait_for(agen.__anext__(), timeout=idle_timeout) + except StopAsyncIteration: + break + except asyncio.TimeoutError: + raise StreamIdleTimeoutError( + f"Worker {worker_expr} 流式空闲超时 ({idle_timeout}s 无数据)" + ) from None + if not data: + continue + decoded = orjson.loads(data) + if isinstance(decoded, dict) and decoded.get("error"): + # Worker 错误收尾块:转发后终止流 + yield data + return + yield data + finally: + # 消费方提前退出(SSE 断连/网关放弃):停流并取消在途派发 + await agen.aclose() + + return _local_stream() + + reply_iter = self.session.get( + full_key, + payload=orjson.dumps(payload), + consolidation=zenoh.ConsolidationMode.NONE, + timeout=timeout, + ) + + async def _stream(): + # 逐块消费;zenoh reply 迭代器是同步阻塞的,必须放线程执行, + # 避免阻塞事件循环(与 call() 的 _get_first 同模式)。 + # 走专用 _ZENOH_EXECUTOR 池,不与全局默认池的其它阻塞调用争抢 + while True: + try: + loop = asyncio.get_running_loop() + reply = await asyncio.wait_for( + loop.run_in_executor(_ZENOH_EXECUTOR, _next_reply, reply_iter), + timeout=idle_timeout, + ) + except asyncio.TimeoutError: + raise StreamIdleTimeoutError( + f"Worker {worker_expr} 流式空闲超时 ({idle_timeout}s 无数据)" + ) from None + + if reply is _SENTINEL: + break + + if reply.ok: # type: ignore + data = bytes(reply.ok.payload) if reply.ok.payload else b"" # type: ignore + if not data: + continue + decoded = orjson.loads(data) + if isinstance(decoded, dict) and decoded.get("error"): + # Worker 错误收尾块:转发后终止流 + yield data + return + yield data + else: + # zenoh 层错误回复(如 queryable 异常):转发后终止流 + error_payload = reply.err.payload # type: ignore + error_msg = str(error_payload) if error_payload else "unknown error" + logger.warning( + f"Worker {worker_expr} 流式返回错误: {error_msg}" + ) + yield orjson.dumps( + {"error": f"Worker {worker_expr} 返回错误: {error_msg}"} + ) + return + + return _stream() + + # ── 共享缓存二级 I/O(executor 化,不阻塞事件循环)──────────── + # 进程内一级(_rpc_mem_cache)在事件循环内 O(1) 直读;二级 diskcache/redis + # 的 get/set 是同步 I/O,统一丢到 _ZENOH_EXECUTOR 线程执行,避免高并发下 + # 磁盘/网络 I/O 卡死事件循环。值恒为 bytes,选 None 作为未命中哨兵安全。 + + async def _cache_get(self, key: str) -> Optional[bytes]: + if self.cache is None: + return None + loop = asyncio.get_running_loop() + return await loop.run_in_executor(_ZENOH_EXECUTOR, self._sync_cache_get, key) + + def _sync_cache_get(self, key: str) -> Optional[bytes]: + try: + return self.cache.get(key) if self.cache is not None else None + except Exception: + return None + + async def _cache_set(self, key: str, value: bytes) -> None: + if self.cache is None: + return + loop = asyncio.get_running_loop() + await loop.run_in_executor(_ZENOH_EXECUTOR, self._sync_cache_set, key, value) + + def _sync_cache_set(self, key: str, value: bytes) -> None: + try: + if self.cache is not None: + self.cache[key] = value + except Exception: + pass + + async def call( + self, + worker_type: str, + subject: str, + payload: Dict[str, Any], + timeout: float = 3.0, + retries: int = 1, + cacheable: bool = False, + instance_id: Optional[str] = None, + ) -> Any: + """ + 统一调用入口(dict 语义,MQTT Bridge 等调用方使用) + + Args: + worker_type: Worker 类型标识(如 "example") + subject: 调用的主题(如 "calc.add") + payload: 请求参数(信封格式,由路由层构造) + timeout: 单次调用超时(秒) + retries: 失败重试次数 + cacheable: 是否允许缓存结果(若为 False 则跳过缓存读写) + instance_id: 定向进程实例;给定时请求固定发往该实例(不参与随机 + 负载均衡,重试也只在该实例内换副本),None=默认随机均衡 + + Returns: + Dict[str, Any]: 调用结果(worker 回复解析后的 dict) + """ + # cacheable=False 时不计算 cache_key,省一次排序序列化 + cache_key = None + if cacheable: + # 定向调用的缓存键含实例名:定向通常用于读取该实例的本地状态 + # (本地库/设备连接),共用缓存会拿到其它实例的结果 + cache_key = ( + f"{worker_type}:{subject}" + f":{orjson.dumps(payload, option=orjson.OPT_SORT_KEYS).decode()}" + ) + if instance_id: + cache_key = f"{instance_id}@{cache_key}" + reply_bytes = await self._call_impl( + worker_type, subject, orjson.dumps(payload), + timeout=timeout, retries=retries, cacheable=cacheable, cache_key=cache_key, + instance_id=instance_id, + ) + return orjson.loads(reply_bytes) if reply_bytes else {} + + async def call_raw( + self, + worker_type: str, + subject: str, + payload: bytes, + timeout: float = 3.0, + retries: int = 1, + cacheable: bool = False, + instance_id: Optional[str] = None, + ) -> bytes: + """ + 统一调用入口(bytes 语义,HTTP 端点透传用):返回 worker 回复的原始字节。 + + 与 call() 的区别: + - payload 为预序列化的请求字节(普通信封或字节拼接信封),网关不再二次序列化 + - 回复不做 JSON 解析,直接返回原始字节(配合 Response(content=bytes) 零开销转发) + - cacheable 时仅在缓存写入前解析一次做成功检查(业务错误不缓存), + 缓存与返回均保留原始字节 + + Args: + worker_type: Worker 类型标识(如 "example") + subject: 调用的主题(如 "calc.add") + payload: 预序列化的请求字节(信封 JSON) + timeout: 单次调用超时(秒) + retries: 失败重试次数 + cacheable: 是否允许缓存结果(缓存原始字节) + instance_id: 定向进程实例(见 call());None=默认随机均衡 + + Returns: + bytes: worker 回复的原始字节(空回复兜底 b"{}",与 call() 的 {} 语义一致) + """ + # cacheable=False 时不计算 cache_key(当前唯一用途),省一次 sha256 + cache_key = None + if cacheable: + cache_key = f"{worker_type}:{subject}:{hashlib.sha256(payload).hexdigest()}" + if instance_id: + cache_key = f"{instance_id}@{cache_key}" + return await self._call_impl( + worker_type, subject, payload, + timeout=timeout, retries=retries, cacheable=cacheable, cache_key=cache_key, + instance_id=instance_id, + ) + + async def _call_impl( + self, + worker_type: str, + subject: str, + payload_bytes: bytes, + timeout: float = 3.0, + retries: int = 1, + cacheable: bool = False, + cache_key: Optional[str] = None, + instance_id: Optional[str] = None, + ) -> bytes: + """ + 调用实现:缓存 > 选副本(随机均衡/实例定向)> 超时重试,返回回复的原始字节。 + + call() / call_raw() 共用本实现,仅入参出参形态不同(dict ↔ bytes)。 + instance_id 给定时选副本与重试都被约束在该实例内(定向语义,不跨实例回退)。 + """ + # 1. 缓存检查(仅在 cacheable=True 时检查) + if cacheable and cache_key is not None: + # 一级:进程内内存 LRU(事件循环内 O(1),零 I/O,命中完全绕过 Zenoh) + mem_val = self._rpc_mem_cache.get(cache_key) + if mem_val is not None: + logger.debug(f"命中内存缓存: {cache_key}") + return mem_val + # 二级:共享缓存(diskcache/redis),I/O 移到 executor,不阻塞事件循环 + if self.cache is not None: + disk_val = await self._cache_get(cache_key) + if disk_val is not None: + self._rpc_mem_cache.set(cache_key, disk_val) + logger.debug(f"命中共享缓存: {cache_key}") + return disk_val + + # 2. 获取可用 worker(instance_id 给定时固定到该实例,否则随机均衡) + worker_expr = await self.get_worker(worker_type, instance_id=instance_id) + if not worker_expr: + raise RuntimeError(await self._no_worker_message(worker_type, instance_id)) + + full_key = f"{worker_expr}/{subject}" + + last_error = None + tried_workers: List[str] = [] + + async def _switch_worker(why: str) -> bool: + """换一个副本重试;返回是否值得继续(定向调用只在同实例内换副本)。 + + 换完更新闭包外层的 worker_expr / full_key。定向实例内已无未试副本时 + 返回 False —— 继续循环只会重复打同一个坏副本,白等一整轮超时。 + """ + nonlocal worker_expr, full_key, last_error + new_worker = await self._pick_other_worker( + worker_type, tried_workers, instance_id=instance_id + ) + if new_worker: + if new_worker != worker_expr: + logger.info(f"{why}切换 Worker: {worker_expr} -> {new_worker}") + worker_expr = new_worker + full_key = f"{worker_expr}/{subject}" + return True + # 没有任何未试过的副本可换:继续循环只会重复打同一个副本(若它已死, + # 白等一整轮超时;若它已回业务错误,结果必然相同),直接结束重试。 + # 保留具体失败原因(last_error 已由调用方按场景写入),便于定位。 + scope = f"实例 '{instance_id}' 内" if instance_id else "" + last_error = f"{scope}已无其它未试副本可换({why}):{last_error}" + return False + + attempt = 0 + for attempt in range(retries + 1): + # 记录已尝试的 worker + if worker_expr not in tried_workers: + tried_workers.append(worker_expr) + + try: + local = _local_dispatcher_for(worker_expr) + if local is not None and local.has_subject(subject): + # 进程内直调(inline 快路径):省 zenoh 往返,语义与下面 zenoh 路径一致 + logger.debug(f"[local-rpc] {worker_expr}/{subject} 进程内直调") + local_res = await asyncio.wait_for( + local.invoke_unary(subject, payload_bytes), timeout=timeout + ) + reply_ok, reply_payload = local_res.ok, local_res.payload + else: + reply_iter = self.session.get( + full_key, + payload=payload_bytes, + consolidation=zenoh.ConsolidationMode.NONE, + timeout=timeout, + ) + # 使用辅助函数获取第一个回复,避免 StopIteration 在 asyncio.to_thread 中传播的问题 + def _get_first(iterator): + """线程安全地获取迭代器的第一个元素""" + for item in iterator: + return item + return _SENTINEL + + # 走专用 _ZENOH_EXECUTOR 池(隔离于全局默认池,避免高并发下排队) + loop = asyncio.get_running_loop() + first_reply = await asyncio.wait_for( + loop.run_in_executor(_ZENOH_EXECUTOR, _get_first, reply_iter), + timeout=timeout, + ) + if first_reply is _SENTINEL: + logger.warning( + f"Worker {worker_expr} 无回复,尝试重试 {attempt+1}/{retries+1}" + ) + last_error = f"Worker {worker_expr} 无回复" + self.mark_worker_suspect(worker_expr) + if not await _switch_worker("无回复"): + break + continue + + reply = first_reply + if reply.ok: # type: ignore + reply_ok = True + reply_payload = bytes(reply.ok.payload) if reply.ok.payload else b"{}" # type: ignore + else: + reply_ok = False + reply_payload = reply.err.payload # type: ignore + + if reply_ok: + reply_bytes = bytes(reply_payload) if reply_payload else b"{}" + # 缓存写入(仅在 cacheable=True 时):解析一次做成功检查, + # 但缓存与返回均保留原始字节(网关侧零二次序列化)。 + # 先写内存一级,再异步写共享二级(executor,不阻塞响应返回) + if cacheable and cache_key is not None and reply_bytes: + parsed = orjson.loads(reply_bytes) + if self.is_success_response(parsed): + self._rpc_mem_cache.set(cache_key, reply_bytes) + await self._cache_set(cache_key, reply_bytes) + return reply_bytes + else: + error_msg = _err_text(reply_payload) + logger.warning(f"Worker 返回错误: {error_msg} (尝试 {attempt+1}/{retries+1})") + last_error = f"Worker {worker_expr} 返回错误: {error_msg}" + # 错误回复(含 Zenoh 超时)也换一个 worker 重试 + if not await _switch_worker("错误后"): + break + continue # 重试 + except asyncio.TimeoutError: + logger.warning( + f"Worker {worker_expr} 超时 (>{timeout}s),尝试重试 {attempt+1}/{retries+1}" + ) + last_error = f"Worker {worker_expr} 超时 (>{timeout}s)" + self.mark_worker_suspect(worker_expr) + # 超时后换一个 worker 重试,避免打到仍在处理中的同一个 worker + if not await _switch_worker("超时后"): + break + continue + except Exception as e: + logger.error(f"调用 worker 异常: {e}") + raise + + raise RuntimeError( + f"Worker '{worker_type}' 调用失败 (已尝试 {len(tried_workers)} 个 Worker, " + f"实际尝试 {attempt + 1} 次/上限 {retries + 1} 次, timeout={timeout}s): {last_error}" + ) + + # ------------------------------------------------------------------ + # 管理能力:盘点 worker 实例 + 精细化控制单个副本(admin Queryable) + # + # 单副本协议(worker 框架声明):worker///__admin__ + # POST {"data": {"cmd": "restart"}} 平滑退出并以新 worker_id 重建 + # POST {"data": {"cmd": "stop"}} 平滑退出且不自动重建 + # (无 cmd / GET) 返回副本实时状态 + # ------------------------------------------------------------------ + + def list_workers(self) -> List[Dict[str, Any]]: + """ + 盘点当前全部 worker 实例。 + + 合并 Zenoh liveliness 与本地注册表: + - 仅出现在本地注册表 / 也出现在 liveliness 的 = inline(网关进程内加载) + - 仅出现在 liveliness 的 = external(独立进程) + 每个实例含 worker_type / worker_id / mode / alive。 + + Returns: + List[{"worker_type", "worker_id", "mode"}] + """ + result: List[Dict[str, Any]] = [] + # 本地注册(inline):type -> [key],key 形如 "worker//" + local_keys: Dict[str, List[str]] = { + t: list(v) for t, v in self._local_workers.items() + } + for wtype, keys in local_keys.items(): + for key in keys: + parts = key.split("/") + wid = parts[2] if len(parts) >= 3 else key + result.append({ + "worker_type": wtype, + "worker_id": wid, + "mode": "inline", + "alive": True, + }) + # liveliness(inline 亦会出现,去重;其余为 external) + known = {(r["worker_type"], r["worker_id"]) for r in result} + for wtype, keys in self._workers_cache.items(): + for key in keys: + parts = key.split("/") + if len(parts) < 3: + continue + wid = parts[2] + if (wtype, wid) in known: + continue + result.append({ + "worker_type": wtype, + "worker_id": wid, + "mode": "external", + "alive": True, + }) + result.sort(key=lambda w: (w["worker_type"], w["worker_id"])) + return result + + async def _send_admin_cmd( + self, + worker_type: str, + worker_id: str, + cmd: str, + timeout: float = 5.0, + data_extra: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """向单个副本的 admin Queryable 下发指令并等待回复。 + + Args: + worker_type: Worker 类型 + worker_id: 副本 id(如 worker-0-1-12345) + cmd: "restart" / "stop" / "config_set" + timeout: 单次下发超时(秒) + data_extra: 追加到 data 字段的附加载荷(如 config_set 的 config) + + Returns: + worker 侧回复(dict) + + Raises: + RuntimeError: 无可用副本 / 下发失败 / 超时 + """ + admin_key = f"worker/{worker_type}/{worker_id}/__admin__" + data = {"cmd": cmd, **(data_extra or {})} + payload = orjson.dumps({"meta": {"method": "POST"}, "data": data}) + reply_iter = self.session.get( + admin_key, + payload=payload, + consolidation=zenoh.ConsolidationMode.NONE, + timeout=timeout, + ) + loop = asyncio.get_running_loop() + reply = await asyncio.wait_for( + loop.run_in_executor(_ZENOH_EXECUTOR, _next_reply, reply_iter), + timeout=timeout, + ) + if reply is _SENTINEL: + raise RuntimeError( + f"副本 {worker_type}/{worker_id} 未响应管理指令(可能不在线," + "或该 Worker 运行旧版运行时未声明 __admin__ 管理能力)" + ) + if reply.ok: # type: ignore + data = bytes(reply.ok.payload) if reply.ok.payload else b"{}" # type: ignore + return orjson.loads(data) + error_payload = reply.err.payload # type: ignore + error_msg = str(error_payload) if error_payload else "unknown error" + raise RuntimeError(f"副本 {worker_type}/{worker_id} 管理指令失败: {error_msg}") + + async def restart_worker( + self, + worker_type: str, + worker_id: str, + timeout: float = 5.0, + ) -> Dict[str, Any]: + """精细化重启单个 worker 副本(经 Zenoh 下发 restart,副本以新 id 重建)。""" + return await self._send_admin_cmd(worker_type, worker_id, "restart", timeout) + + async def stop_worker( + self, + worker_type: str, + worker_id: str, + timeout: float = 5.0, + ) -> Dict[str, Any]: + """精细化停止单个 worker 副本(经 Zenoh 下发 stop,副本退出且不自动重建)。""" + return await self._send_admin_cmd(worker_type, worker_id, "stop", timeout) + + async def scale_workers( + self, + worker_type: str, + count: int, + timeout: float = 5.0, + ) -> Dict[str, Any]: + """把某类型的副本数调整为 count(扩缩容)并持久化到配置。 + + 下发逻辑(与 inline/external 的统一管理协议一致): + - inline:类型在本进程内有 WorkerPool,只下发本网关进程内的池(进程内扩缩, + 无需跨进程广播,副本按其所在网关分摊配额); + - external:类型由独立进程/容器运行,向该类型**全部存活**副本经 Zenoh 广播 + rescale 指令,由各进程内 WorkerPool 按「全局 count ÷ 存活实例数」重新分摊 + 并各写回自身配置(避免只缩/扩其中一个实例造成不一致)。 + 全局缩放同时清除各实例的「逐实例绝对副本数」覆盖,回到对称分摊——因此它是 + 「一键复位对称」入口;逐实例精细缩放走 scale_instance(见后)。 + + 缩容走优雅排空:摘除 liveliness/业务 Queryable 隔绝新请求,等正在处理的 + 在途请求完成后再关闭副本(避免中断业务)。 + + Args: + worker_type: 插件类型 + count: 目标副本总数(全局总量,>= 0) + timeout: 下发超时(秒) + + Returns: + {"worker_type", "target"(全局总量), "mode", "sent_to", "reply"} + + Raises: + RuntimeError: count 非法 / 类型无可用副本 + """ + count = max(0, int(count)) + # 「count=全局总量」收归 worker 层:网关不再直写 count/quota 缓存、不再直调进程池。 + # 统一经 rescale 下发全局 target,由 worker 读取在线网关分摊、写缓存并扩缩本进程副本; + # inline/external 口径一致(inline 副本在本进程,经 Zenoh 直达其 __admin__)。 + inline = False + try: + from chongming_worker.runtime import get_process_pool + inline = get_process_pool(worker_type) is not None + except Exception: # noqa: BLE001 - 运行时缺依赖视为非 inline + inline = False + candidates = [ + w for w in self.list_workers() + if w["worker_type"] == worker_type and w.get("alive") + ] + if not candidates: + raise RuntimeError(f"类型 '{worker_type}' 没有可用副本,无法调整数量") + + # 目标选择: + # - inline:只下发本网关进程内的池(任意一个 inline 候选都会落到本地池,幂等); + # - external:同一类型可能有多个独立进程/实例,必须广播到全部存活实例,由各自按 + # 「全局 count ÷ 存活实例数」重新分摊,避免只缩/扩其中一个进程造成不一致。 + if inline: + inline_cands = [c for c in candidates if c.get("mode") == "inline"] or candidates + targets = [inline_cands[0]] + else: + targets = candidates + replies: Dict[str, Any] = {} + for cand in targets: + try: + replies[cand["worker_id"]] = await self._send_admin_cmd( + worker_type, cand["worker_id"], "rescale", + timeout=timeout, data_extra={"scale": count}, + ) + except Exception as e: # noqa: BLE001 - 单个实例失败不影响其它实例 + logger.warning("[scale] %s 下发失败 %s: %s", worker_type, cand["worker_id"], e) + if not replies: + raise RuntimeError(f"类型 '{worker_type}' 没有副本响应 rescale,无法调整数量") + logger.info( + "[scale] %s 全局目标=%d 下发 %d 个实例(%s)", + worker_type, count, len(replies), "inline" if inline else "external", + ) + return { + "worker_type": worker_type, + "target": count, + "mode": "inline" if inline else "external", + "sent_to": list(replies.keys()), + "reply": replies, + } + + async def list_instances( + self, + worker_type: str, + timeout: float = 3.0, + ) -> List[Dict[str, Any]]: + """按「外部进程实例」分组盘点某类型的落地实体。 + + 对类型下每个存活副本发轻量 status 查询,从回复取 instance_id 归组; + 一个副本组 = 一个能独立承载若干副本的执行单元(inline=本网关进程/网关 host, + external=独立进程/容器)。副本数=组内在线副本个数,worker_ids 供精细缩放寻址。 + """ + workers = [ + w for w in self.list_workers() + if w["worker_type"] == worker_type and w.get("alive") + ] + groups: Dict[str, Dict[str, Any]] = {} + for w in workers: + try: + reply = await self._send_admin_cmd( + worker_type, w["worker_id"], "query", timeout=timeout + ) + iid = reply.get("instance_id") or "unknown" + except Exception as e: # noqa: BLE001 - 单副本查询失败不阻塞整体 + logger.warning("[instances] %s/%s 查询失败: %s", worker_type, w["worker_id"], e) + iid = "unknown" + g = groups.setdefault(iid, { + "worker_type": worker_type, + "instance_id": iid, + "mode": w.get("mode", "external"), + "replicas": 0, + "worker_ids": [], + }) + g["replicas"] += 1 + g["worker_ids"].append(w["worker_id"]) + groups.pop("unknown", None) # 拿不到身份的一律归入 unknown,避免误当独立实例 + return list(groups.values()) + + async def scale_instance( + self, + worker_type: str, + instance_id: str, + count: int, + timeout: float = 5.0, + ) -> Dict[str, Any]: + """把某个外部进程/**实例**精确调到 count 个副本(绝对数,不影响其它实例)。 + + 经该实例任一副本的 admin Queryable 下发 `rescale{scale:count, instance_id, + absolute:true}`,由其进程内 WorkerPool 直接 set_target(count) 并写入 instcounts + 覆盖表(进程重启后仍生效);目标其它实例的指令被 worker 安全忽略。 + 返回与原 /scale 结构兼容:{worker_type, target, mode, sent_to, reply}。 + """ + count = max(0, int(count)) + insts = await self.list_instances(worker_type) + inst = next((i for i in insts if i["instance_id"] == instance_id), None) + if inst is None or not inst["worker_ids"]: + raise RuntimeError( + f"类型 '{worker_type}' 下没有在线副本归属实例 '{instance_id}',无法调整数量" + ) + wid = inst["worker_ids"][0] + reply = await self._send_admin_cmd( + worker_type, wid, "rescale", + timeout=timeout, + data_extra={"scale": count, "instance_id": instance_id, "absolute": True}, + ) + logger.info( + "[scale_instance] %s 实例=%s 目标=%d → 下发 %s", worker_type, instance_id, count, wid, + ) + return { + "worker_type": worker_type, + "instance_id": instance_id, + "target": count, + "mode": inst.get("mode", "external"), + "sent_to": wid, + "reply": reply, + } + + async def get_worker_config( + self, + worker_type: str, + timeout: float = 5.0, + ) -> Dict[str, Any]: + """经 Zenoh 向该类型任一存活副本查询其 [workers.config]。 + + 由 worker 访问自身缓存(或回退 toml 种子)返回;网关不直接接触外部 worker 的缓存后端。 + + Returns: + {"status", "config": {...}, "source": "cache"|"toml", "mode": "toml"|"cache", + "worker_id", ...} + + Raises: + RuntimeError: 无存活副本 / 副本未响应(可能运行旧版运行时无 get_config 能力) + """ + candidate = next( + ( + w for w in self.list_workers() + if w["worker_type"] == worker_type and w.get("alive") + ), + None, + ) + if candidate is None: + raise RuntimeError(f"类型 '{worker_type}' 没有在线副本,无法读取其配置") + reply = await self._send_admin_cmd( + worker_type, candidate["worker_id"], "get_config", timeout=timeout + ) + return { + "worker_type": worker_type, + "worker_id": candidate["worker_id"], + "status": reply.get("status"), + "config": reply.get("config"), + "source": reply.get("source", "toml"), + # 旧版运行时回复无 mode:按本网关 inline 模式兜底(external 副本以自身 + # [workers.cache].launch_worker_count 为准,新运行时会在回复里带回) + "mode": reply.get("mode") or self.config_mode, + } + + async def update_worker_config( + self, + worker_type: str, + updates: Dict[str, Any], + timeout: float = 5.0, + ) -> Dict[str, Any]: + """经 Zenoh 下发 config_set 指令,让目标类型的全部副本修改配置并自我重启。 + + worker 侧行为(见 chongming_worker.runtime): + - 仅 index0 副本把 updates 写入共享缓存的 [workers.config](key + worker:cm:configs:{type},全局唯一,路由/保留键会被拒绝);不再回写 config.toml。 + - 所有副本合并 updates 后以新配置重启(新 worker_id)。 + + 本方法只负责"下发 + 汇总",写操作由 worker 访问其自身缓存触发执行,故进程插件 + 位于不同路径/宿主机时也能把配置持久化到各自的共享缓存后端。 + + Args: + worker_type: 插件类型 + updates: 待写入 [workers.config] 的键值({"key": value}) + timeout: 单副本下发超时(秒) + + Returns: + { + "total": 下发副本数, + "failed": 失败数, "ok": bool, + "results": [{worker_id, ok, reply|error}], + "errors": [错误信息列表] + } + + Raises: + RuntimeError: 该类型无任何存活副本 + """ + candidates = [ + w for w in self.list_workers() + if w["worker_type"] == worker_type + ] + if not candidates: + raise RuntimeError(f"类型 '{worker_type}' 没有可用 Worker,无法更新其配置") + + results: List[Dict[str, Any]] = [] + errors: List[str] = [] + for w in candidates: + try: + reply = await self._send_admin_cmd( + worker_type, w["worker_id"], "config_set", + timeout=timeout, data_extra={"config": updates}, + ) + ok = reply.get("status") == "accepted" + if not ok: + errors.append( + f"{w['worker_id']}: {reply.get('error', '未知错误')}" + ) + results.append({ + "worker_id": w["worker_id"], "ok": ok, "reply": reply, + }) + except Exception as e: # noqa: BLE001 - 单副本失败不中断其他副本 + errors.append(f"{w['worker_id']}: {e}") + results.append({ + "worker_id": w["worker_id"], "ok": False, "error": str(e), + }) + + return { + "total": len(results), + "failed": len(errors), + "ok": not errors, + "results": results, + "errors": errors, + } diff --git a/src/chongming/app/routes/__init__.py b/src/chongming/app/routes/__init__.py new file mode 100644 index 0000000..194527a --- /dev/null +++ b/src/chongming/app/routes/__init__.py @@ -0,0 +1,5 @@ +from .stats import router as stats_router +from .worker_admin import router as worker_admin_router +from .auth import router as auth_router + +__all__ = ["stats_router", "worker_admin_router", "auth_router"] \ No newline at end of file diff --git a/src/chongming/app/routes/auth.py b/src/chongming/app/routes/auth.py new file mode 100644 index 0000000..f6681cb --- /dev/null +++ b/src/chongming/app/routes/auth.py @@ -0,0 +1,53 @@ +"""内置登录端点 —— auth 域的基础设施部分(管理平面可用性底线)。 + +拆分自 plugins.auth:登录 / token 签发不再依赖 auth 插件是否加载, +auth 插件缩容到 0 或全部副本故障时,登录与 admin API 仍可用; +用户管理功能(注册 / 资料 / 改密)仍由 auth 插件提供。 + +共享契约(Users 模型 / claims / 失败文案)来自 chongming_auth 包, +路径与响应 shape 与拆分前完全一致(前端零改动)。 +""" +import time + +from chongming_auth import ( + DEFAULT_TOKEN_EXPIRES_SECONDS, + LoginPostRequest, + LoginPostResponse, + issue_token, + touch_last_login, + verify_login, +) +from chongming_config import get_config +from fastapi import APIRouter, Request + +config = get_config() + +router = APIRouter(prefix=config["default"]["prefix"], tags=["auth"]) + + +@router.post( + "/auth/login", + response_model=LoginPostResponse, + summary="登录", + description="根据用户名和密码进行登录,登录成功返回token,获取请求需带式token进行请求", +) +async def login(body: LoginPostRequest, request: Request) -> LoginPostResponse: + db = request.app.state.auth_db + user, message = await verify_login(db, body.username, body.password) + if user is None: + return LoginPostResponse( + result="fail", token="", message=message, timestamp=time.time() + ) + + await touch_last_login(db, user) + + security = get_config()["security"] + token = issue_token( + user, + security["secret_key"], + security["algorithm"], + expires_in_seconds=DEFAULT_TOKEN_EXPIRES_SECONDS, + ) + return LoginPostResponse( + result="success", token=token, message="登录成功", timestamp=time.time() + ) diff --git a/src/chongming/app/routes/stats.py b/src/chongming/app/routes/stats.py new file mode 100644 index 0000000..637ff42 --- /dev/null +++ b/src/chongming/app/routes/stats.py @@ -0,0 +1,23 @@ +# src/chongming/app/routes/stats.py +""" +网关级调用统计查看端点 + +数据来源为 core.call_stats 的内存统计(进程内视角): +动态路由每次调用在后台任务中更新计数/延迟聚合/最近调用缓冲, +此处提供只读快照,便于观测接口调用量、错误分布与延迟。 +""" +from fastapi import APIRouter + +from chongming_config import get_config + +from ..core import call_stats + +config = get_config() + +router = APIRouter(prefix=config["default"]["prefix"], tags=["stats"]) + + +@router.get("/stats", summary="调用统计") +async def get_call_stats(): + """返回内存调用统计快照:总量、per worker/subject/status 计数、延迟聚合、最近调用""" + return call_stats.summary() diff --git a/src/chongming/app/routes/worker_admin.py b/src/chongming/app/routes/worker_admin.py new file mode 100644 index 0000000..6fb7c55 --- /dev/null +++ b/src/chongming/app/routes/worker_admin.py @@ -0,0 +1,329 @@ +""" +Worker 管理端点(插件 / 单副本重启) + +提供插件实例盘点与"精细化重启单个 worker 副本"的能力: + - 经 Zenoh 下发管理指令到目标的 admin Queryable(worker///__admin__) + - 仅超级管理员(JWT payload.is_superuser)可访问 + +协议说明(见 chongming_worker.runtime 单副本管理协议): + - 每个 worker 副本在 worker///__admin__ 注册内部 Queryable + - {"data": {"cmd": "restart"}} → 副本平滑退出并以新 worker_id 重建 + - {"data": {"cmd": "stop"}} → 副本平滑退出且不自动重建 +""" +from fastapi import APIRouter, Depends, HTTPException, Request +from pydantic import BaseModel, Field +import pathlib +import tomllib + +from chongming_config import get_config + +from ..core.auth import jwt_auth_dependency +from ..core.logger import get_logger + +config = get_config() + +logger = get_logger("worker_admin") + +router = APIRouter(prefix=config["default"]["prefix"], tags=["worker_admin"]) + + +def require_admin( + payload: dict = Depends(jwt_auth_dependency), +) -> dict: + """仅超级管理员可操作(重启属高风险操作,普通登录用户无权限)。""" + if not payload.get("is_superuser"): + raise HTTPException( + status_code=403, + detail="需要超级管理员权限 (is_superuser)", + ) + return payload + + +def _get_manager(request: Request): + """从应用状态获取 WorkerManager;未初始化时(lifespan 外)报 503。""" + manager = getattr(request.app.state, "worker_manager", None) + if manager is None: + raise HTTPException(status_code=503, detail="WorkerManager 尚未就绪") + return manager + + +@router.get("/admin/workers", summary="盘点 Worker 实例") +async def list_workers( + request: Request, + _admin: dict = Depends(require_admin), +): + """返回全部插件实例(worker_type / worker_id / mode / alive),供前端渲染与选择。""" + manager = _get_manager(request) + return {"workers": manager.list_workers(), "success": True} + + +@router.post("/admin/workers/restart", summary="重启单个 Worker 副本") +async def restart_worker( + request: Request, + worker_type: str, + worker_id: str, + _admin: dict = Depends(require_admin), +): + """精细化重启单个 worker 副本:经 Zenoh 下发 restart,副本以新 worker_id 重建。 + + - worker_type: 插件类型(如 example) + - worker_id: 副本 id(如 worker-0-1-12345),由 GET /admin/workers 提供 + """ + manager = _get_manager(request) + try: + result = await manager.restart_worker(worker_type, worker_id) + except RuntimeError as e: + logger.warning( + "重启 Worker 副本失败: %s/%s 用户=%s 错误=%s", + worker_type, worker_id, + _admin.get("username", _admin.get("sub", "-")), + e, + ) + raise HTTPException( + status_code=502, + detail=f"重启失败: {e}", + ) + logger.info( + "重启 Worker 副本: %s/%s 用户=%s 结果=%s", + worker_type, worker_id, + _admin.get("username", _admin.get("sub", "-")), + result, + ) + return {"success": True, "result": result} + + +@router.post("/admin/workers/{worker_type}/restart", summary="重启整个插件(全部副本)") +async def restart_all( + request: Request, + worker_type: str, + _admin: dict = Depends(require_admin), +): + """重启某插件的全部副本(逐个下发 restart,各自以新 id 重建)。 + + 注意:网关进程内加载的 inline 插件副本经本接口重启,仅重建 worker 协程, + 不会卸载插件;external 插件同理逐个副本重启。 + """ + manager = _get_manager(request) + candidates = [ + w for w in manager.list_workers() + if w["worker_type"] == worker_type + ] + if not candidates: + raise HTTPException(status_code=404, detail=f"无类型为 {worker_type} 的 worker") + results = [] + failed = [] + for w in candidates: + try: + res = await manager.restart_worker(worker_type, w["worker_id"]) + results.append({"worker_id": w["worker_id"], "ok": True, "result": res}) + except Exception as e: # noqa: BLE001 - 批量重启不因单个失败中断 + failed.append({"worker_id": w["worker_id"], "ok": False, "error": str(e)}) + results.append({"worker_id": w["worker_id"], "ok": False, "error": str(e)}) + logger.info( + "重启插件全部副本: %s (成功 %d/%d) 用户=%s", + worker_type, len(results) - len(failed), len(results), + _admin.get("username", _admin.get("sub", "-")), + ) + return {"success": not failed, "total": len(results), "failed": len(failed), "results": results} + + +class ScaleBody(BaseModel): + """副本数量调整请求体:目标副本数。""" + count: int = Field(..., ge=0, description="目标副本数(>=0)") + + +@router.post("/admin/workers/{worker_type}/scale", summary="调整同类 worker 副本数量(扩缩容)") +async def scale_workers( + request: Request, + worker_type: str, + body: ScaleBody, + _admin: dict = Depends(require_admin), +): + """把某类型的副本数调整为 body.count 并持久化到配置。 + + - 扩容新增副本;缩容随机关闭副本,并**优雅排空**(摘除 liveliness/业务 + Queryable 隔绝新请求,等待正在处理的在途请求完成后再关闭)。 + - inline 直接调整本进程 Worker 池;external 经 Zenoh 下发 rescale 到存活副本, + 由其进程内池扩缩并写回自身配置。 + """ + manager = _get_manager(request) + try: + result = await manager.scale_workers(worker_type, body.count) + except Exception as e: # noqa: BLE001 - 无副本/失败映射为 4xx + logger.warning( + "调整副本数量失败: %s -> %d 用户=%s 错误=%s", + worker_type, body.count, + _admin.get("username", _admin.get("sub", "-")), + e, + ) + raise HTTPException(status_code=404, detail=str(e)) + logger.info( + "调整副本数量: %s -> %d 用户=%s 结果=%s", + worker_type, body.count, + _admin.get("username", _admin.get("sub", "-")), + result, + ) + return {"success": True, "result": result} + + +class ScaleInstanceBody(BaseModel): + """逐实例精细扩缩请求体:目标实例 + 该实例的目标副本数。""" + instance_id: str = Field(..., description="外部进程/实例身份(list_instances 返回)") + count: int = Field(..., ge=0, description="该实例的目标副本数(绝对数,>=0)") + + +@router.get( + "/admin/workers/{worker_type}/instances", + summary="按实例分组列出某类型 worker(精细扩缩寻址)", +) +async def list_worker_instances( + request: Request, + worker_type: str, + _admin: dict = Depends(require_admin), +): + """把某类型的在线副本按「外部进程实例」分组返回。 + + 每个实例 = 一个可独立承载副本的执行单元(inline=本网关进程/host, + external=独立进程/容器),含当前副本数与可选中的 worker_id。 + """ + manager = _get_manager(request) + try: + instances = await manager.list_instances(worker_type) + except Exception as e: # noqa: BLE001 + logger.warning("列出实例失败: %s 错误=%s", worker_type, e) + raise HTTPException(status_code=404, detail=str(e)) + return {"success": True, "result": instances} + + +@router.post( + "/admin/workers/{worker_type}/scale_instance", + summary="把某实例精确扩缩到指定副本数(精细扩缩容)", +) +async def scale_worker_instance( + request: Request, + worker_type: str, + body: ScaleInstanceBody, + _admin: dict = Depends(require_admin), +): + """把指定外部进程/**实例**精确调整到 body.count 个副本(绝对数),不影响其它实例。 + + 经该实例任一副本下发 rescale{scale, instance_id, absolute:true},由其进程内 + WorkerPool 直接扩缩并记入逐实例覆盖表;重启后仍保持该绝对数。 + """ + manager = _get_manager(request) + try: + result = await manager.scale_instance( + worker_type, body.instance_id, body.count + ) + except Exception as e: # noqa: BLE001 + logger.warning( + "逐实例扩缩失败: %s instance=%s -> %d 用户=%s 错误=%s", + worker_type, body.instance_id, body.count, + _admin.get("username", _admin.get("sub", "-")), + e, + ) + raise HTTPException(status_code=404, detail=str(e)) + logger.info( + "逐实例扩缩: %s instance=%s -> %d 用户=%s 结果=%s", + worker_type, body.instance_id, body.count, + _admin.get("username", _admin.get("sub", "-")), + result, + ) + return {"success": True, "result": result} + + +class ConfigUpdateBody(BaseModel): + """配置更新请求体:写目标插件 config.toml 的 [workers.config] 段。""" + updates: dict = Field(..., description="待写入 [workers.config] 的键值(路由/加载控制键除外)") + + +@router.get("/admin/workers/{worker_type}/config", summary="读取插件现有运行时配置") +async def read_worker_config( + request: Request, + worker_type: str, + _admin: dict = Depends(require_admin), +): + """读取某插件 config.toml 的 [workers.config] 现有值(供前端展示)。 + + 经 Zenoh 向该类任一存活副本下发 get_config,由其访问自身缓存(或回退 toml 种子) + 返回 —— 网关不直接接触 external worker 的缓存后端。无存活副本 / 旧版运行时 + 不支持 get_config 时,回退回直读 config/workers//config.toml 的 [workers].config。 + 路由配置(add.toml 等)不在此范围。 + + 响应中的 source/mode 供前端如实标注展示值的来路(避免把回退读到的 toml 值当成缓存值): + - source="cache":值来自运行时缓存;source="toml":缓存缺失或副本未响应,值取自文件; + - mode:该类插件的配置优先来源模式(toml 模式以文件为准、cache 模式以缓存为准)。 + + - worker_type: 插件类型(如 example) + """ + manager = _get_manager(request) + try: + info = await manager.get_worker_config(worker_type) + cfg = info.get("config") + if isinstance(cfg, dict) and info.get("status") in ("ok", None): + return { + "worker_type": worker_type, + "worker_id": info.get("worker_id"), + "source": info.get("source", "cache"), + "mode": info.get("mode") or manager.config_mode, + "config": cfg, + "success": True, + } + logger.debug("worker get_config 未返回有效配置(%s),回退读 toml", info) + except Exception as e: # noqa: BLE001 - 副本未响应等一律回退读 toml + logger.debug("worker get_config 查询失败,回退读 toml: %s", e) + + path = pathlib.Path("config/workers") / worker_type / "config.toml" + if not path.exists(): + raise HTTPException(status_code=404, detail=f"插件 {worker_type} 的配置文件不存在: {path}") + with open(path, "rb") as f: + data = tomllib.load(f) + workers = data.get("workers") or {} + cfg = workers.get("config") + return { + "worker_type": worker_type, + "config": cfg if isinstance(cfg, dict) else {}, + "source": "toml", + "mode": manager.config_mode, + "success": True, + } + + +@router.post("/admin/workers/{worker_type}/config", summary="更新插件运行时配置(下发至 worker)") +async def update_worker_config( + request: Request, + worker_type: str, + body: ConfigUpdateBody, + _admin: dict = Depends(require_admin), +): + """更新某插件的运行时配置 `[workers.config]`(路由配置 add.toml 等除外)。 + + 流程:网关经 Zenoh 下发 config_set 给该类型全部副本 → 由 worker 触发写回各自 + 配置文件(仅 index0 落盘避免同文件并发写)→ 写成功则以新配置重启所有对应副本; + 任一失败则返回失败明细(error 信息)。 + + - worker_type: 插件类型(如 example) + - body.updates: 待更新的键值,如 {"data_dir": "./data2"} + """ + manager = _get_manager(request) + if not body.updates: + raise HTTPException(status_code=422, detail="updates 不能为空") + try: + result = await manager.update_worker_config(worker_type, body.updates) + except Exception as e: # noqa: BLE001 - 无副本等运行时错误映射为 4xx + logger.warning( + "更新插件配置失败: %s 用户=%s 错误=%s", + worker_type, + _admin.get("username", _admin.get("sub", "-")), + e, + ) + raise HTTPException(status_code=404, detail=str(e)) + logger.info( + "更新插件配置: %s\n updates=%s\n 结果=成功 %d/%d (失败: %s) 用户=%s", + worker_type, + body.updates, + result["total"] - result["failed"], result["total"], + result["errors"], + _admin.get("username", _admin.get("sub", "-")), + ) + return result \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..b133d38 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1838 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] + +[[package]] +name = "aiosqlite" +version = "0.22.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, +] + +[[package]] +name = "amqtt" +version = "0.12.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "dacite" }, + { name = "psutil" }, + { name = "pwdlib", extra = ["argon2"] }, + { name = "pyyaml" }, + { name = "transitions" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/bf/f08b1a1f3efec308d6883253bb3c2b689309535931e16040b1516f0cebdf/amqtt-0.12.1.tar.gz", hash = "sha256:7112cce32f9b9d3f24abb1f60c604695909cd6a31f89e79e430585326ebe97d8", size = 93594, upload-time = "2026-09-16T17:22:17.732Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/9e/624469a7b7df2c07701b5e977b9bd84b2c8f04789ed9e5250f8ca6a3414d/amqtt-0.12.1-py3-none-any.whl", hash = "sha256:dfaf9d5989a31f5bae8506d9c91451d677e874efc1c89ca2e76ca650cd9b5cbb", size = 129862, upload-time = "2026-09-16T17:22:16.266Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "26.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/43/bb8b6e8708d49a5ab36781333af092d9f483b198a2710d01281204640055/argon2_cffi_bindings-26.1.0.tar.gz", hash = "sha256:63505c71542a44b68b1e38060450fb006404170da375feb31af153e7f9c6205d", size = 1790807, upload-time = "2026-08-20T07:44:22.492Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/d2/0ae991f1b2181e5be49007c574710a800ad36c2978683addb3e67c474e55/argon2_cffi_bindings-26.1.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:21ca0396fe5ec995dd54431c32698189666f9224810acfa752e50d2bd94d9df2", size = 25521, upload-time = "2026-08-20T07:32:43.019Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/e4/ad91d8297638aa2258aad4501c306aca99480dfe76ccd638173fa3702db9/argon2_cffi_bindings-26.1.0-cp310-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78de2d65e0b9ea7ce9d1b1c3e87297b2d7305a02c266ee2a2d6910daddd7ee69", size = 27177, upload-time = "2026-08-20T07:32:44.158Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/86/5363df11b86d02cf3662208e7406496327649cc90eb365bf6f4e8a54a41f/argon2_cffi_bindings-26.1.0-cp310-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27f1821903e2ceadcb88ec2b45ef190897b7682449c772f4d9b53e42c520cf29", size = 26597, upload-time = "2026-08-20T07:32:45.172Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/b5/a14dcc592652347dad23ee93b278a4da5d2a25c9ed3ebd10d68eea823a4f/argon2_cffi_bindings-26.1.0-cp310-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d88e5f7e60f28ae0b0cc6b2f16c43e87cd642a196a86f85e0d8bb6fe016fc16d", size = 27403, upload-time = "2026-08-20T07:32:46.13Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/81/b4a20d4902af7f796390bf9245ff83c5217dfa7367efa1d14986956c482b/argon2_cffi_bindings-26.1.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:34b7d9c24a4165a2c61cc8ae11d44d48c9ce2830fb536cb7914e11fdd9962728", size = 27132, upload-time = "2026-08-20T07:32:47.13Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/1b/c8de358af07b1c490e0fcb863ef98e46ddb486e45567aca5a60bd68d9daa/argon2_cffi_bindings-26.1.0-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:224865cbbcb7a2bd1356741dff12b0134df726b6d44bb7b500df8e303cbd9e81", size = 27588, upload-time = "2026-08-20T07:32:48.087Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/2f/7ee62a6e79f9309f9d9982d301b22a00010adb580c05c8109b94d7b33de0/argon2_cffi_bindings-26.1.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ffff613aaa9ce6236766e2fc6dc560bb5abde7a2e2416e3db1f9ae395a2b4dd4", size = 26785, upload-time = "2026-08-20T07:32:48.977Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/10/960d0ee93d4897741bcaf4799c697dae2d81499f66fd1ed042a7dd54c1f4/argon2_cffi_bindings-26.1.0-cp310-abi3-win32.whl", hash = "sha256:a86c069c91a747a2c4e5c51473590aeb48172fff9b2130d23729a42d98665ecb", size = 23898, upload-time = "2026-08-20T07:32:50.114Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/3a/0cc14a05810e6add9bce5e87693334baa2222de5f647fa31781885b6573f/argon2_cffi_bindings-26.1.0-cp310-abi3-win_amd64.whl", hash = "sha256:2c36ff87b5dfaa477d0bd51e9d7f6abdae7c8955d2983c97419085d842154b3e", size = 25730, upload-time = "2026-08-20T07:32:51.091Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/db/d83cf2af140547f0b9cdaece05b2dc2dcbf991be4667331d073eff771435/argon2_cffi_bindings-26.1.0-cp310-abi3-win_arm64.whl", hash = "sha256:f9c4420a7a864fe1b86ce35befc95b8e39fb852493b81cf798671ddc265de638", size = 24478, upload-time = "2026-08-20T07:32:52.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/5f/f652055e18d2627e2eed94c7f31a792127cfe38df786635395d742321674/argon2_cffi_bindings-26.1.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:af11ac37a7c53dc16cb7950a6190851b0870fe218b6c60c0bb7ac355234e3083", size = 15434, upload-time = "2026-08-20T07:32:53.143Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/38/de696045960f5b846d428c0fb6c130ed3da87aac2af209b05c193815404c/argon2_cffi_bindings-26.1.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:db0fcd827ca61622a01b220aadfbece01939acf53888f2cb98cd93e9b1e2c97e", size = 15449, upload-time = "2026-08-20T07:32:54.075Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/0a/c25af768f6b75a5a71e31207f87c540656b2808c015260444a22763221ad/argon2_cffi_bindings-26.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:28524438cd3e723f25412f63d4fd516ff5bae9ae5aa56acbe2a1404398a0cf31", size = 25683, upload-time = "2026-08-20T07:32:55.05Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/7e/be212c751ab0bcea7f646615f933bf262e8e50b3f7bef32f861d0a2d066b/argon2_cffi_bindings-26.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac82fc756a446b6ccd7139ce70efa9d8bbe541e7ad579a12dcb52764b7175c5f", size = 27311, upload-time = "2026-08-20T07:32:56.166Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/ee/f84b28e4afd13d3cac36c1d8fa8c239d2dc2c51cd978d02ee5d5ad98d9bb/argon2_cffi_bindings-26.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a4e68eed961a8de6928d1c17ff3dc2a547e0e923c17f8f1cd79fb7bc9502f98", size = 26771, upload-time = "2026-08-20T07:32:57.206Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/c3/95c07a023691ecd529da9cb6a8f0779e13ebc1bdfaa86d145fdc1c6e7e79/argon2_cffi_bindings-26.1.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:151dfaad9de753f4af2a7854e707e4784f2acc434340ade64239c5b104b2d605", size = 27568, upload-time = "2026-08-20T07:32:58.361Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/31/3a18e31406d8694b4d6a31573c3e572fff6bed318bb744453eb653766d22/argon2_cffi_bindings-26.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:061a6919145bbf282ebf1f9c59d3135d4833c25313c8595c0d68cf7712ddfce2", size = 27280, upload-time = "2026-08-20T07:32:59.343Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/39/d4be4577e178b2397aa5b5575c8a309bf0da2afe05fe0c72c8f398662d63/argon2_cffi_bindings-26.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:62ff20cd130c956c7c9144d5fe35228f98b51c579b2439e988b27ef93e16c02a", size = 27776, upload-time = "2026-08-20T07:33:00.325Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/47/78f4dd96f7411339f723b96fe24039c1bd5835102b8a5ba71ac4ec712ac7/argon2_cffi_bindings-26.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:19423e5d7ac1cc354baab59eaabf18db2ec04ef6593b5abe5a34f323c4a8f87a", size = 26932, upload-time = "2026-08-20T07:33:01.272Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/cd/96bfd37434cc0a848a9066c291d84b28846c4c9ea289ed9866b1164d622b/argon2_cffi_bindings-26.1.0-cp314-cp314t-win32.whl", hash = "sha256:4f84cdd868978d7b7350a566c254042d44216d9e37f241f3a6d3b1dfebeede35", size = 24878, upload-time = "2026-08-20T07:33:02.189Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/42/d8b6810abd9b1bd2f47ebbccf460da59c9f32e94888bea4f7b137d998797/argon2_cffi_bindings-26.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2b741888c93147444fdfc851abd81cc207f37f7f7da42062a00deb3888e57da8", size = 26656, upload-time = "2026-08-20T07:33:03.222Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/d1/095d95eaf2ed1d9f77268cf3291bde148c6cd56121f8db2c74c1ba618a0e/argon2_cffi_bindings-26.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6ab674f668d5962a3a4136ae0812519b0f1586874263723a32181d60d64137e1", size = 25378, upload-time = "2026-08-20T07:33:04.332Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/cb/214092c39c4dbcb72cf98b12234ddac2221f8fe2c0acf29c6a70fa83be53/argon2_cffi_bindings-26.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1d98e33bd8bd67d7206c124e200bf2229c4cfa8c9c19f7b44a897f0fc71837eb", size = 25683, upload-time = "2026-08-20T07:33:05.337Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/e5/02015b83e9b05ccb85ff2ced424cf6e83a12d3810bc7f66d679a92b69ffb/argon2_cffi_bindings-26.1.0-cp315-cp315t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccaf0a46cbb380f1fd102a874e32aa629fd3cb0c0e94f4943fa1f6d5edc5dac6", size = 27310, upload-time = "2026-08-20T07:33:06.344Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/4a/85e612787d0796878b3b4f6bd53dcd5484b6fe7b64cc6fc7b6e6a04cf835/argon2_cffi_bindings-26.1.0-cp315-cp315t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0c3103fcff20183e593459cfea6e012281c0e76ae3ed8b5565ad1b92eac3990", size = 26771, upload-time = "2026-08-20T07:33:07.429Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/84/ccb003b6f9969820e87656398f4d49c857def71a85ca1588a0e809afd7ce/argon2_cffi_bindings-26.1.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c49e853a3bef9dd10329f31f702e7fa9b5c58229ff9c2ff6d069efaf09177c08", size = 27569, upload-time = "2026-08-20T07:33:08.598Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/07/c26b76debf0998ee08fbe947ab2058ac5de37d4b9d46b06c17abaa6c4ce9/argon2_cffi_bindings-26.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:6376d4b3aca039375ca8bf92f770da0ec424a1ce3a37077a8d3c557411aa56ca", size = 27279, upload-time = "2026-08-20T07:33:09.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/0d/ead6ddc029f91bc9b9390686dad3c808ab08100d348f6266b5f93f8970ee/argon2_cffi_bindings-26.1.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:9bacedc04b0402837586a17f0919e3dfdd95291f441f1f56bd80ec274c2840a1", size = 27774, upload-time = "2026-08-20T07:33:10.728Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/47/c108530d9eb86036b78d3af4de28b83b4a2d9a70512bd10ff8e59966aab4/argon2_cffi_bindings-26.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:76ae29acace5d33355344612844d588e19deaaba4639d8bb01601e4b1418ef36", size = 26933, upload-time = "2026-08-20T07:33:11.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/02/0bfc59e781c89acf64c31c388aade9d9d1c1ea38aa1ba1292fe07f607fe9/argon2_cffi_bindings-26.1.0-cp315-cp315t-win32.whl", hash = "sha256:df612391feca41c44d20118f3b88d1b86419465cd1f5496859f715ca60ec2210", size = 24875, upload-time = "2026-08-20T07:33:12.616Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/c7/c3e46068cddffccecb8ad94d71135e9bf62bbc789589e7dfadc7c6f59214/argon2_cffi_bindings-26.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:1a0a29ed86960e44eaace7e081bdfab4f08b012fd96ec8edba71e2ad020939e4", size = 26655, upload-time = "2026-08-20T07:33:13.521Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/ca/18b9c8c45fecf34b9100ec6d7946057f14a158f2eaa20ea123a3e82351cb/argon2_cffi_bindings-26.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d157ddfab1e8b21f2f1dedda9c09645d98b5ed0b667b0626be600a345d426440", size = 25376, upload-time = "2026-08-20T07:33:14.491Z" }, +] + +[[package]] +name = "asyncpg" +version = "0.32.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/4e/59dc964f962f09e3ed472e5d2d3ba670a41a2be25080dc62ab3db507ff5e/asyncpg-0.32.0.tar.gz", hash = "sha256:45e64e56714d888330b884aad1dfb363d0bf43fb343e3d1a8968525f3bade478", size = 1075156, upload-time = "2026-10-06T20:32:40.251Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/06/d5f956db9c936c90cd3289cf948a86c3efc9849e26354356c23da29f6a2d/asyncpg-0.32.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cb31f7a8472ddc6b6f5c9da1290e901d5c77c8441c7213bd13b13ef6fe6359c", size = 681566, upload-time = "2026-10-06T20:30:52.779Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/93/ea55f3b26fd40ec90e5b6d6c53b9ff52633cf6b87a468d9c033a727832f4/asyncpg-0.32.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:643d8d6e955a355045dddfe827d74f4f0d1dc4a18e06963a08260af838fbf093", size = 704359, upload-time = "2026-10-06T20:30:54.608Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/2c/a3704e8675d37b168f3584661fc9f64f3021659c9b94e51cf9ab957b2bc5/asyncpg-0.32.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:14ff79ca2574182ce258159c48978a086f9026fc121d935017b5d10c64fa3c72", size = 3707008, upload-time = "2026-10-06T20:30:56.326Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/30/4fd8d1155b3d7a32a2c241dcb9c5d9e9bd74a59ae71ed25ef8ddb8e038e1/asyncpg-0.32.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:54851411bee2aa51a30d0911524201fbb05f82cc0f7c248b140203db637c723d", size = 3810163, upload-time = "2026-10-06T20:30:58.114Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/25/5b0992d45661e1488aba775cf17a2e6c82c7d1d7e10acc71efd394760a00/asyncpg-0.32.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8592f0ed9c315b2117dbdc707cf3292f09a89d5b07661016a84dd881326965cf", size = 3600446, upload-time = "2026-10-06T20:30:59.946Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/88/1c82c6feacec813423401b5aef1a43baea951694157f4d405b2d14e80e6d/asyncpg-0.32.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4dbe0982cb3ded878de0867dfaeae3116faf471d484ea28b3e3da942f01fb778", size = 3764563, upload-time = "2026-10-06T20:31:01.462Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/f5/5a3796088f0c3f7d22aaf7c48536f40b27e44b7c9603d4d7abfeca2ed97e/asyncpg-0.32.0-cp312-cp312-win32.whl", hash = "sha256:fbe1f8c788fb5df18ea8a5432dfa2473fd8f7f088025fb83d089a7c7b37e37b0", size = 551810, upload-time = "2026-10-06T20:31:03.248Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/42/f4d333a3f67b0e7cf58ea855f9d5d9104ce38c21f2a2f22bf7dce524428c/asyncpg-0.32.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd7157a86817730c3239bc687abf8186a471525d695e225c187b9a523a808a98", size = 626763, upload-time = "2026-10-06T20:31:04.927Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/82/9d82e16e1d0b4e2a639a2db649d4b444b8a479cd52553a9c36ba0d6320a8/asyncpg-0.32.0-cp312-cp312-win_arm64.whl", hash = "sha256:9509e21fc526f1fc27cf80ad9f9b8dde3f3e21935d46be66d649635321d3407c", size = 577288, upload-time = "2026-10-06T20:31:06.776Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/ee/b6b5870b51e004880d9a216313ea7d4f180961c5869f32e58e8cb9b71e96/asyncpg-0.32.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c032869fd9c3c9fd1a86ad67e53f63906159068087c2674dd1e19be3cffff571", size = 683362, upload-time = "2026-10-06T20:31:08.078Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/8b/1f450742bc6eab0c015cae26aef94fac2ff29433e3f18a019126c3912c49/asyncpg-0.32.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:0c764dce865b41878396e736d4d2c6c6ce3a8e1b61d1f6bb292e30d265ae7ca6", size = 706652, upload-time = "2026-10-06T20:31:09.524Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/dc/13f3c0ef7e867bafdccd470e5cfae1f2fd9a7085c771546bd4b94018e043/asyncpg-0.32.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:925ce1cc54419d468bfb77632d91e5e2be5be0fdf9d43680c68fe7cedf87051a", size = 3698244, upload-time = "2026-10-06T20:31:10.894Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/64/b00ef3fc0d861c28a1937f08d2c7f6e6119c152b414d50fa800c3aee83b5/asyncpg-0.32.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cec40b66a36b14921c155db78631cd96ed00e225fdf38dd5532e9aef350a498", size = 3801314, upload-time = "2026-10-06T20:31:12.964Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/1b/215067d97a13206ce1565da920ddbefe5a1e5f89903e6de862fdd0a034a1/asyncpg-0.32.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fba43a9a230ce4d2b4593b761b8e03630c613c282b24566e27c7f53695273b1", size = 3598650, upload-time = "2026-10-06T20:31:14.797Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/45/2bfcb5c9b04df3f17fd367647c9f3ee9fe64ea0612b509a6b1832afcedae/asyncpg-0.32.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a8f7fa8304f757e23cccb8ffef6a6fce0b6320ffc565a884ee3cd0dfad1ac5", size = 3762739, upload-time = "2026-10-06T20:31:17.186Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/45/e6b37756e6c8979fe070e9821654244f38319493f5b0589e549d9a40c001/asyncpg-0.32.0-cp313-cp313-win32.whl", hash = "sha256:d809399022e244eb86bb532a4ae9a45746e0f6dc5154fd6aa2f6ad63fa3f5373", size = 551065, upload-time = "2026-10-06T20:31:18.812Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/46/0a4e92f4310da644b28595b22ef2fff1ffd3dab84953dc8b4c5eef72b764/asyncpg-0.32.0-cp313-cp313-win_amd64.whl", hash = "sha256:38640b106705fef8b0f46cdb5fd9dcf6a638eed5cadb0f441714a21405ca8a0a", size = 625571, upload-time = "2026-10-06T20:31:20.571Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/f4/48ed4b580b99b1fabc480c707229bb8f1e4ba0f5b24a50822b339efe1e48/asyncpg-0.32.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78145adedfe51dc2fda623e6602cf816dabc2eafcff693bd50484321a1c9034", size = 576342, upload-time = "2026-10-06T20:31:22.29Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/25/a30ca6417f9142c6a63a7caf5f33717902b2d0ca8a8ff8fc72c6cc2fa77d/asyncpg-0.32.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ac18d9ee7a8ca70aed276f79b249d9f37e4d55e3525db1002b5f0b62ddec4f5", size = 691699, upload-time = "2026-10-06T20:31:24.168Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/b5/59f10f2381a073c199cd868fce0d8f7aa448b08412de4dc4dbe4118bcee9/asyncpg-0.32.0-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:e1120ef2ae3a5e514c9ea9fce83519ba692710ea5f38434eadbbf12789073dfe", size = 715194, upload-time = "2026-10-06T20:31:25.969Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/59/79a5aebd58250bedefa6dcd43b22b037d9cf0054ceb4c718c53ebf04e63f/asyncpg-0.32.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fa68acb42f22436597016e5d7feef7b0b5c49b4c56aece3fdb3ba0da2326cb2", size = 3729978, upload-time = "2026-10-06T20:31:27.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/db/fc91b503b3ec66cf242d83c799388285ea5f0ee238435d53dd9c1a8648a9/asyncpg-0.32.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63417b8f7369c54f6754c1fbd5a2968fbe632ff55bfbedd56a0177b6a96bd251", size = 3794539, upload-time = "2026-10-06T20:31:29.617Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/bd/7359320499fdb2733206191b8fd15b7ec602656cbc1444bff7a8c66a365c/asyncpg-0.32.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c6366841a792d0a4d16991de240a8053b7c4772a18a5f27fa6fad09c0e359fb", size = 3632884, upload-time = "2026-10-06T20:31:31.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/75/dd3c3dd99f1db55b9736d23a44da29501f07f852bf4df91507f37b156fb1/asyncpg-0.32.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3ef1dfd11919280e011ffd1c873323c5088a94fd2c3f77946a5250cf306e2eb", size = 3764931, upload-time = "2026-10-06T20:31:32.916Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/4f/161b275759725a774d170a383c1208996865ebad50d6891e60d35461a3e6/asyncpg-0.32.0-cp314-cp314-win32.whl", hash = "sha256:77cf9d7023f063ae6f9e443077b55af0dc1807dd9afff1ae656b93ee0cddedc9", size = 557690, upload-time = "2026-10-06T20:31:34.856Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/03/880d0db1faedf8b740a57a7ba50e115651a0f05c5905140195813879b086/asyncpg-0.32.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f87452025b47ce80dcc3a0be2b5d1f8aab5deec2516d266f1643d4e53cc40d5", size = 634859, upload-time = "2026-10-06T20:31:36.512Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/bb/2e86b462a2a2a795eaa7838266db019876b8e7a12c465b903517a4e87fd0/asyncpg-0.32.0-cp314-cp314-win_arm64.whl", hash = "sha256:d0e4508a3d62b0f42d7a99c030c364050b11e75f61c9dd4861e5fdda7cb60636", size = 594013, upload-time = "2026-10-06T20:31:37.91Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/1d/5369c4438496e654121cbda75be2e8043d1fcae3552b856d44011a19b723/asyncpg-0.32.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:afec11e0b9c001e69966becacd2f948cc8949b4916ec4c0f4dc9b52e47de4528", size = 743832, upload-time = "2026-10-06T20:31:39.261Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/b0/4b92582c2339a164275a6418ccaeeb0453b72f2e0d7003702379cb50e852/asyncpg-0.32.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:418d266a553e932bf961bb43bfd610ee6c5425fb1b9a599a5828fd12bae8f5c4", size = 769568, upload-time = "2026-10-06T20:31:40.691Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/88/919d9ff7ca3c3b96aa404b88b6a53e142b4422623c5ee5a69c4b733240ce/asyncpg-0.32.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b1666e1b747ebbc75c87cb31972704ae8a3ca15b950f94456e97d26781c67d10", size = 3948962, upload-time = "2026-10-06T20:31:42.456Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/8b/e9f412ae9a3e3f0eb23415249e8d5933e7aeb01068b4083fc86714043d1f/asyncpg-0.32.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83510bb25d38f0415e155aa3a7af78621369891f5ecd8730d012d9cb26143ffc", size = 3874815, upload-time = "2026-10-06T20:31:44.094Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/71/24364e9ff7bb9860548452513f295306b12f5b24e8fb0b78f1605c443946/asyncpg-0.32.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87957755d11639cf248c6aaa094eee9d150f07065866d1710c9427e02dfc0790", size = 3762465, upload-time = "2026-10-06T20:31:45.908Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/e1/33cb7e805ec6806b196473e2c7a2ba9d5af3ad2928930aa06359c8eeef87/asyncpg-0.32.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:764227423bf30a3001d3da6df90e82d30a2a097d762e4ee5fa074236eda262f4", size = 3797285, upload-time = "2026-10-06T20:31:47.53Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/e7/85eb86d6040725f5c191fd6af9f10769c60ed971634b47f4b4bcab293d44/asyncpg-0.32.0-cp314-cp314t-win32.whl", hash = "sha256:f2342b1f3e87b2096320a77edcbb830fbd23b1d4d4842c57567764430b95e4fc", size = 594006, upload-time = "2026-10-06T20:31:49.197Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/aa/ea75defe55718457bcf41cde42248db5bbee65fce8c6f0a0e43d9eca1723/asyncpg-0.32.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5c3a48908cb0a02393e5bdab7fa92aefd700f2a93212bf91f04aa9657b4f554d", size = 674647, upload-time = "2026-10-06T20:31:50.547Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/0b/078d362872c6c72dd5d11c214dde8dac65b1c87ece96fd2fc2f786a8f66c/asyncpg-0.32.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f8eadd207c26850a2e15f3c2a1096b5d051ea6758a26f2f3e65ce16f84297ed8", size = 624589, upload-time = "2026-10-06T20:31:52.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/83/e0145d19197b965438693179c88dd99cfc69bc1bf954815f44762ab88843/asyncpg-0.32.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:58975b1a51a100c4716ebf22f84c249d27140f7b9385b64ad9b676836f1db9ab", size = 689708, upload-time = "2026-10-06T20:31:55.809Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/13/f394919a59f104288b1b17fb6c7a3ac4738b8c555690a63caf603f91ca83/asyncpg-0.32.0-cp315-cp315-macosx_11_0_x86_64.whl", hash = "sha256:6b95fc2ebdb4af072bfa8b64c6d0397b49242d17bef1c0337857904f9267dab2", size = 714408, upload-time = "2026-10-06T20:31:57.504Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/3d/1123cf41bff78fdfd80e6fd143cc86bf1ef2875af8f5d8742c03f471e913/asyncpg-0.32.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a759f98c5652443db501b20041aeee548e9a04fe7ae939067321acd207218447", size = 3733440, upload-time = "2026-10-06T20:31:59.308Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/24/ff4b045e85d7bdf6f61f67c285800abd6e82f26319671d7f0dfadadc1aa0/asyncpg-0.32.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ceea1064500d0d7a46c092cdbe9752064c23b720ab0e0bff83d1030fffe7a50a", size = 3824312, upload-time = "2026-10-06T20:32:01.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/63/1ec7eb6e20f7e8ae120a41aad9669044cce964f39773baf644897a046aee/asyncpg-0.32.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:543f02790d086244c7cdc849e4b671b6c2048be0242b78d943494da6e80c0001", size = 3637212, upload-time = "2026-10-06T20:32:02.699Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/68/528e362eb5adbc1a7defe4c5f157756a031346d3efa9920467b245e4ce41/asyncpg-0.32.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f24d20a68f0e37ca6fc490388e7eeb48abab3da0dbf06248135ed6179f5f521d", size = 3791355, upload-time = "2026-10-06T20:32:04.415Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/e3/22f443f456bf93d1806f43a820da8ee463dfe9b93a9d77a3f00fedcdaad6/asyncpg-0.32.0-cp315-cp315-win32.whl", hash = "sha256:110f72d33c8b944ab421ca383db0b8849cfeb861547fee6cbb61f65a6bcd0985", size = 557457, upload-time = "2026-10-06T20:32:06.52Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/d5/ccb76555a333f543c4d6ad6422b616efc0811dbbde5054fda071e249c7bf/asyncpg-0.32.0-cp315-cp315-win_amd64.whl", hash = "sha256:6d1d1cd1348ebb9b204b5f56f977c5d4380674c25cc094064bf32bd9c3b7273d", size = 635573, upload-time = "2026-10-06T20:32:08.197Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/70/dff17e837ba0eb4347bb33da33f54df87230d3d176793d4bb2ad7786b1b8/asyncpg-0.32.0-cp315-cp315-win_arm64.whl", hash = "sha256:cd5d16b3a5db37c1e6e445e362952b4af569f85f94e162f947bfa8ea25a45fa5", size = 594218, upload-time = "2026-10-06T20:32:09.717Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/b8/c5506dbde0cfb213963210fd0c80e60036ddaaa883ac0d3c55d05a10ebe8/asyncpg-0.32.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4ea1a72a00fe705b68a9727c3d538c4c56690af9bb1cbbf3c089f5d3ddcccea0", size = 741693, upload-time = "2026-10-06T20:32:11.168Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/98/9f998c651aa5d66b59ab6c13da71a15d74ccb1ddc4d65290ea5e2e5aedc1/asyncpg-0.32.0-cp315-cp315t-macosx_11_0_x86_64.whl", hash = "sha256:ed3ae4c3659aea1fb0e3a6c1061fc4c64d9b7a2a8f4a27443dc43d74fa84cf03", size = 768101, upload-time = "2026-10-06T20:32:12.948Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/ce/d8c63a71e908f5d80de1a3a057c8407aaea07cf19980d4b24ab624943c99/asyncpg-0.32.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db69b9cf879bddeea41210c80b8c8877bfe2709e2bee9d18d5a5c00e7eb75972", size = 3940715, upload-time = "2026-10-06T20:32:14.544Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/a5/5d2b17682e297e39206eda1dfe0120fc239e84d3440b39ff7c9cc7ec83db/asyncpg-0.32.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bee7bb5394bf55fc3bf4144625c33f298949961acdb1e0d67e60f958ac9a2e6", size = 3907504, upload-time = "2026-10-06T20:32:16.212Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/80/38ec7277f31f26267a0a0547d0997d936850d05007d1e0e1041bf8070e1d/asyncpg-0.32.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:d74eabd68e68861333e3fcb92b520a2a851f6485abf4b723887590399d4980c1", size = 3750324, upload-time = "2026-10-06T20:32:18.061Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/74/089e80eda7d543a49875687a84121e2ad61a7c69698963623ee77372c4e9/asyncpg-0.32.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6af2af292a93d5ef800007c8f8f66b85af2a49b49e4b56a10685a0dc24a6af83", size = 3826457, upload-time = "2026-10-06T20:32:19.757Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/3c/38104e60cda6131977f95b634d45536ddc1cde53ef8bc765f9056e3e17ee/asyncpg-0.32.0-cp315-cp315t-win32.whl", hash = "sha256:d148cb6a9081ed999ca3cd0d95fb9eaf79bf17d885bba93c83de52273d2fe0af", size = 592437, upload-time = "2026-10-06T20:32:21.668Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/09/85cba249db0910708826ea428b32a4a05630df993621c369bdb8d42c73c5/asyncpg-0.32.0-cp315-cp315t-win_amd64.whl", hash = "sha256:e101801b4124e905da0732cf2b0d838f682a9ea5273d7cced3d54bdbe744e6f7", size = 672417, upload-time = "2026-10-06T20:32:23.147Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/11/ec5f7f306dd361aa9558f002cbb6acfa1e9ba32fa59b8f53135fbdfa14f1/asyncpg-0.32.0-cp315-cp315t-win_arm64.whl", hash = "sha256:3bbf08c08e31f43be858255614518e78cdfb343571e557e818e9fe736334f4c8", size = 622767, upload-time = "2026-10-06T20:32:24.64Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/1c/f41d4e74c28ab327ff3acd36053f7ea506c55872d7a90b0fa71aa3ab0c89/charset_normalizer-3.5.2.tar.gz", hash = "sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef", size = 172659, upload-time = "2026-09-30T04:39:23.398Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/c8/693809898870237d82785a03f3b2b58fe4c9f14669f84a7d4e623c92a59e/charset_normalizer-3.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491", size = 367780, upload-time = "2026-09-30T04:35:30.888Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/87/2fea8c13dc24b3ca9c6f803a5b2dfdeae73eb4f9e12c7885ed908ff0433c/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c", size = 246730, upload-time = "2026-09-30T04:35:32.286Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/9e/09efac30b937722f46d3110ba30b875b24b2e3a266ed746cc4e376a94d80/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0", size = 237707, upload-time = "2026-09-30T04:35:33.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/18/70d76670b13686237863a379928d60bd10e021f17d243ab3d7014c4a5f4e/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51", size = 273050, upload-time = "2026-09-30T04:35:35.138Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/e2/77a8b09d5adc013ed07b95b01b8b8fa5441c4e810e83ee7e4aae2fa4d91a/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5", size = 270345, upload-time = "2026-09-30T04:35:36.502Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/c5/38806a25ab5e65fc178f39affeda20858efafede2fce1ffc2556cfc9fe73/charset_normalizer-3.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649", size = 257601, upload-time = "2026-09-30T04:35:37.919Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/8d/213565184708fdb263ae55e2c04ee1ff748129dd65d48ed0e3502da9c85a/charset_normalizer-3.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e", size = 252222, upload-time = "2026-09-30T04:35:39.544Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/24/76d2cefc25472531e4c5c7dfff68865eb1c39b78482f0fdc15b46f047830/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346", size = 248482, upload-time = "2026-09-30T04:35:41.088Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/dc/65a801b66ab4c197e22c433ab25e7ac24324ac6f45a2269aca42cce309bf/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1", size = 241206, upload-time = "2026-09-30T04:35:42.59Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/95/ca9b5eabde673002c6f1e7ada1b223916fe18f6d661da7aabd4d643718f1/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875", size = 273190, upload-time = "2026-09-30T04:35:44.347Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/8b/803b4d2a3f6e1740f63f1e87b04d14b42f3d4fdfe6ed7d4db2d34102b14f/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1", size = 253527, upload-time = "2026-09-30T04:35:45.915Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/55/93c0e5dbd085ae0471346026abbe7e0db9ea2d6fea74e51f0b5a46f233a7/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413", size = 271285, upload-time = "2026-09-30T04:35:47.49Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/69/0dbd0e0b9b16cfa816cdfcb3e2e3854a1f680dc07fb1245ea125e7448060/charset_normalizer-3.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869", size = 260010, upload-time = "2026-09-30T04:35:48.996Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/9d/e7b88e7b1bf403590c3b573277b5e1e488c68c7a6fbacca310a2c324e90c/charset_normalizer-3.5.2-cp312-cp312-win32.whl", hash = "sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e", size = 184125, upload-time = "2026-09-30T04:35:50.777Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/e6/e6e083884cbcfd49c64865af05027fe7011be7b2d9179524f099a1b611f3/charset_normalizer-3.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc", size = 207486, upload-time = "2026-09-30T04:35:52.194Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/e3/017aea0911ada7405a825c7d937eb3a13009664e2f5b38e8c4bbf2abf894/charset_normalizer-3.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3", size = 196734, upload-time = "2026-09-30T04:35:53.636Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/34/68292d68512768591aaff07c59bb53ee31341c87759433a859c4641a50c2/charset_normalizer-3.5.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5", size = 225946, upload-time = "2026-09-30T04:35:55.313Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/80/bee0b01b90ccd5322ae1d0abb33fab1bd95b7c2eadaf02aeccf22e04ee83/charset_normalizer-3.5.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e", size = 238619, upload-time = "2026-09-30T04:35:56.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/6e/60ce52a85a7fd631ae8482ae6d74521014ca2f255892679484dc04d7ef56/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a", size = 206701, upload-time = "2026-09-30T04:35:58.639Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/8c/71aafad23f971afc84c2b295bc0c560739ce1dac558aad9fec22e39f3639/charset_normalizer-3.5.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d", size = 210506, upload-time = "2026-09-30T04:36:00.147Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/da/3c5a7798c046df7d2d68ad653cf5b6c5a8bfee225055a843c6f2f42aac1a/charset_normalizer-3.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055", size = 366626, upload-time = "2026-09-30T04:36:01.77Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e1/16/710ac3de2ee354e2bd1a9c94efe45a2d27b5c6ad39b2d6a905be2c094b6c/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858", size = 244624, upload-time = "2026-09-30T04:36:03.389Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/39/45c7439f5b63d24f7d5b2a1d760f34af7628782d7144b4cc8ded45c2d4bc/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234", size = 235731, upload-time = "2026-09-30T04:36:04.987Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/34/38f3154785ce92e9f56eb226f4d35bdfae6b008480dd055f58837a89c810/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21", size = 270352, upload-time = "2026-09-30T04:36:06.412Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/f3/859f74e7babc977705026b30593b3be04049632a522fb7000f83c033d747/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718", size = 267393, upload-time = "2026-09-30T04:36:07.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/85/41d27f234b82e47c167a5f6c0f62501dc0c640585ff4aba79e08a390336a/charset_normalizer-3.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4", size = 254660, upload-time = "2026-09-30T04:36:09.248Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/ca/5d1a997587febe5b26d8daffe363b5c1a091cece19828eec6502fd09c5ef/charset_normalizer-3.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3", size = 249996, upload-time = "2026-09-30T04:36:10.73Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/1f/d1e78246f7ed60c8c8d606b4ac27f66ce49cc3e95f24893ccbeba9f77302/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c", size = 247028, upload-time = "2026-09-30T04:36:12.294Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/37/eba316edd4f0c4d3a5d945924c4eeeae59abac4056aa815d8a4268f863a2/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429", size = 240562, upload-time = "2026-09-30T04:36:13.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c8/8e/aaa037d40ca9ef045977f1a661048b1aa33f223adfce3452fe9be9f79d14/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f", size = 271699, upload-time = "2026-09-30T04:36:15.41Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/19/1c1c9f75974adf523b87f34b8a2adc5a435cd65916812bcbd0dfa45f9a29/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a", size = 252030, upload-time = "2026-09-30T04:36:16.839Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/90/0660ef18e18df0a4d2a1a0edff7dfbba42d4e50ef2425557a5bb7051f77b/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00", size = 268565, upload-time = "2026-09-30T04:36:18.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/ba/57adc269824e8658f1a0f97a9e514c247445a9632b3419b97e0ba37f16dc/charset_normalizer-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d", size = 257627, upload-time = "2026-09-30T04:36:19.938Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/85/33abd4315c052d3d4f54c92b1ee49bfbc0dc7115a981e462a793b6d2ab87/charset_normalizer-3.5.2-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3", size = 143407, upload-time = "2026-09-30T04:36:21.376Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/de/6435e18d1aaa5d910b896d551411c96af1f42a0c56c29afc2016c61ccc2e/charset_normalizer-3.5.2-cp313-cp313-win32.whl", hash = "sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd", size = 183207, upload-time = "2026-09-30T04:36:22.776Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/76/b8ec57f4e9ee3253541abf95e4a462c0175fe8032dcd070f1f2421240942/charset_normalizer-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639", size = 206489, upload-time = "2026-09-30T04:36:24.306Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/60/c647c6ae47480221e875ea5d743ff94946f7416e3c69415ab772928e8d32/charset_normalizer-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3", size = 195501, upload-time = "2026-09-30T04:36:25.846Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/ca/7aa91362a2f77ac8e9e28a9b902a74f7d0e11a851ef0d27a74308da8cd90/charset_normalizer-3.5.2-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187", size = 226010, upload-time = "2026-09-30T04:36:27.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/cf/ac8878d0322cf88a1aad4c7b147db32ca0bd806eb0060957b2e31486dbe6/charset_normalizer-3.5.2-cp314-cp314-android_24_x86_64.whl", hash = "sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad", size = 239266, upload-time = "2026-09-30T04:36:29.434Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/6d/9a08d7e0b29b7208e2c6c01dc56c8e0520e7c7beadbbfb024b58fd69c8a5/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf", size = 206783, upload-time = "2026-09-30T04:36:30.872Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/44/b0aa350280e6ff5a5492d17cf10460dd39d5ee848f872f7ba2df10607f60/charset_normalizer-3.5.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995", size = 210449, upload-time = "2026-09-30T04:36:32.625Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/8a/40db9aa9f5907bb0e6f8b6d64064bf8852fb33d4b813ff9414911df7647c/charset_normalizer-3.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424", size = 367815, upload-time = "2026-09-30T04:36:34.197Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/72/9c5e7707b57c8ddfa9ddf7b0b1d009d7fbab9e9e887d5b721060f37e307d/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13", size = 247095, upload-time = "2026-09-30T04:36:35.803Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/09/71e453691e927de4ddf792770cfaab3f49d494e222f66ea5e404bbd5e39c/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d", size = 233942, upload-time = "2026-09-30T04:36:37.407Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/86/85c84e4da8b27dd409577d9437926ff581c5f9d3c66038dc68c1a526de51/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4", size = 273762, upload-time = "2026-09-30T04:36:38.904Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/08/564955a4b5f2ccb410ab480bbe8c6a18063ff27f2d35458731c4a5335df9/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438", size = 269514, upload-time = "2026-09-30T04:36:40.469Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/24/bad3ac4271589df29cf5ce2f5ae490518a5739358052bd0d61209e6fea54/charset_normalizer-3.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a", size = 255056, upload-time = "2026-09-30T04:36:42.02Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/3e/350d89ad49916b86554d6f5f2d03ec1152148f87e5ff735106c6a03b1a36/charset_normalizer-3.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56", size = 251311, upload-time = "2026-09-30T04:36:43.577Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/5b/4970a2d154df502e133402906dd04e3ae7cada7b3011283c88d0479a2585/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd", size = 250190, upload-time = "2026-09-30T04:36:45.185Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/8c/f1a91bddc8fb47c2889e29ea7ea49a194eb0d9868675d786806519c00d76/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204", size = 237098, upload-time = "2026-09-30T04:36:46.689Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/0e/bb5dace3cc7e79068425386a6589c19b5a2ab5fefc2a46abea6919683332/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7", size = 274228, upload-time = "2026-09-30T04:36:48.31Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/79/b849ad523017ea9f5a45581bbebed91439e0cf42fd2860a6f64e358eb5a6/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd", size = 252401, upload-time = "2026-09-30T04:36:50.091Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/8c/75469d690cf47200bce8f6cad7655724fc23148e147abfc5ce78b5f65863/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc", size = 271846, upload-time = "2026-09-30T04:36:51.719Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/cd/6d52d3c7437cdcf2e310ce9f28f282e733d4ef60ed19105d1819c356255f/charset_normalizer-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874", size = 258151, upload-time = "2026-09-30T04:36:53.234Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/4c/070b38bdb5f49a70199fce923ec0726a49536a63ab262abbfcaaf351110b/charset_normalizer-3.5.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655", size = 143570, upload-time = "2026-09-30T04:36:54.816Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/84/9ebfc8ed6c8c4fcd8e726ff6bf220cc8deb3966e31dce9be8dd8aa017e64/charset_normalizer-3.5.2-cp314-cp314-win32.whl", hash = "sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0", size = 186412, upload-time = "2026-09-30T04:36:56.643Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/78/5ed86f743d4bc350db307e7636419a0a5ee1d91806d30c7f667bd5c80dae/charset_normalizer-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c", size = 210965, upload-time = "2026-09-30T04:36:58.205Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/94/a3a7698e9b1a395e1eb99ccd9a324be9347973bff4e72db2a06496d7cd27/charset_normalizer-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253", size = 200406, upload-time = "2026-09-30T04:36:59.764Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/48/c5dd00d5ef7791f02666de250a5bb6071e29b7e133cf4b835800b6d3bc27/charset_normalizer-3.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709", size = 396871, upload-time = "2026-09-30T04:37:01.543Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/c8/8379554b42e8368161d898476686947a0fdbd3e8865170d7909dcabfdee8/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084", size = 249711, upload-time = "2026-09-30T04:37:03.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/eb/2ddb1035d17320caa9f41682935123a9a250277b261c3efc86b2d2a21343/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb", size = 231146, upload-time = "2026-09-30T04:37:04.721Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/24/2ecb4bde104322cd7859d6594fcfa74649f8d90b3221c9feecbef149875b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f", size = 271552, upload-time = "2026-09-30T04:37:06.295Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/98/9d5f6ebc3aee9fef5d30b4aff11fb2ab7a1222b4064f8ef2c7c87cde217a/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09", size = 269810, upload-time = "2026-09-30T04:37:07.905Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/e1/a3b06a10461b1b7628853c934c644e03bc28e42767116afb52f19a56519b/charset_normalizer-3.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80", size = 259845, upload-time = "2026-09-30T04:37:09.554Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/d3/6f561f74a296cf27d61775a1dc665ad13f3bff6a798810ca05907f37a7c4/charset_normalizer-3.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c", size = 252825, upload-time = "2026-09-30T04:37:11.274Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/9f/69e13ca3b18f43e0eafcd34c04a45b732ae22a43b54a5fc9e119103356eb/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f", size = 252659, upload-time = "2026-09-30T04:37:12.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/a9/ace29806a0dae18939919c76ba526472d83214afa101105fabff2cf30625/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03", size = 238819, upload-time = "2026-09-30T04:37:14.659Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/c1/6116d52a2e3311ec80f21f5fb5e17b27405f10b9608af8f6e69516841a1b/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604", size = 272973, upload-time = "2026-09-30T04:37:16.346Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/aa/9955c7e93bba10a9c7e8f7a5031b7ced66f3a1883a55c00712b8d5850ff3/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8", size = 253692, upload-time = "2026-09-30T04:37:18.212Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/33/2a6ae7fdc1b10cb581cef91addd8cdfc5f40d50abb5702309369d5834579/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93", size = 268757, upload-time = "2026-09-30T04:37:19.877Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/22/80992720a0282cd39bba1db35868e6b9c22f41281160143a836544bc1d8a/charset_normalizer-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915", size = 262220, upload-time = "2026-09-30T04:37:21.583Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/9f/181fd07e1bffea1d95cd80c84ac537354f50699c22cfc4d3c02b6fc16208/charset_normalizer-3.5.2-cp314-cp314t-win32.whl", hash = "sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5", size = 197797, upload-time = "2026-09-30T04:37:23.235Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/1c/25d8415ec1c4f2f41f1680435e4c87cfb378ff2f677d950946f2a45d0632/charset_normalizer-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc", size = 222765, upload-time = "2026-09-30T04:37:24.891Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/b4/46b48f013dadfc0d0d33b375438e31bdf5a989dc68389c6bf627054d4df9/charset_normalizer-3.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105", size = 210985, upload-time = "2026-09-30T04:37:26.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/e9/34e597dee616d0b8ee4b34d29399e85c2204ade174157a48505d42baa4ff/charset_normalizer-3.5.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26", size = 367676, upload-time = "2026-09-30T04:37:28.329Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/9f/a5d1c91c0263745e2cd344c5a4415d787c575501ab1d449f1148ac6b495d/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364", size = 248414, upload-time = "2026-09-30T04:37:30.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/79/e697f77464748a3ee3cf490c83d592459400d4898380d66c38366b03080c/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253", size = 244775, upload-time = "2026-09-30T04:37:31.964Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/87/3d42a42e18ea066e2513936fd678a00696e77878b5ae04528976abdbcb83/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0", size = 273877, upload-time = "2026-09-30T04:37:33.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/76/8a28136f3938ba9836f84280ce0c4d61ed1cf15a036b2034900c62634162/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc", size = 269455, upload-time = "2026-09-30T04:37:35.573Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/a1/4fbf5d0f0f1b2a080474c1cf9a2f12c4c6531bb0e8ba591055e846d2b4e9/charset_normalizer-3.5.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229", size = 257336, upload-time = "2026-09-30T04:37:37.397Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/a2/8b50aa320adb880ad579518e6f718f24944804b42a88b83d267d5d444125/charset_normalizer-3.5.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5", size = 257584, upload-time = "2026-09-30T04:37:39.522Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/57/50e3fed84e175f40349bd0da7a4fce94c87f0378f52d74f511d89e0bdc20/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98", size = 251854, upload-time = "2026-09-30T04:37:41.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/54/f7fbb3493c9f49091213b9c2d6dd65800696f1ce1a3f196a4205f50417b1/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3", size = 248767, upload-time = "2026-09-30T04:37:42.883Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/37/b3a6385acc5a1e45b39ae9c90bfb9cf838a09b9dd37ef2740ab4c6b4a2eb/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2", size = 274479, upload-time = "2026-09-30T04:37:44.658Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/44/809913e2cfd279e635a9294fdbbfb1b1dc62a8189d473d561f649fce98d8/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf", size = 258310, upload-time = "2026-09-30T04:37:46.529Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/a2/f28400ab13359d91bd39179df8e149376b9bf36588e739a3a4f9de2b84b2/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95", size = 271100, upload-time = "2026-09-30T04:37:48.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/89/9bab37955edf0adb3b66f8a3a6617d9f2f487e0d56f295a6a286cb640aa6/charset_normalizer-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d", size = 259936, upload-time = "2026-09-30T04:37:50.023Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/b5/4459e08d45a679f903d50fea08bc52cfa728cca4d7bd02c757b5e5abda2e/charset_normalizer-3.5.2-cp315-cp315-win32.whl", hash = "sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847", size = 186301, upload-time = "2026-09-30T04:37:51.722Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/e8/55d5fd3935b4bce6da4fe0df61898e8c82653e317e677bd58aceb9c60f13/charset_normalizer-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8", size = 210858, upload-time = "2026-09-30T04:37:53.427Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/5b/974423c2fd8e524c7a7f64318c1e02240ef954912fa2b4d70344107b9c68/charset_normalizer-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a", size = 200309, upload-time = "2026-09-30T04:37:55.015Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/f9/00ee0195db1013d8f7c416fd770fbeb560bb46eb2e36b054d05cb56f6cfa/charset_normalizer-3.5.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1", size = 395343, upload-time = "2026-09-30T04:37:56.743Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/3a/c00b50e94c964cf934c7899cd47c97952fc11dad71cc5884b3c61795b09b/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b", size = 250114, upload-time = "2026-09-30T04:37:58.607Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/27/d102dc880bbcffd0479ab64dfc1fb96777a854355a55e2bda72a71efadcb/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f", size = 239997, upload-time = "2026-09-30T04:38:00.511Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/4a/bf7ef45794dd293fab5f98a9309817977fbb845b9998f171b8cc5d8437a3/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3", size = 272313, upload-time = "2026-09-30T04:38:02.509Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/ee/008a2837737991474c5754bb3191010007663860979701990982a502cbaf/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e", size = 269986, upload-time = "2026-09-30T04:38:04.435Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/ad/bd74a283940dc910c5b14f8e4f80a248082bc9c0fcbe1f54530cb6d9cc5e/charset_normalizer-3.5.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9", size = 260202, upload-time = "2026-09-30T04:38:06.549Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/7b/ed341c66f69f688723501fac752be3d63c7159ca0d0d4174fc611e5710bb/charset_normalizer-3.5.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a", size = 259083, upload-time = "2026-09-30T04:38:08.311Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/9d/e41588b777965e5031a43128a1e96173ebb35ac75fc53ec3b517e7c21cd4/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115", size = 253081, upload-time = "2026-09-30T04:38:10.402Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/35/b761eb6d8c1eb218b9b42b9b4d5ac902afdc399fb6dac6f9a9aac7bda589/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c", size = 248712, upload-time = "2026-09-30T04:38:12.317Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/2c/147169a041b747759f37405c0a97157e8e92de967968373101ff14915cba/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d", size = 274176, upload-time = "2026-09-30T04:38:14.138Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/2d/0ff8db0d373ba8538db686db11cd7e8912031490b9e4f383b41912e8d594/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d", size = 259413, upload-time = "2026-09-30T04:38:15.841Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/8e/b4a085fb47c9d3a7e43576a4784fdd8fe23f907514a972de8086edaf7a48/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4", size = 269434, upload-time = "2026-09-30T04:38:17.626Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/1c/d8d8d7322a7c3eecdf3237a4a419cf41d2eaad8e006ce7dfdd9d4c8fa2eb/charset_normalizer-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b", size = 262716, upload-time = "2026-09-30T04:38:19.214Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/16/0e4c6ba9b44e97a2da150e52d331e8f9c968b21b358fbffa6c856cebcd89/charset_normalizer-3.5.2-cp315-cp315t-win32.whl", hash = "sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800", size = 197159, upload-time = "2026-09-30T04:38:21.037Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/33/e90bc2b1374f7f36ef106f56620de5a783907e19ca857efe2277e31cac3e/charset_normalizer-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21", size = 221322, upload-time = "2026-09-30T04:38:22.886Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/89/dfa6dcb08c200b7830ab56439e8c1890f2971d51aafbb3937894a2e7fcfc/charset_normalizer-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58", size = 209192, upload-time = "2026-09-30T04:38:24.648Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/ab/176fbfd5b64939c55d652366aa5b9ef1d767af207a3aa6ebeb0d226c484d/charset_normalizer-3.5.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd", size = 331815, upload-time = "2026-09-30T04:38:26.216Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/84/371eac6b30bdbcbf2d632a1a01809103459216fcaae61b8b8d922c1bfb8a/charset_normalizer-3.5.2-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7", size = 253276, upload-time = "2026-09-30T04:38:28.032Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/6f/c4fbae58febff71709c51bc7e18fdfa55341dc382704740f9f0cbf03817b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f", size = 241239, upload-time = "2026-09-30T04:38:29.732Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/71/458c3f42164a07d0c5210798e9e704b39e540a6793b05aba67f3a35243a9/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93", size = 231121, upload-time = "2026-09-30T04:38:31.462Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/54/ab9e89367076f6331bb6c65c4bf14a5361fa5191cb6561bf534f18504e1b/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade", size = 260350, upload-time = "2026-09-30T04:38:33.239Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/c1/061431ecc688d9d76602502cb57cc01e691e682c18f1beb45f9673b5bbd2/charset_normalizer-3.5.2-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0", size = 255430, upload-time = "2026-09-30T04:38:34.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/1f/20c8949f0676f7ab811abdeb7f4d7f1cbc6e61ff20bef08b44edeb092bc8/charset_normalizer-3.5.2-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26", size = 250612, upload-time = "2026-09-30T04:38:36.649Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/9e/46f2fa4c431fc98c4ae76a8cb5bdca54e0341e3cfc3fcfd8e82740250818/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011", size = 242083, upload-time = "2026-09-30T04:38:38.26Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/39/559be29a0c0f086e0bba6922babd38916cc5e0b58ced4de13ee01ea05508/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621", size = 232738, upload-time = "2026-09-30T04:38:39.81Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/6c/387b0e4f756a282831c1d9fc6aeb6c51ca4507ca202767c8de15ce9b12e2/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4", size = 260703, upload-time = "2026-09-30T04:38:41.346Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/92/1fdf015f09ef449f50d3ac4b67c90887c9c318b727daa95cc4f866e6521d/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e", size = 247622, upload-time = "2026-09-30T04:38:42.937Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/3c/8e7b8a5671ad5d433669fb2a76f1a0164df2d9b1718b0206bc2a16d840cc/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c", size = 257500, upload-time = "2026-09-30T04:38:44.604Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/f0/45b579df5cabc1d5d53ea1cc35e8437d3ca768c0acccc7041517cb6fbb32/charset_normalizer-3.5.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0", size = 255100, upload-time = "2026-09-30T04:38:46.289Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/68/fdec18a343f5fb3f310588dd478b09ac4799e0b187dbade3a8cd776f03ef/charset_normalizer-3.5.2-cp37-abi3-win32.whl", hash = "sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf", size = 174499, upload-time = "2026-09-30T04:38:47.999Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/8a/b618149cc5207943a0242068d7a27897f56a62947b5a039085f2a22029f8/charset_normalizer-3.5.2-cp37-abi3-win_amd64.whl", hash = "sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036", size = 200092, upload-time = "2026-09-30T04:38:49.707Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/cf/4c66866fa9e2b1c78e3c911516d1de497a677b7ac60f1eceda74ce777ca3/charset_normalizer-3.5.2-cp37-abi3-win_arm64.whl", hash = "sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e", size = 294363, upload-time = "2026-09-30T04:38:51.312Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/ad/d07d7862a62ffa6d79d68074d14823243dd235a77c45262acbf6adeb28bf/charset_normalizer-3.5.2-py3-none-any.whl", hash = "sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685", size = 68872, upload-time = "2026-09-30T04:39:21.828Z" }, +] + +[[package]] +name = "chongming-auth" +version = "0.1.0" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +dependencies = [ + { name = "chongming-database" }, + { name = "chongming-jwt" }, + { name = "chongming-password" }, + { name = "orjson" }, + { name = "pydantic" }, + { name = "sqlmodel" }, +] +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-auth/0.1.0/chongming_auth-0.1.0.tar.gz", hash = "sha256:745aa621e9ad4ed3ca583c2d310362869dce63e9731f5581d9b777b55203487d" } +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-auth/0.1.0/chongming_auth-0.1.0-py3-none-any.whl", hash = "sha256:a89948aa9a0229a79e0f1e2285f1ecf60cefc0c029a7fbdabf0b244a9b902d9a" }, +] + +[[package]] +name = "chongming-cache" +version = "0.2.2" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +dependencies = [ + { name = "orjson" }, + { name = "redis" }, +] +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.2/chongming_cache-0.2.2.tar.gz", hash = "sha256:1c224329747d99171956ae8e4606a08847acfcbfc6664662bca2513f0c0ca1d1" } +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.2/chongming_cache-0.2.2-py3-none-any.whl", hash = "sha256:902877168da16e34204982643f44d1313bad5a43bca65d348522523d307f0c16" }, +] + +[[package]] +name = "chongming-config" +version = "0.1.0" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +dependencies = [ + { name = "tomli" }, + { name = "typing-extensions" }, +] +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-config/0.1.0/chongming_config-0.1.0.tar.gz", hash = "sha256:4c1ae9af83363b4772552352430d96ed1790b3be7dbc1b5fe14017264df2ce4f" } +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-config/0.1.0/chongming_config-0.1.0-py3-none-any.whl", hash = "sha256:55416141088d3bc1ab2754817ed8266f107c3d087c89702778ff3dbe9873f5cd" }, +] + +[[package]] +name = "chongming-database" +version = "0.2.1" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +dependencies = [ + { name = "aiosqlite" }, + { name = "asyncpg" }, + { name = "sqlmodel" }, +] +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-database/0.2.1/chongming_database-0.2.1.tar.gz", hash = "sha256:f5c5dab6b4630567135f5284c718611b86b0e8aa68c11b64db609470a9b5c69f" } +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-database/0.2.1/chongming_database-0.2.1-py3-none-any.whl", hash = "sha256:64fa23f1d7221806fd3c71d3b6e29e7ffa6c2a35af84233688ca3b35196f0a30" }, +] + +[[package]] +name = "chongming-gateway" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "amqtt" }, + { name = "chongming-auth" }, + { name = "chongming-cache" }, + { name = "chongming-config" }, + { name = "chongming-jwt" }, + { name = "chongming-worker" }, + { name = "eclipse-zenoh" }, + { name = "fastapi" }, + { name = "granian", extra = ["reload", "uvloop"] }, + { name = "gunicorn" }, + { name = "httptools" }, + { name = "orjson" }, + { name = "pwdlib" }, + { name = "pydantic" }, + { name = "sqlite-vfs" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "uvloop" }, +] + +[package.dev-dependencies] +dev = [ + { name = "twine" }, +] + +[package.metadata] +requires-dist = [ + { name = "amqtt" }, + { name = "chongming-auth" }, + { name = "chongming-cache" }, + { name = "chongming-config" }, + { name = "chongming-jwt" }, + { name = "chongming-worker" }, + { name = "eclipse-zenoh" }, + { name = "fastapi" }, + { name = "granian", extras = ["reload", "uvloop"], specifier = ">=2.8.4" }, + { name = "gunicorn" }, + { name = "httptools" }, + { name = "orjson" }, + { name = "pwdlib" }, + { name = "pydantic" }, + { name = "sqlite-vfs" }, + { name = "starlette" }, + { name = "uvicorn" }, + { name = "uvloop" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "twine", specifier = ">=7.0.0" }] + +[[package]] +name = "chongming-jwt" +version = "0.1.0" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-linux_aarch64.whl", hash = "sha256:0512956acaa7a17aaf9708da8d7b3a7dc366ef0226f03550d7b1b06940ba733b" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-linux_x86_64.whl", hash = "sha256:527676e710d038f270093d0964c92fc5e976f1f5db23e846e3819b1c8a566721" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:ec47b7698e928141c9828200bed4377754c296e688f29d694f9b1cc3d5f00bf0" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:d2329de197af17141048da32b52dcda87ff578353b4fae78c3bbd0043244cdfc" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:d63cc24550b7084e55d4dd0cb9f99cbd0a9d77da39ad0685698fd484e4376e72" }, +] + +[[package]] +name = "chongming-password" +version = "0.1.0" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-linux_aarch64.whl", hash = "sha256:752651cce38a999466e9b342f18f0ceb78b17907646440d2b1c6306ebc9c7648" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-linux_x86_64.whl", hash = "sha256:97414ddb764a8efa0183eb6a4519530a8522729ff633a06285a88b7b22fa77b4" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:22109f4d1d1263bf840554601da9eb2206fb56ba7deae6828f4ebf710edb8dd1" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:65fbe21f0d9cd152e0142b129e23b1697234f5eeba6a9d0ded1a14949849cca3" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:9620458d04e1cc9c93842f7b960b2b7af8b9bc5f88643e785d12686301071b3c" }, +] + +[[package]] +name = "chongming-worker" +version = "0.5.0" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +dependencies = [ + { name = "chongming-cache" }, + { name = "chongming-database" }, + { name = "eclipse-zenoh" }, + { name = "minio" }, + { name = "orjson" }, + { name = "pydantic" }, + { name = "tomli" }, +] +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-worker/0.5.0/chongming_worker-0.5.0-py3-none-any.whl", hash = "sha256:c39b1bb8e373c11efa892e0bdbbdcc019e1b86bd6a5053aa7138b9aaa18f86f1" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, +] + +[[package]] +name = "dacite" +version = "1.9.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/a0/7ca79796e799a3e782045d29bf052b5cde7439a2bbb17f15ff44f7aacc63/dacite-1.9.2.tar.gz", hash = "sha256:6ccc3b299727c7aa17582f0021f6ae14d5de47c7227932c47fec4cdfefd26f09", size = 22420, upload-time = "2025-02-05T09:27:29.757Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl", hash = "sha256:053f7c3f5128ca2e9aceb66892b1a3c8936d02c686e707bee96e19deef4bc4a0", size = 16600, upload-time = "2025-02-05T09:27:24.345Z" }, +] + +[[package]] +name = "docutils" +version = "0.23" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" }, +] + +[[package]] +name = "eclipse-zenoh" +version = "1.10.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/1f/54e50d0fdd2d41499b18fd2066add8a53460cddb85b5c2585afd0d3159ed/eclipse_zenoh-1.10.1.tar.gz", hash = "sha256:e601b9883746b742eb7210ec73348c9980a2e65cbd54b4a8017662de9aeb15cb", size = 170364, upload-time = "2026-09-07T15:25:53.179Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/24/d0802eec65a925bfef6f96924faa8c0285c8c584660881af938d92a92c7b/eclipse_zenoh-1.10.1-cp39-abi3-linux_armv6l.whl", hash = "sha256:f2351b810dbda09f6dbe668ca965560bf8cee98075b28b8b71e88428f613ed35", size = 10952070, upload-time = "2026-09-07T15:25:35.566Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/6d/2ffd5e7a18c5b90c6d8468fd48c3416a4615b3d346dd2bbfe88849adad14/eclipse_zenoh-1.10.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9e99d5801e0e12a10dbc5d976d227ec7a1ff73d86c20ff4315102b8237ac3ee8", size = 19315408, upload-time = "2026-09-07T15:25:37.87Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/e8/02f615c757ddc983152ce9fabe1f94f073678593b57a93654e10aeb827e5/eclipse_zenoh-1.10.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ad269f1cb3e4a66a0130ea63a0b2bca214cbbe75181baa4fe12f27ddb5c42221", size = 9876094, upload-time = "2026-09-07T15:25:40.379Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/29/e856a2115f3a43cab68ec24d09f0d253466d7bc4083841bfd9bc1f6a1adb/eclipse_zenoh-1.10.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:476bfca6f15f1c68e71037efbbf61b880719ea1a8eaadc6f9df978e144cf44d2", size = 10796254, upload-time = "2026-09-07T15:25:42.969Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/32/b524fcf38ebc80f9c39e5104473a55a3354bbc3ac151d1cb79514cb0b8ad/eclipse_zenoh-1.10.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb73bce18afe43fdd799d2cf3f066c7ce353d2552ae2776badffe6144352e79f", size = 11747371, upload-time = "2026-09-07T15:25:44.906Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/a2/5a54606f5202581256a8bb405e51b6d69254aa84a44e716aedae21fb2964/eclipse_zenoh-1.10.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:faa4071aea57650594e8f4ea39147f791fbd6105cbd7afe15e96dc3589bd83db", size = 10331829, upload-time = "2026-09-07T15:25:46.947Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/21/976cd9e4091362bc25ed5031f229d72032363c611bd5260950284583861e/eclipse_zenoh-1.10.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1950fdc8de72e5a3847d845d57e72dedf35275725e42be46a26454cfabb54c8c", size = 10905687, upload-time = "2026-09-07T15:25:48.924Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/24/10f71ebd91d1cfbfd68449a1c52b6e38303f4302f8dc5d612d0b0ff385a8/eclipse_zenoh-1.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:44e368a6a509cf385d3657f48ed99f1d677ce9877e8893e15b9cf1b504dc9150", size = 9130747, upload-time = "2026-09-07T15:25:51.229Z" }, +] + +[[package]] +name = "fastapi" +version = "0.143.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/d7/6a8753ab6c1d432dc53703c3e1b92974a94531b7d047c32bbaae461ea844/fastapi-0.143.0.tar.gz", hash = "sha256:1acffe48206a80917cf7dac21992b5c44b25384e8902bf745c1fd9dabcf6c51f", size = 468391, upload-time = "2026-10-08T12:29:46.54Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/f4/27e386913417ad32aae42bba48b0c0cce40e9ff2fba1a871ca2702c37324/fastapi-0.143.0-py3-none-any.whl", hash = "sha256:3e9395fd35276425b61b516a31fdd7c77fe2af83e41b4da22e30696fb1304c5d", size = 144665, upload-time = "2026-10-08T12:29:44.853Z" }, +] + +[[package]] +name = "granian" +version = "2.8.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/6e/7002545a24aa0652c2ea00e626563379316b713ef1a09c2db5eacc109a41/granian-2.8.4.tar.gz", hash = "sha256:15e4f240dda62ca1bc9d84b264a25503812481ac60dfbedd784a3a25de110436", size = 130456, upload-time = "2026-09-30T15:12:35.901Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/b7/229ef688c4829522b65113754cd0ede0b3b3586596e1e8da4dd7e0932c90/granian-2.8.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2991b162535c9a0d8847b18c5111f8f0b9547520f0fcfb36b12332fdafe84660", size = 4549026, upload-time = "2026-09-30T15:10:49.089Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/53/76be0f6ad8be7214cb156ff26f4d36eee6f2e6ee5ba492a33c8d721416f1/granian-2.8.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0519692106465e0145d1a810034227a3cae0008d2aee3a19fe250105307c265d", size = 4257474, upload-time = "2026-09-30T15:10:50.613Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/fd/b18ca267dd93f067bfe2d2b0a484d662987c44303bd1b31324cb9713e4fb/granian-2.8.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96ff07be39e0d7d94a43736a87c1cc41c8a8d17ef9336334b04cc055911b5968", size = 5133239, upload-time = "2026-09-30T15:10:52.086Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/5d/65bb8ac6b6f2593902bb5844655753fe1cf1f06825a9880b5efe88f681c2/granian-2.8.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:123daec45941e285b9f057a957183404f1607f239fcf7aeffef168384dfb3fa7", size = 4652024, upload-time = "2026-09-30T15:10:53.836Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/7b/3a7501dec4bb630db8832f53c1d65ad344783aebf579b3d5e99824c064ec/granian-2.8.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18037cc4a5fbaebedcf333ae29ce2f742158e2a73195f6948d2f26cd9e689636", size = 5030621, upload-time = "2026-09-30T15:10:55.233Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/e4/a60f4e237e9f096d6d0976ad51718d695336acc785b1a0a98ae218153840/granian-2.8.4-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:19feff2d50f9c72d88d554e66e7cf465bf49635a99c874fa5b632df0e27e487d", size = 5127306, upload-time = "2026-09-30T15:10:56.567Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/d7/74e1777a5c064bdcd1960014cd7d9ba928d384083123529e334d6f97380e/granian-2.8.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:05bf45f207d1be729ae383f09c07ee08d85aa6cd263e066ec88c44a8792b116e", size = 5169531, upload-time = "2026-09-30T15:10:57.974Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/0f/8f8ed0bb55f1c64b1de86c8a416cdd85775acf90ffdf4042e926280029ec/granian-2.8.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:cc7ef4fc9036789a91cca6158e41062587817243ac96e2f05604c0ec145669a6", size = 5251863, upload-time = "2026-09-30T15:10:59.526Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/11/7a1658cbeb6fc2ca10c1f8931865322d53ea72773d00fd769b0a888c8787/granian-2.8.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d3a7d8b71692f3dd22595fc4f4cda45a6d82e773df4da39a5f9ecf17fc8c543b", size = 5100426, upload-time = "2026-09-30T15:11:01.028Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/28/886c3ca853cc905d20c6ca39c686953c169a5b9a8ccaf32bdf65b8c4292a/granian-2.8.4-cp312-cp312-win_amd64.whl", hash = "sha256:ecdf904691c07e5e73f79a27b12926aac86d04f264fd8a72fedf2e0bdc53c5c4", size = 3168325, upload-time = "2026-09-30T15:11:02.47Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/ab/0fc042bf594304f7587ac288bf29d9026b434b503e643c52f54c963c5732/granian-2.8.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1cb89448969d5b64b131a7d2709cfcd207c85d5535a4b9c11648f78184b1fa54", size = 4522590, upload-time = "2026-09-30T15:11:03.86Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/a5/282169d9e30f99dab2bf9d9b2d559fd425deaa1b8b15ab64e9029b6dabd1/granian-2.8.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8359c7bbc316cc6d9272e44cc82e62a14bd6affe47b0c10c95c30cf3d0f02d91", size = 4258002, upload-time = "2026-09-30T15:11:05.159Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/20/555b072727e7a6ee13a08ad5c3cc752195ce5042ed820767ee5aa389f752/granian-2.8.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf0c4a2cabce0df8a605a49d98b0022cd067d3b813ac45b3a88f512c3db59fe1", size = 5022333, upload-time = "2026-09-30T15:11:06.688Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/f1/76de3df725f6fa815f3e3f051cfdcca2a921e3b1832104c04d1bf6422535/granian-2.8.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39c19059bdc7e9156c14efe2c540af16049dfb3a8d197ba0742b973f34ae48e6", size = 4572162, upload-time = "2026-09-30T15:11:08.325Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/5a/c64b1c67127deaed269ed73efce0f76171a0701893a0c724c300c86e4fbe/granian-2.8.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f28d6ff79f26e7cd4c992b6198a9f14fae266da80d2b6bd0da8fceef07c57850", size = 5031889, upload-time = "2026-09-30T15:11:09.852Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/c2/ea37cb80127293a24ea8dd5c9223b446ec132de819d8abcf58a05fbc32c8/granian-2.8.4-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c5a028410a4f9532ec5c93769828cc978b17efe7e3fae76fc66311df81aeb8d0", size = 5120823, upload-time = "2026-09-30T15:11:11.358Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/b6/a00f0b11fcf1894914c3c0067a577beed4fb3983683fa919b1c0981bd668/granian-2.8.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:82285a809e72d59a576904fed60bb7aa117e52a1f20ac62bb1149b585cba6dea", size = 5164882, upload-time = "2026-09-30T15:11:12.783Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/0e/96cb2d5d2682f0c00fa7919b9771ebd09d38837be35b28b885d7e7365f46/granian-2.8.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e0fe0f742a807abb25f4145303e46ff2dfd6fcd4b3c541fa89f691a0b1fd84c0", size = 5119982, upload-time = "2026-09-30T15:11:14.211Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/c0/2aeab45b53968edb7ef80466da684c34f1afd36c6a692ceb3e90f5c41e6f/granian-2.8.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8bc056a839cfc1a498c8505ee9fafe86e6da4caa01bc2358efbb59fb6607d663", size = 5103851, upload-time = "2026-09-30T15:11:15.658Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/72/9ff987c1651dfe446ed514312b1f8c8d7a83eb7e26d082b7d6184e37213a/granian-2.8.4-cp313-cp313-win_amd64.whl", hash = "sha256:e1aca643411ee94cb7acf5c2378d46c9e32d6cd94d235ae940fbbcef972cd60b", size = 3189341, upload-time = "2026-09-30T15:11:17.124Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/c3/91d8bb7c8250ed85ef187271495e16447afa7d282dfd8cb26d8bd620de63/granian-2.8.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e6387e6c784ba6c778b6417161bfed6fd75dc93df7152fedc61847ca79bdf3c4", size = 4537988, upload-time = "2026-09-30T15:11:18.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/8a/89d37db67ff2682a3ffaec1b7ea09af7db59624713d30f5e7b7fb3a633d7/granian-2.8.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:522de7ed1154082f11176a45df841bf58ed45fcaf810eef6200e39b4f371a29c", size = 4294229, upload-time = "2026-09-30T15:11:19.967Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/69/5a/3840c16e0ba01724f2aa00acd784d69f5979143651a22bea709e513ed201/granian-2.8.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:439f2bc74e0db5485a1749ec7040043a4061e05d444e4a754393d90738697668", size = 5019362, upload-time = "2026-09-30T15:11:21.321Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/09/cf2b9f5e43723260e7abca049ab637c3d6c7c417fadffa2fbe1ddbaef192/granian-2.8.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f00690ea448a4dcd4cc69e8bcdcf8cb6e7acf9549c273ef344577bc5f5d05cb", size = 4567850, upload-time = "2026-09-30T15:11:23.056Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/44/7cee563075a450361faa1441af71e9ad2e847398f88dbab67324dcf5a368/granian-2.8.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41fd375030f9de7c864d52e51bc436db5476a5599fa97a6cd481fbf7c1e349ca", size = 5009761, upload-time = "2026-09-30T15:11:24.599Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/77/f82439fecd929514915582039a3de7d27aff95c1bff517324aea77273f30/granian-2.8.4-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:72e09942428b04931a0b1fb853f5101352a11f82e5805468d72bd2f5ec27e810", size = 5128512, upload-time = "2026-09-30T15:11:26.06Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/5d/bf0619b52f671b6a535c56295f48a87d11e6f7f27108433ba176c39322d3/granian-2.8.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bd7796798d4e127b273dde3d5b7fd3b43f682e6b21f2cef84c8b20e6a09e1255", size = 5166857, upload-time = "2026-09-30T15:11:27.854Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/e6/f15ee7af0a18fa7881ca692300d100296eeea98358d523b2d321cbe6f3b4/granian-2.8.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:71242dee81f4f59e8d6dff173682332cadd9635e76a5b014e7b664421db90566", size = 5118394, upload-time = "2026-09-30T15:11:29.517Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/6c/f8fda6d6551576f635d403c4bb7945fe9400141fb30236556a8b1ab4ea04/granian-2.8.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:85b87b6fb351d1bb1926691c6c73ec95f21eb6882f4742241b597391c456a645", size = 5080931, upload-time = "2026-09-30T15:11:31.139Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/89/5825188a9acfad14a780bf90435ebb8a603c66b459d9eff44fdee596b060/granian-2.8.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0479221d6d0224f26130decd24f01a4415c672c18f35c8b4500cd42ec5591c0", size = 3139278, upload-time = "2026-09-30T15:11:32.61Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/ba/7d522854e9994a53e50b652e30f26833d742395d12d23ffdb0c10389afd3/granian-2.8.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:169339c7f6f8c755fb27e8639ba4c5e86e1c70a371d6067ae8448b6f74e52ba2", size = 4434002, upload-time = "2026-09-30T15:11:34.047Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/5a/f7df2a966d1be6b6d741806e70a634f3a134e4718479a2f489d61467fcde/granian-2.8.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20db1d287d1eba2e25ed71ed64ef5385bba09866d686b540f190c7d3c16f422f", size = 4150598, upload-time = "2026-09-30T15:11:35.854Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/a6/5d4b8e092a105d77f523008ea727d8d29f7aa5ba08c690baa15f12e59ac8/granian-2.8.4-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d14375c88b689fa236bc68f80a8157142207971e4c87d500fd20740e69ba16d", size = 4413958, upload-time = "2026-09-30T15:11:37.366Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/4b/d1790037d362936370ca6aecb366429a23ab4829c4ac9d416077e9972781/granian-2.8.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6273949df06399caf9dbab4d4580f0c90ac47bdb2c2befc582883b16352d908", size = 5041687, upload-time = "2026-09-30T15:11:39.026Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/4d/7e9f7168bce74d4629b596ca9832388fa1831aef0ed51ac5f01752092c54/granian-2.8.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:605ec23545d5c49c8a42ba0f94f0bccc4c05777cedc7ebcdd008313de0fb34c3", size = 4858472, upload-time = "2026-09-30T15:11:40.595Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/3f/5a953871497f0d3d6b9dd23dee5bf96421a97ecbec34d1732ca68512805d/granian-2.8.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:8696d0a85251c6be57d5f768af38d0b065c2704c61f7872b692ad9d604b202e5", size = 4906779, upload-time = "2026-09-30T15:11:42.555Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/a5/71735f279a6598c7e09cbb020dda67a4303bb74ece2c834e51345a12d678/granian-2.8.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:9cab808b3ef5d8db83226e898510b337d6872bceaa8e1e5f932ba0df90fdf974", size = 5106296, upload-time = "2026-09-30T15:11:44.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/0b/e11a340ebbb717eb3e9b30c1baa9684fcb0d065f9109412f1e7a8067849d/granian-2.8.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:6d9c15ad2e6f692ef7c8e7d0a82f6a9942268b8859ed790e4aaaa5dc18aa1a60", size = 5163457, upload-time = "2026-09-30T15:11:45.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/ab/922185fd354eb531fc78cf2205f3523e25452efc2faf4e0ba47f4649a5de/granian-2.8.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:663bdf86c3afbb16c2f4b390de78dbd4ce8ad5ba41264ae4ac52a79b58bcddd7", size = 5099484, upload-time = "2026-09-30T15:11:47.379Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/e1/2290930bb9f322e2bc6c3f6f576600ff16778fc8d19fcac6bce90d298bc0/granian-2.8.4-cp314-cp314t-win_amd64.whl", hash = "sha256:66bf5670d5b29e4b026516e29a3ce74cbb83a76b4b448398a4fc503c5497c772", size = 3110657, upload-time = "2026-09-30T15:11:48.86Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/a6/83006f1d834631d7baecdea42d504c4d2ab9a25cc2e21044c106983c7527/granian-2.8.4-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:5cb49d576297e15657282bb8755945e990729e0e3a830a2bc1e959e7680f4528", size = 4537585, upload-time = "2026-09-30T15:11:50.36Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/95/099a730f340556291d13aafa2fabd425ed951edd9e7d25c15c5affbb4420/granian-2.8.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:61692e10cba625ea8455039629b87c8df2b1c25daca6c6891384d2315b7a61a2", size = 4293488, upload-time = "2026-09-30T15:11:51.938Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/5f/1ea52bf78cf5234f3fb4d6c13349f9c475122c6fc1b3278549622a2f1da3/granian-2.8.4-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6bf588c3c8d26bd355f75442a55806a2c62a0d81005ed83c7c382062da8127ae", size = 5019509, upload-time = "2026-09-30T15:11:53.357Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/b6/d4fe258bc4c82eaec3902545dc4a7fb90f9810504dbba1260b022b8ef8e9/granian-2.8.4-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c2f85e97c4c1578c05fafe4c82878a9d868049ae7e919ebd28d3b5ea30f0dd3", size = 4568784, upload-time = "2026-09-30T15:11:55.601Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/6c/95b2d44f773cc54c40c73c556228a28234d3b28a24ebd1d8a9f106724a3b/granian-2.8.4-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79cc8b1d9f6f3027800b6c14398978d832fee78a2050ca83c60a6c0cdcd95195", size = 5009546, upload-time = "2026-09-30T15:11:57.028Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/a3/7724eb6ce6bb0fc6eceff22b9ca9ab3a8669543beef5a1061fc75593f89b/granian-2.8.4-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:3c3385e268c4c4e79af73f1b2d8dc4eec9701a3105fffbb148f786351580fc82", size = 5127719, upload-time = "2026-09-30T15:11:58.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/8a/52701781c0761683fafdc0675244b769c7e952ebe907c1c30a6b32d6a17d/granian-2.8.4-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:bc21d4aec5c36b251855ef63ef7173a3cd7105fc20936c4e7ea0c03a9638d246", size = 5166484, upload-time = "2026-09-30T15:12:00.276Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/c5/e41b74a152882c1af274df3ed2d24d626538d5ea99763670367e5f9ad3ba/granian-2.8.4-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:b1aa1a2f42e41cc63fbd2b3cb7de0fe65b23e656615779c6ec96ccf2112e5325", size = 5119075, upload-time = "2026-09-30T15:12:01.732Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/11/f61ed83a4d888e7b3d5171ede6daeadf045ae273bf0d58cb3c7eedce24c1/granian-2.8.4-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:38519ba3650c5667af5aec2d6dcc778656696742a777661a3c27caa0b68ac05f", size = 5078995, upload-time = "2026-09-30T15:12:03.344Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/f9/7ed2d4489b24a76d59529efd28c814e3ec2100af830809ac46cac564a07e/granian-2.8.4-cp315-cp315-win_amd64.whl", hash = "sha256:a23f30b2acf5bc1b30fc72110142e098cf929d75321c47db2f6c16a309ef9fe2", size = 3138950, upload-time = "2026-09-30T15:12:05.221Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/39/e07a28bc53346d93a518bd89eb21c1839af1ab05d930502bf0177f0c064a/granian-2.8.4-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:05e0b6fd7f6ac6f584d36886407399a327d292b1950f6dc96b0f9f865d5ddd99", size = 4433179, upload-time = "2026-09-30T15:12:06.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/86/3b8e351e427bcf77bfcb91f84bc76e487fd6d1808106874f071a97b4c3a7/granian-2.8.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4eaa524434e86ff49a5ec91cc9f68379c8c1fbb95640b4178173f74dda9daf51", size = 4150416, upload-time = "2026-09-30T15:12:08.358Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e1/0f/0512b4ccbd3622d58ac15214b19e9991f863322bc3d99eed83c63c3ef1f1/granian-2.8.4-cp315-cp315t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a751aa614a7c970c49abdd09e2c8434e23b6c4b9f574db811d7e34a1bfb63a2", size = 4414189, upload-time = "2026-09-30T15:12:10.087Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/c1/36e55a2e62faf9043004082240d1ba5dc7d2b86aaa324e0af70a85ae68a0/granian-2.8.4-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:408f231dd02f2b0b0f9cbd1979952a242f28ea0abb7f4ba28dac93a607ce82da", size = 5042083, upload-time = "2026-09-30T15:12:11.6Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/a4/20a42d79ca47af0376e766f5edd420bc967d41ce34a0dcf58eb028806dee/granian-2.8.4-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96b36fa67273e846a98634abf00adbddae7a56480c1639920157b21a1e96fc5d", size = 4858516, upload-time = "2026-09-30T15:12:13.049Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/82/adba7f7748dbd66c2c56f26ccfcaf6fffb715ccda626da7741a1d2cff1d1/granian-2.8.4-cp315-cp315t-manylinux_2_28_aarch64.whl", hash = "sha256:f49deab00a5bb19a262f7ed727181c9b24a47c1db58debc6e929559c9480dbff", size = 4905319, upload-time = "2026-09-30T15:12:14.44Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/8c/caa449473cd4a3c7d13957dd6af0da478f0fce4abe3b0f808edd2262783b/granian-2.8.4-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:74ef5fd4547f72c949daa2bfa5e1595f84e5c5e486d64ba821e1cbd9254dbc64", size = 5105886, upload-time = "2026-09-30T15:12:16.008Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/0e/f4bb890dd82ddc7a0137eb6a65634f7a2bc43e38b1f9894776fe0cbad2d3/granian-2.8.4-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:1a3140ae4381bcae90cf61fbaadd34862d5757f4d50491f21f95907ccdd8f0a8", size = 5163449, upload-time = "2026-09-30T15:12:17.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/46/61c05c9ccb0548682580e1e47fe84b8338be8041f7d7c988b7b4eadb6c48/granian-2.8.4-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:2628902003bdb7830dae67dba301aa3457922beb06a66d090755f79c46801628", size = 5098768, upload-time = "2026-09-30T15:12:19.137Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/a4/b3f7b8010d71dc233bcb1a6bd9742153cf094da3b9d2f93a59a85690e6dc/granian-2.8.4-cp315-cp315t-win_amd64.whl", hash = "sha256:44d3a5fdd97b78cd9971244996486387b2a80f577b42703725e78681f36ea810", size = 3111688, upload-time = "2026-09-30T15:12:20.912Z" }, +] + +[package.optional-dependencies] +reload = [ + { name = "watchfiles" }, +] +uvloop = [ + { name = "uvloop", marker = "platform_python_implementation == 'CPython' and sys_platform != 'win32'" }, +] + +[[package]] +name = "gunicorn" +version = "26.2.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/8a/e4ef6ee11701b6cd64702848415ffb69eeff85cb388a3c6c7fe86f22f3f8/gunicorn-26.2.0.tar.gz", hash = "sha256:62b864895d9ebff0b2f9867ba04fe811c93121596540830c9c916d0769668447", size = 787921, upload-time = "2026-08-24T15:05:59.3Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/85/7522a52e5e2f42faf1a129113ab63e548c42e103e9af395b7bfe65e403e2/gunicorn-26.2.0-py3-none-any.whl", hash = "sha256:bd249d0b3f7972f7432f0a6b6ff3b3ee2d129f70cd1ff6c09a9dd9e29a2b88e3", size = 228389, upload-time = "2026-08-24T15:05:57.67Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httptools" +version = "0.9.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/ec/deed52912ab7ca6c0b12859330c571c60c61d7267b341b28951fcbf13694/httptools-0.9.0.tar.gz", hash = "sha256:d484ebb7e3a3f3597b0f645fbd1b85633674ca808c1f5ba11c2caf7c66f5c8b6", size = 282523, upload-time = "2026-10-09T19:57:04.301Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/ed/0916b8b7ebd1deeaf22acba71b68c57b4b6b69aa1918f3812dea208b4276/httptools-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9ccc9884241efceb4547a92955d128574c864681f11b7ea3ecbde295fafbe8b", size = 119039, upload-time = "2026-10-09T19:54:32.556Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/0b/9b6de4a01a563a904d0826c9069c824b330e1816df26c9bdf93f60b50857/httptools-0.9.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:45b3002392948dcf578029c89f6318e1289a993a1a5ec38a4161560fab60f811", size = 115051, upload-time = "2026-10-09T19:54:33.908Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/3f/642113e9882f53158ecddf58003d25f18ded2c210ed23bf6eb663d4d51c3/httptools-0.9.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3e3201fe4d46e0d15d7ff9fafc94a605da9eb82d2c5b9837f0368acb325481f1", size = 552848, upload-time = "2026-10-09T19:54:35.434Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/4c/3ecc59c99c28652d8d08d9b5be65770a14d2cadc616dad94224cee2b0e7e/httptools-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58a1b0ec4cbb930e69669f9771715b2c7898d3cdf064d9811f7a66afef96b544", size = 549181, upload-time = "2026-10-09T19:54:37.099Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/ce/21f5b2759590b7054e38d3b704a3c6b853c3395c370b3d6f16c45aea0fc0/httptools-0.9.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4c58dc91aefb31adad500aa68054334f429b840b36dd29e34e834101044cb2ef", size = 569121, upload-time = "2026-10-09T19:54:38.772Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/c3/7c523aa8d0fa7a57010a3e1bbdebc209585009076465f3d1ae6a3f54b814/httptools-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b900073e7b8481ef1aaf4f6c1789d210a1db01a9da8789821578cfeb4c2d540", size = 487649, upload-time = "2026-10-09T19:54:40.398Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/e2/d90d60002692b8afcbc06fb49ca3a4365b32abed6c40fb2b612c67721a00/httptools-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c12d0393a903b58bc5f5a7406d6c5290acfb8284290d68547ce620c06f7d133", size = 529583, upload-time = "2026-10-09T19:54:42.296Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/c0/19172874cde0344a20c85877a0b2d0dcfca31111729ad8a79e8b4ac4e207/httptools-0.9.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:29b0d823e3c1e7cd1093a5dc889245db693ef13ada624cd66e2262421ef38867", size = 552414, upload-time = "2026-10-09T19:54:44.19Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/b8/02ea7910f69e5371986b025fb3b410592106df54e977a5732fd1d95917b5/httptools-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6ebd39ee26db460cfe5ab8b71a15d1149b289139a0d3981522757d6af620887e", size = 482400, upload-time = "2026-10-09T19:54:46.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/97/f05eac916d44cbbfe43668a6a40ab93e7fd8f94d5120d1ce2d8e55c69871/httptools-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4efbee349138a3fee7a4cc3a95abd2d499fae70dd5bff9fed9138d6f570f4283", size = 536148, upload-time = "2026-10-09T19:54:47.695Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/e9/9435dfcb7f1a1d6ebdc79a902164dfca70e33774c80bb60d25c630c31ef1/httptools-0.9.0-cp312-cp312-win32.whl", hash = "sha256:36fac804b8cfd6b935ae64f71349f833d2b6298404626d017a2c57bb942bc643", size = 86274, upload-time = "2026-10-09T19:54:49.1Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/69/813f1bf90be507d4166c437be1a413574d0e0abf36e2fec10c266661b0ee/httptools-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e32b83bd8c2f8b6fa726ef34e63e21c4d7eddc277d40d4ef7245ea3ed28e5b6", size = 92446, upload-time = "2026-10-09T19:54:50.498Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/e0/1d29e328c4cafe843403341e1455e0aec18b0e6910fbb14f12b36b563f19/httptools-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:813a32f94991b9627795528053c73a57d2ce3eb98ede89f0e1c7a31095938e81", size = 88676, upload-time = "2026-10-09T19:54:51.844Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/04/223994f8589750d2a36ceb43203e739cf75bd9e12c226680d73567766908/httptools-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4fb995082fe41ec410b33c48b54fb1d44abb8a6ee762c31e8c42519e8c3a30a9", size = 117115, upload-time = "2026-10-09T19:54:53.356Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/d8/b4407836e567a862ce79d78a628d785db99aba52e63496d68c60eed0d475/httptools-0.9.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:b9cd15cb7cf0d5cc41f649fd789aae12c56c3b83eff593f8e095c1d4555ad5c3", size = 113225, upload-time = "2026-10-09T19:54:54.81Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/f6/0caa51b077492a7306bdbd9dfb907a2246985f0aed1fe2d086255921848b/httptools-0.9.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:088de1738e1af624466a01c35d652dbe6fb825be887c76d68aa850621d81db88", size = 520112, upload-time = "2026-10-09T19:54:56.3Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/da/7a47b7c2106bb10e6d4c04a139d045257a4f93c672fae6f0b9e92b1f7bc2/httptools-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b1ac7f1bc6c0dbf90684b77571a51a21b2463909fd916ce0ac9bfc4d566dc75", size = 516079, upload-time = "2026-10-09T19:54:57.938Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/4d/417b42d2663acf4f5aeb2718dc894ec2be4e3dcfd8caa2d3bf9ee2dce511/httptools-0.9.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9430f65db521db7962ad951571d446171213686f96c998a54dc18ed574821e2", size = 535040, upload-time = "2026-10-09T19:54:59.769Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/de/8df4c09a33ddaf50f697719f20201cf93631ef4b50cec05e42acf179a7c1/httptools-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:52fe0176682a25b15370f23f5b0f1366a84771df89144fb0cd979cb72a94b5ca", size = 462799, upload-time = "2026-10-09T19:55:01.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/90/1bfe91e3fca29c541d85d7ba8ed92a406d4dd13608c281baf7ec75369fec/httptools-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:757e3f79cb865a7db94e0db5f4d0ed3284a69e39d53568f433982ea13c60cac1", size = 497596, upload-time = "2026-10-09T19:55:03.201Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/af/2bbd5af0dd7a0e0c3b63bfefafd87a07041eb13d7cd710fbf30708b70773/httptools-0.9.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6ff5f0ed70783dcb9562dbd20edca51c3d4d277f128223709e3da6b75986d1d4", size = 517110, upload-time = "2026-10-09T19:55:05.011Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/7a/9f165817c3e27df9098f3d50a675417d8721253f1073434f48a3f9d9a6c2/httptools-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0f537e5e8152e8d9cae82804024790cb973061abd3b7ef8f66f46e2b5c7bb51", size = 459198, upload-time = "2026-10-09T19:55:06.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/20/b93279e334946c359d39aaf405241c6fd60f9e60da709bc4156731a4413c/httptools-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a7f1df31829c258158be01bb04eb668c4fba7df1ddf2262131a972962e651b6", size = 502996, upload-time = "2026-10-09T19:55:08.733Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/c9/ac3657943d40c5a9949b72565ee03151e480fb18c062c7c13c0c0276df6f/httptools-0.9.0-cp313-cp313-win32.whl", hash = "sha256:714bf348f468532d86bed670837e7d5ddff3834dd7f5d3c08066da400c86f088", size = 85878, upload-time = "2026-10-09T19:55:10.275Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/69/d23079cd4bc16d11e49c3f51c2540c018736f26701a2a73183cae9255a1c/httptools-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:805b0f2618e5d4c3e28f45b731eb1a0539691ae4a2f97b4ce014de0bf96a1ff5", size = 91549, upload-time = "2026-10-09T19:55:11.701Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/ed/5ff678a774b721f054c095f04d84fc536e7369ea4f4c9af3813a518d95b6/httptools-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:bfdabac0c6d3d6a5be8c2a100a001c92c14a39bbafd5999545a675c493626e64", size = 88043, upload-time = "2026-10-09T19:55:13.046Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/39/0965023968452245ece67b161adbf7c5652f8d0697ac69312f9d21849411/httptools-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a4050a651e1f2faf05eb028ce9f2168abbcee9e24b209f5c1f2eb96d8c569e4", size = 118142, upload-time = "2026-10-09T19:55:14.491Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/39/a6ec662d81059e505e953af709797038e83e489014df721e506f4fd0d3c5/httptools-0.9.0-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:130635fea6e611a6b2026120037965ddb88b3dafd11bb64e264b101a70a76630", size = 113943, upload-time = "2026-10-09T19:55:15.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/04/4ecb7251a6c55bef61b157bb93fd44678943c35702a5966e4d5ebda2d450/httptools-0.9.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:18d800aaa2d6bff7d889df810d1b19a5fde72b1f6c0ca96e8d9f28a692fe5460", size = 514397, upload-time = "2026-10-09T19:55:17.48Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/5a/0c26c98ee06f0f39608de715e7ca868baec942171a77feace5a0ba548ca6/httptools-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e45def4d9ce7073e2226535572442d9d6efb4047c7a5fd8960807e877ce70a", size = 514383, upload-time = "2026-10-09T19:55:19.221Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/6c/0f85d4f1f579c49aea6e4946dd304e9f33a680382b5117970ab887885bc7/httptools-0.9.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f6da814aeecbc6cb8872d6d3e85ed16e8ab1653f9557cea8658725ce212348a", size = 534993, upload-time = "2026-10-09T19:55:20.992Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/32/97a836533b7bc9e269fc6d075c2d27669ca9786bf43f229158b9b4b15021/httptools-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8e1e037bb57dbc549c6fe20370b763ea74bdb09413cdcf857e4f14d9e4e2fb13", size = 461494, upload-time = "2026-10-09T19:55:22.785Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/cf/a2d5e8dc3bad9b0b966bb546170234b4614275346cccbc01f6cdb6fce3b3/httptools-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cd3e55223a77d6e08d5730ebacb4930ecca5d2ce7c57e7ba10833be7e52903f1", size = 495859, upload-time = "2026-10-09T19:55:24.9Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/d9/7472c4ca2aa1cfe6d0f9923380784b034cb77addc88589f2e5c92fd3b4df/httptools-0.9.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:beb2c8a34cc90fb4d862b7284eafdb322030d6a8b2ee5eb6a744f84205beedc3", size = 516303, upload-time = "2026-10-09T19:55:26.84Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/dd/f9be002ba859714cc306fe86204b7cb12bac091be66a7e23d7bb25d259bb/httptools-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0cc339a807c156d840b54f8bf050ba0fc265eb81692c24bca8535b52fbd797c6", size = 458188, upload-time = "2026-10-09T19:55:28.571Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/7a/ed8bb5344071afd12c87e57e8839fa65abc3895b92a5d065be79ecacb919/httptools-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b6ee42112d785a913dd63ec0335435a3dddbea5040c151252db815b0095cf066", size = 498356, upload-time = "2026-10-09T19:55:30.301Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/8d/3f1390c901d4a266ad9d5b988c47c4883e322e6f6cc021c592b9a050fb19/httptools-0.9.0-cp314-cp314-win32.whl", hash = "sha256:d1e329a1866981efe0201d05a374617f6c6cf14434a501d78ab22793d1ab1fa6", size = 88479, upload-time = "2026-10-09T19:55:32.071Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/05/7de70a4eea3b52d31a95fe64eb5775ccdead01e4913e4741b4424e9ef180/httptools-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:edd5aa045fa3cc57143db018dd32ce7962bd5b525d05230709015d7e570100aa", size = 94809, upload-time = "2026-10-09T19:55:33.423Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/79/7f6c354a8f8f74381fd473f365d2db3cd976ee8d1422b8dd7455dfc52b62/httptools-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:6ff0145b34610e57c9fae20df4e133c8d54266447387de6fcc0bdabfe4db4569", size = 91508, upload-time = "2026-10-09T19:55:34.764Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/0c/f9e8148ca684b41b4b5d0ced0860530b9a9bcb7c38bf727d83dcbfea42d0/httptools-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:80eae881cfb69383303e9a4d7961a478025b89c24f38f2e69b30c516fa0d57f2", size = 123684, upload-time = "2026-10-09T19:55:36.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/54/3c1d910e8f0bc9ee0ba7867b687e3272c8ae4a7da2df2fbf1b2bce77f0f9/httptools-0.9.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:b2ab3aad55d75d0b8df8d8a1b5920baaec9b161112cd5e95984848b4d2cd3dfe", size = 118378, upload-time = "2026-10-09T19:55:37.851Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/ce/3b9694880da927ae69b5629b8847cfe73d14584be2aa974a92ed2675b7da/httptools-0.9.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db735a23ecb0f0450d2b24e0a05fb00a8a35c9db172919c4d3e023e7c7ee4c9b", size = 591295, upload-time = "2026-10-09T19:55:39.501Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/89/1ff2835b6adf5c08a477d3a199e72b71e7f26df55ceaaed7d7364d745a1d/httptools-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:995b52f7c260ac7023640221f27472303968753cb6fc6fce1ddfb0e9db59a398", size = 603709, upload-time = "2026-10-09T19:55:41.404Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/40/4f59a0d9dca6d60002e7cb5dbf1441b558ced5a65b5b4131d57cbbd7c806/httptools-0.9.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3af4e45ff455fce5511fdf2653c1ce428ef09c56fe37a83eb4d924c2d474f31e", size = 607379, upload-time = "2026-10-09T19:55:43.119Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/19/381d444a3ba704cd5c67eb4617ae7a08e920a8239c688f23ba0de07a270b/httptools-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ce8e723b4637034b76f5382a30a6b725518c332273e8d62a6c7d46e90837c947", size = 530031, upload-time = "2026-10-09T19:55:44.85Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/c5/c9ba7758bf266240f598934510af4a800edafd9c8eb1fcf15feac0427063/httptools-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:465bc1526debf53a3be92022a16ca0c38f891ea3b5c1587af4f52e44020f8a07", size = 575129, upload-time = "2026-10-09T19:55:46.536Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/87/c17f3a53616a3849681f7c8e913ce966487b95038504bbb035c38f5f2fbe/httptools-0.9.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:8463b34ebde3f000627e9dbd8a545f995ad49fbf7ff9dd5abc0cd507da98a603", size = 582996, upload-time = "2026-10-09T19:55:48.545Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/e3/cb33ba1348ddfa5853f96021f4c38674ac383b92c944492cf7638bd6bfd0/httptools-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f9489c1d87160c126f73b004742fe8654fa1ce37ed89e9e01330a1c10aaecde4", size = 526150, upload-time = "2026-10-09T19:55:50.261Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/00/af0e2f33ba5be60803a492ad377e798714d0c970e76015e313849b351ef7/httptools-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06bfe7fad972a417269d8a5fc53b87e4eca970354abf5e9e24336fd06d64292e", size = 571731, upload-time = "2026-10-09T19:55:52.422Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/35/e67e9c9dd3da036ebfcbd273eec44bd39213f952d638858b09b9f3ecaf3f/httptools-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:c42424213c28804f8d0e20f5692106cfb57bf72e1dbc4092b8481fb2f9e4c707", size = 94622, upload-time = "2026-10-09T19:55:53.982Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/5c/af620c73de59b5f3d431ae778c7412d30bba7bf56ca8b4140107a8ac0e54/httptools-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bb1533541c729ad422f870a780d8b4af924f9817d45b5f580390418cda72eaa2", size = 101934, upload-time = "2026-10-09T19:55:55.417Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/90/fc6019b5179d13007c6c3039346ea2696cf2e94369d6ca96e57f23b01989/httptools-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6f9549ca354a1d6d6167c458a1f1b12147726b968f02dd64b6a5801dba91ae0f", size = 97211, upload-time = "2026-10-09T19:55:56.878Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/77/e226b16a2f291f2a4ce25a24a3297e98749d80b8a713b8f3b11d8a82e904/httptools-0.9.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:d3906b5c549ff2ad2473cb711e1fc65d76715c2726a402108fbf55eab6c6b49d", size = 117817, upload-time = "2026-10-09T19:55:58.295Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/08/050ad8985ec34064e4401e6e5aeca7238685bc218eaff20025f7c04b0723/httptools-0.9.0-cp315-cp315-macosx_11_0_x86_64.whl", hash = "sha256:cb2bb3ac0af7fdab2311b895c9eb95442b45deb14cc949b9e65545e74aa0be69", size = 113669, upload-time = "2026-10-09T19:55:59.915Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/0f/af812488a4963ce59d97b73a00c72bba49f5eebca1a13ab6f114372b5e82/httptools-0.9.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:63d38e9a9a10a20fb57593742e63c6b1e78dd7f6ef5472de8e0b1e4cf4f3db26", size = 515633, upload-time = "2026-10-09T19:56:01.529Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/6d/73c987b84e0d02fa6c4109c7ce6ea00518d0aa3005fb92b75553ffd5ddf8/httptools-0.9.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eae4e9c7a0785a1a715de0a74fb822ab40084c060f444f18f075d05e322aa7ef", size = 516049, upload-time = "2026-10-09T19:56:03.327Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/f9/74cc01fba5a0ea05501eb39eddba4baa00c10e4d1caebdb78f23eaacafe5/httptools-0.9.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0adc974916efe1fbf89d0363a86dcb2c746727643e362ff398de1a4b50b6bc77", size = 534832, upload-time = "2026-10-09T19:56:05.068Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/a2/a7bb90643c059e8136c2a5fdfb0d7e1a18b2c5c4f1a78f2de14b1303184d/httptools-0.9.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050f84b7ec46a6efe0e5f521cf8729e3397c1cef4384f62ed8d5d68ca0045776", size = 466489, upload-time = "2026-10-09T19:56:06.757Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/19/bb3f18e05cbad9628e7f1254176c475e05ac79c72697ec7c144fc2cc877f/httptools-0.9.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9b4da5789d7cf576c7e81f0088c632f6ee3786d87d17f08e90e703c22ce15633", size = 497146, upload-time = "2026-10-09T19:56:08.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/e6/90e2433d7a947bec66a5ad22e948626a26672ff62aa3ebf949899f687a3e/httptools-0.9.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:f78f7ae1c2e5aabf29583fc0d302d8081a663776f84578025662eb6f5d63a921", size = 516153, upload-time = "2026-10-09T19:56:10.415Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/c7/86373edd9d800eb723b8b68d3fce0e31d3e3211f9d7b0eaf8c3deadfada0/httptools-0.9.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:b2cc6991f16f6d666d48e4b57318104e7b29109e32e2f6b86e9d44c4e6a27f4e", size = 463141, upload-time = "2026-10-09T19:56:12.406Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/46/8dc41d9ebf78fa56f609f251ed8ac5a9f66513b0ce712040bd7ada7b19cc/httptools-0.9.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:dbc9fd1521e573045d71b6afab7398439c5cc259e8cb9d416fe62d485c4899c6", size = 499125, upload-time = "2026-10-09T19:56:14.109Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/41/38db94fda8b266dcde50722a4fcef825b189380a220e02c682518bc1b430/httptools-0.9.0-cp315-cp315-win32.whl", hash = "sha256:34266cec8c1d4e3e91fcca7efe38971d6bdda64a7944f2a46ab576da15173680", size = 88370, upload-time = "2026-10-09T19:56:15.873Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/cd/347f12eb16e20972dcdacbca907f2c52d72a36542199a5bf3ca342c92098/httptools-0.9.0-cp315-cp315-win_amd64.whl", hash = "sha256:b5a3f5f70967a1aa2bc47fec42a1e19d2fb38c61700e3ee62b63a4af4f4fd001", size = 94617, upload-time = "2026-10-09T19:56:17.257Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/08/086ba2f53989d504a05f4669b03673a04fc72554bc37d4696c3c6132be75/httptools-0.9.0-cp315-cp315-win_arm64.whl", hash = "sha256:e0acbd474d0af4afacc6e66c4273f8a19e25f8af4379fc816388095ea6b01371", size = 91358, upload-time = "2026-10-09T19:56:18.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/3a/9ba59ec76d45bf8eb7ad3a18f2c6e9074fa4ce5cbbd3900fffb8d840f9e7/httptools-0.9.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:02bc5b3dcb6394b9d825fd62a7bfa0b2943063a3c89abc4492ad45e334a20eb5", size = 123063, upload-time = "2026-10-09T19:56:20.023Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/2d/49eb389bda75a8ef0d04bf025dfb8412a3646637051c8a88bdeea700e343/httptools-0.9.0-cp315-cp315t-macosx_11_0_x86_64.whl", hash = "sha256:fc1a4f9d18d32a6e0a0a0a382986a60a2126f5144dd08715be7adb8df18e8a46", size = 117328, upload-time = "2026-10-09T19:56:21.439Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/6b/2d6439378fd3d1f9c06272b35d61f4519e2d9bf9967611df069fa6c23044/httptools-0.9.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df3867518b205be3648e2fbd522bf380c851b5c2500588047505afdd786b6669", size = 588680, upload-time = "2026-10-09T19:56:23.056Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/65/3fb50e861bbb6103ca58fd88b4127d346fc909eb9f06d250455033a3f698/httptools-0.9.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26e1d9629f3bf70d23f0d22238152aec51c837a7c9e384cb74f356fdccad7eb3", size = 600031, upload-time = "2026-10-09T19:56:25.216Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/9b/40d33d4098fde007845804b1c923ddf5a27fd48aca1c8080bdbdac6c16fa/httptools-0.9.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:050f7ab098121873c8f13e35857f97ab60a76185c8302bde9a384939bb7c3b96", size = 604407, upload-time = "2026-10-09T19:56:27.04Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/37/472afc9000aca3c7dd61a9b8ac6f3e2765900e3614f8d7f13e772c9c5438/httptools-0.9.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8d90d10e9b6594c28f27896a68fab97fd784c43804e9fe419dab8e8dcfcf4b02", size = 529459, upload-time = "2026-10-09T19:56:28.944Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/f9/9956910fb1d181578249cd2cc966c0c46ad3c558b43ac2b79af50f94589f/httptools-0.9.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b928ab0ecaa664e8caecc529dcb8bc881b6b35bb2b74bf9a39ae25f982ee8812", size = 570845, upload-time = "2026-10-09T19:56:30.602Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/8c/d1c160a3cc2c18e41a6f763c3aad979530dfb295039449312b8814e19753/httptools-0.9.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:2319858018eedd0c0b2f950a620413c0a9d1352607be4267eb28209eca8b1e3f", size = 579194, upload-time = "2026-10-09T19:56:32.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/3c/3f7cc49925928a8c82f4141d504b8b8c2901c4b35cb88800211828312561/httptools-0.9.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:931f45f84e15daafec5f82cc92e6710569e1f50933f3253d206eab4132bec678", size = 524950, upload-time = "2026-10-09T19:56:34.103Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/98/8e2154e99b8e8818fad3e6c5dd7cf21c050f6314b1bd8072e8dc29f49eb5/httptools-0.9.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f67db0ba2bedafec15b8e5330d40da1e1c7921559fa715af021252bfef81a6f8", size = 568603, upload-time = "2026-10-09T19:56:35.876Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/a3/86fe9fef3a1bfab5db62262f8880c294cbf8a8d94cffe2a2aa8b4aeed40c/httptools-0.9.0-cp315-cp315t-win32.whl", hash = "sha256:2095207b75a83c9e947346da9c127fb7e4fb29f41589df2643764f06b750989c", size = 94042, upload-time = "2026-10-09T19:56:37.441Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/4d/f2d88782251467325a62ec4ad704249bb1b09c21aacb997181a9f4421f30/httptools-0.9.0-cp315-cp315t-win_amd64.whl", hash = "sha256:bca180cbe84e4fba7807eb408a8655295f697928512324517e30a091ede522a8", size = 100837, upload-time = "2026-10-09T19:56:38.831Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/4b/5e96c4e0d171f959a0064971c3fced9cea5a19e5fab7a8e7d57aceb80506/httptools-0.9.0-cp315-cp315t-win_arm64.whl", hash = "sha256:4a4d8c2c7e73ba5967be74d7c3a5ff81fde815ee1b48d9c5c0f14de8463a847b", size = 95947, upload-time = "2026-10-09T19:56:40.562Z" }, +] + +[[package]] +name = "id" +version = "1.6.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.6.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/1f/c23395957d41ccf27c4e535c3d334c4051e5395b3752057ba4cbaec35c56/jaraco_functools-4.6.0.tar.gz", hash = "sha256:880c577ec9720b3a052d5bc611fb9f2269b3d87902ef42440df443b88e443280", size = 20837, upload-time = "2026-07-14T01:28:02.544Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl", hash = "sha256:99e3dc0060c5cbe8fcd1cdb36258e2a65ca40f1566b2033b12abb1bb44dd3c30", size = 11677, upload-time = "2026-07-14T01:28:01.59Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "minio" +version = "7.2.20" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "certifi" }, + { name = "pycryptodome" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/df/6dfc6540f96a74125a11653cce717603fd5b7d0001a8e847b3e54e72d238/minio-7.2.20.tar.gz", hash = "sha256:95898b7a023fbbfde375985aa77e2cd6a0762268db79cf886f002a9ea8e68598", size = 136113, upload-time = "2025-11-27T00:37:15.569Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" }, +] + +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + +[[package]] +name = "nh3" +version = "0.3.7" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/2f/022b27146d52d24b1b353b003359134788ecbcd6fcdf6283adbd57c0fbc8/nh3-0.3.7.tar.gz", hash = "sha256:71860d01c16f4d8c72e334e0674beb2b0899dbd0bf760de18932ef4390303848", size = 25662, upload-time = "2026-08-23T14:26:30.728Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/88/b594f0e86856b37e182fb663283da419eea6424972506e640e890885467f/nh3-0.3.7-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:91a4dab4e94d9fc54b9f67b1adfb23e81fab7ab43f33c3b8c97be9aa38f789ba", size = 1471147, upload-time = "2026-08-23T14:25:55.259Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/60/847a21339f095c4d4c655af31fa2d18b174585bcc210709facacc7ce205c/nh3-0.3.7-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eae64328e46a25785535afcb6885b6f182ecaf5ee8c88f8c075422db8aacc65b", size = 820463, upload-time = "2026-08-23T14:25:56.803Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/7f/1a103e00aaf5e59f2dee4c2709aac609bb2d4bb74fddaf0dcfade11ed87b/nh3-0.3.7-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4968fe8d2db97c6f047659bf46a449fd8ec377f44ebf3e0a1b96c0d3a333ae32", size = 861456, upload-time = "2026-08-23T14:25:58.087Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/4a/e9c436089a0c80b928011ead0efd156aa7639a19b6064ef58dcedcab8369/nh3-0.3.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:be53a4825585f701955cb9baf49f478f56eb81e20294329fe4bc689dd5dd81fa", size = 1023930, upload-time = "2026-08-23T14:25:59.465Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/5c/aa1468e3e281e78d2b3b7d762ccba59f681af355e971dbd255d5903f7b86/nh3-0.3.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:94fd6e59553fbb9ffd8ba71bbd5a54e3126ba01799a097ae30d5341d750bc6ac", size = 1102614, upload-time = "2026-08-23T14:26:00.869Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/9f/57d186d9d3dd38905dc12dddb3484406cdf6aa0b1ce33639a2d277d4ee1c/nh3-0.3.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:18f4278ecd157d43cb35acd5aae9f35cfa79f546b4922bd86536adc0f6312102", size = 1059915, upload-time = "2026-08-23T14:26:02.388Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/53/097a5ad0b34b15d67a472ef849165a54209fa5fbd3e639801c6fe439ba28/nh3-0.3.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:808def0c8c07843e6e50dc84f532457bfa2cfd17417b219a5d9e7c773709331a", size = 1047402, upload-time = "2026-08-23T14:26:03.897Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/a7/c57a2c70534418310889a65ccfac3525e62f0bc0a8613225903403755ce7/nh3-0.3.7-cp314-cp314t-win32.whl", hash = "sha256:874b7d67a067bd29a59223f6270fc30da4edd8e6d87fd219fc93bcbaa662c946", size = 619895, upload-time = "2026-08-23T14:26:05.105Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/b7/efda1d0a611d940bdfde6893bde1ea6b7b7d48c31273aea48e35b822fd58/nh3-0.3.7-cp314-cp314t-win_amd64.whl", hash = "sha256:614dac4a4c36ad084e78447d16fe898dedd762e354a7ab9cda2984e82f67883d", size = 633456, upload-time = "2026-08-23T14:26:06.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/18/3ab564595cb88196f50d26e163ed0fd2acc731ab26ac615df91981885887/nh3-0.3.7-cp314-cp314t-win_arm64.whl", hash = "sha256:157ec1eb7a62f3d9a7badb8d82d89aa810e3e24e097eedfa481a25d0c8a99877", size = 611003, upload-time = "2026-08-23T14:26:07.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/0d/c257754bf57f829f307aa226bbe136d3a1356b5a0d08324c7b6bd2a8aacd/nh3-0.3.7-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6c3aa50eb26e9228238271db9f983cbc3b006dfbfeca2d4dc34c33ddc6ac5ea5", size = 1493959, upload-time = "2026-08-23T14:26:09.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/42/a687e7091928806e514f89fa2666f25ec9bfe0a902fc4402b25e51ce408b/nh3-0.3.7-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f266d3f1b3647449923a8e406524632220dd5d8b647078dfe45b885d33d10479", size = 859615, upload-time = "2026-08-23T14:26:10.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/05/b0e6bef633549a23347d5462aa288fcc42381e7918482062ca3cb456242a/nh3-0.3.7-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8fd1ab205258b29254f72db377d99e2c96aa7653ef3b015ccab0420b094b506", size = 839872, upload-time = "2026-08-23T14:26:12.037Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/40/2a0921d45b20828708bcb56887e47dcf8cae13818de5bf9a01308d348712/nh3-0.3.7-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:19f288c938ec6eef1f5d2c6cab47838e71fef8097e1c1233802be5a6230ba086", size = 1091325, upload-time = "2026-08-23T14:26:13.34Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/d1/9d70e0e418a48280ec0ddc6c1b08b4b1136ebcc31a1625e57ff5c665fa51/nh3-0.3.7-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de2b2aab32ea303405debefdcfc58043d3e635fa3f67b9eb140d2b0e0c0d2563", size = 1042482, upload-time = "2026-08-23T14:26:14.667Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/a7/02dd159d4e71f98607d8d4249cddb7561e77be1a8e4dec77d76e1b68fc99/nh3-0.3.7-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7279d43323a25225df23576af6594a16693f61431170848b8b2ac21ad4f174", size = 946868, upload-time = "2026-08-23T14:26:16.094Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/ed/c5510c615dce55b6fcc364aa1838142f938beed64f5e4927490dfcaf4405/nh3-0.3.7-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70f5ac8626e899a4bab0ef74ca2f5bd602f49c7b739e6e5026b4afc6d63dac42", size = 832161, upload-time = "2026-08-23T14:26:17.272Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/e3/3212c1a5b5745245d7f18885207bbddb34c56075f34dd682bd539aad55cc/nh3-0.3.7-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:5ffdfcb9a686ffb12765376bcfb6b5b55728516d3c0ee317d29982381ded3df8", size = 849791, upload-time = "2026-08-23T14:26:18.498Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/64/9e36594efad6c290de4240d02cb2bd80c339a4ab1c4de66e599ffa6d9d81/nh3-0.3.7-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bc42bb1193c1e28a1e74c2cabaca178e118a7103e8832699fef8a2b3e2496493", size = 875473, upload-time = "2026-08-23T14:26:19.908Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/0c/1a8985fd43fea5530c0ac890b6f0b423770ee72f111b70b7a77f2dec243a/nh3-0.3.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d56e76bd3cadb09b6b0cef364850811663734b348a25f5f587a2819c495367bd", size = 1036463, upload-time = "2026-08-23T14:26:21.536Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/5d/891e533b716cf00df76ad0ba6485dcfd14d59a6430a3cc99057c4c04004e/nh3-0.3.7-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fd4a70efb45d5372174f718878eb7a35c12677626a63b2f103b23b833457dcac", size = 1116029, upload-time = "2026-08-23T14:26:22.907Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/e5/ae8c0782fce74fb6fcf7234bb3d4017f37ce181b4f9d29369eab21c50a04/nh3-0.3.7-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:15f5fbf090f5c88d61c820e1fc1fceecb6520cca9fe85649c06b57ef9dc9ff62", size = 1076589, upload-time = "2026-08-23T14:26:24.302Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/a4/c3423351e8d864ad756e85e15f0c01433361f14d34e4ed156482c0518f2a/nh3-0.3.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6698a822132beedab80f131c08d8d0ac5a178ddeb488d02ca4b67716ecfac7af", size = 1058871, upload-time = "2026-08-23T14:26:25.674Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/6a/478f153f1d7c0baaa3d1e8bb5fdcee3a6235f90fe44ea969a9d4e2b8c47a/nh3-0.3.7-cp38-abi3-win32.whl", hash = "sha256:6e4280115d44c3b278eef712a86748c1a723105cd79feec46952383117ab4e59", size = 630729, upload-time = "2026-08-23T14:26:26.932Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/b9/34433ccb1f0fe6968dabbb7d4bf5721c6221878ef07832748c06655a6a80/nh3-0.3.7-cp38-abi3-win_amd64.whl", hash = "sha256:618e3059caf41ccdf5dcccb3fa9df4cf6e4efe23d1382a8bbfca272a8a4f8bfc", size = 644462, upload-time = "2026-08-23T14:26:28.294Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/70/e140dffff6e808dc6343598df76e7e2407fd0f581de3524c75fba2e0cf24/nh3-0.3.7-cp38-abi3-win_arm64.whl", hash = "sha256:f04b7d333b27f13ca439da3cf1c75c2fba34f104969f6ce4ac8e7079699c2f4a", size = 621867, upload-time = "2026-08-23T14:26:29.547Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.45.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, +] + +[[package]] +name = "orjson" +version = "3.13.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/72/380b97dc45bd162d23afe5194721ef678d9eac7cfaa549fe2873f7f0a518/orjson-3.13.0.tar.gz", hash = "sha256:d1de5eb04485110c5da4c657e49168995d55e076b1ce60f1a042e254f4186c4f", size = 2732604, upload-time = "2026-10-07T14:09:25.719Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/17/ed65f84ed5ed6a1e06eb628611b4172e7480fc4ad92594856751a6363cac/orjson-3.13.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb8644dc6d705e1269ed2842bf4dbe2b4e50d670de503bf79d5cef3a5148a4c7", size = 223063, upload-time = "2026-10-07T14:08:21.979Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/4d/9332eb96d2e379384be0f211f543835eebc81f460c9403b84abe1294c431/orjson-3.13.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:6ff2a2c67f35202f7d823753d38ad371a9b7fc297567cdfff4420e763cb9f6f8", size = 123364, upload-time = "2026-10-07T14:08:24.026Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/06/558456b7da27e974a8c9ea09117b07119f6fa131cd62b8b9ecad9eea94e1/orjson-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:65c4e0e106ccc7265b488385659117a6805c37d042f737558ecd68aa0c67ad8f", size = 113199, upload-time = "2026-10-07T14:08:25.476Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/f2/1187a9c09965620348262ec0f406868f6d7c234b2e9b5ee51020bdde5748/orjson-3.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:fbbad6b9b1da43f25c1f5b20cd5a268e028a2fc95d5a8d1ade6059973bc71584", size = 130329, upload-time = "2026-10-07T14:08:26.877Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/07/5d1a151bc11600434fe799e73abfc6a4d463d02e149a20e47c59d3a985ae/orjson-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d895cf7bbfd50ef34bb63bb727b14514f259f3e3f8dd010783bd38e864c6e", size = 129072, upload-time = "2026-10-07T14:08:28.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/8c/bb07c368abbf4021c4cd01c12edb526e00090f7f750ff1b88da6e6b6c7a6/orjson-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceadfd314bd238f584fc229a4bbaf0e573597e7a026dec5429fbf29fd66c641", size = 130612, upload-time = "2026-10-07T14:08:30.041Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/8d/4b66d19619ed344ac000ffea7c006477d0061d580646e736ef0e203759e8/orjson-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b74c30e56346aad067937d766846ee74c231d1d18aad3f324e9b9261de3b2d5e", size = 134632, upload-time = "2026-10-07T14:08:31.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/88/f8221f6593e37eb26ec4706e185b9ac6f38ff0c8f7bad5459844031ffd2d/orjson-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4329c19b8a25693f60a77b867c9d2a3ab637b20e36f5b7bea7f5acb492b44b15", size = 126807, upload-time = "2026-10-07T14:08:32.914Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/9d/a1ca7321eeafd7d72e174cdc388cc96301f41516d863e7b1f64f0a1735be/orjson-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:b571236d8393edcd3236e07423f762bfcf571f852aad667a3bce9e7b755e0790", size = 121538, upload-time = "2026-10-07T14:08:34.325Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/a0/1f19b4779c910104370932fceb9ed436b47ac077f297db74008062525c04/orjson-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:8594956a75223f657e1e68c568c0eeb3dd145f02cd6b78a47fd9a8095dbc4eae", size = 126259, upload-time = "2026-10-07T14:08:35.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/56/f8ad2546150168858c16915c452b00eecb79597597524d1ad6ae14ad4eab/orjson-3.13.0-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:64e8f345048d988c8b68d3882e5d41028fca1219a9939b32e4a77be34c8ae8e3", size = 222892, upload-time = "2026-10-07T14:08:37.495Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/19/725d23160b2471a3f27026c55bb79af34687652d8be8f5f583cee5dcd42f/orjson-3.13.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ded33b972cffdaf4ca0ac917338ab61d2bb10d68987dbcae641c313fbfdbf499", size = 123319, upload-time = "2026-10-07T14:08:38.989Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/08/e5d81a00b22c73dfcb60d80da3bd92d5a7684346593536565f184dbae3c9/orjson-3.13.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:45e34deb3437509f4ec9888dd9ee5dc426cfe21be10f1eb4ea3a9e4d33034f9e", size = 113196, upload-time = "2026-10-07T14:08:40.383Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/78/fda6117c69a43e470b1e9dff38dd8c5f0bc6fd8a47e4d4561ab023039335/orjson-3.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9825b954155b345c4759f24e5f8d652b9aec2261bb5d4e1abe06bba0a1200535", size = 130245, upload-time = "2026-10-07T14:08:41.878Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/31/d0cfebd456defb234414795ae7599696bf124843dfe077d0c9ece0c93554/orjson-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b081f0e7b600ff24513dec4ca75507fa05e904607847e386e8310d5b7b96b6c7", size = 128981, upload-time = "2026-10-07T14:08:43.716Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/46/f8d83189ff5b7b2ff225a58c5908618cc4e86afe09e65d17a30ac68c9da4/orjson-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbed5f4c4b88d94bcc36115f4c3bb3aa25da1563a5c3328aa3acebce2b083040", size = 130370, upload-time = "2026-10-07T14:08:45.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/6a/d6344c305003ea826b3fa0482645a897a3cd6d477ed74e1fe15d3322cb23/orjson-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9b61676116f755126b90e740a9cff36b91562f47ec330056cc88cc3b9f02f4b", size = 134595, upload-time = "2026-10-07T14:08:46.63Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/52/d73fa44f88d53e02d10de1cf77c16ed13204ff5bca47e1692da6b406619c/orjson-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ef75ed7e81dae34a3649f82df52cd85f9ac839a7d6ec78ab355b33b3b27ef7f", size = 126513, upload-time = "2026-10-07T14:08:48.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/f8/bcfc50b4ab851c4f9c0ee62f52bf3b28f0bcd0d9fe08e0ad98d4585148db/orjson-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ee06e53b998c71ce3eb93b86222912fdd9dcced685ac64d4525d36fac338ea4", size = 121371, upload-time = "2026-10-07T14:08:49.549Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/7a/d6927845712ec2b1e89263cd12d7203531db185dbad67f914226f2fca156/orjson-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:89efecad02515df7f318d0613b5dfd6d2a1acd323a2b8294712789a715945525", size = 126134, upload-time = "2026-10-07T14:08:51.118Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/10/98b5a3cdc086abf78d8cd20bb0cba124485d4b6a745722197bd209d967a5/orjson-3.13.0-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a7bfc7db961c7d96cb75889dc6a1e4ae1e91d87ee61da564f582bd742b8dfeef", size = 222889, upload-time = "2026-10-07T14:08:52.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/7c/7728c5280ab5202f4891ff4b0b96e2e1dbd5520dfee53edf083c54409a64/orjson-3.13.0-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:91d933e668ff0ffe164d7c2daec36beba6d1ce7fadb71538fbe142a71f8a1e6e", size = 123312, upload-time = "2026-10-07T14:08:54.25Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/a5/d9a44321e6f66c0f64b45be587395f87ad94cb447bce7d92286f6b97d46a/orjson-3.13.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:6c8bfe728b81b0fd58a3c7f3f9c5a113f87f2992c9948e0f28707aafd737c0bc", size = 113146, upload-time = "2026-10-07T14:08:55.803Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/da/d95c80d413f288feb471e16d82e5c1512d2439728e3bac917d058c31f098/orjson-3.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8e05549f3b30f9d8a8e28c5aba11cc2a4b90b90961ec685ca58444b0815fc09", size = 130348, upload-time = "2026-10-07T14:08:57.31Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/0f/36fdfb32ad1852997bac00e3ce52c7888d8a1094ba9dcdcbb22fcc6b953a/orjson-3.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c749ab3ac30b5ab1ffb7677f8b92eacfdfdc5260210baa398f845bc3714c05d8", size = 128971, upload-time = "2026-10-07T14:08:58.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/de/a82acf93bdcca0c79ccff25ef0c6868d24ccbc2e72f21fae39c8cabce4f1/orjson-3.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58a9619d88f8818d9ab6b39d70d203789457ba13c1ed5d274f33ce9ae7e81a36", size = 130359, upload-time = "2026-10-07T14:09:00.412Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/ca/2bc4f7697cb9f6897bf61aca11803df096a5d971bf69ef5538b243bb1fa8/orjson-3.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2715c4808d1571029ed18fd07a82140bf3ba7def0dc89f8d015c416e3649bf87", size = 134583, upload-time = "2026-10-07T14:09:02.047Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/b3/12b1af9b87ff9fa0aaf4e5724c87672b30bb5de76f275f7fac64e8219c1b/orjson-3.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08bf722f923d2100bc5e5a5dcf72c656db557049c1bea26582fdd5dd9d5395a1", size = 126500, upload-time = "2026-10-07T14:09:03.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/ea/cf257fc8a7f4b18f5677c22b3a9673a1b51d4b7161f25177ed389b76560e/orjson-3.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:6adcaa85d79977659a448b4123a88eb33511a11ed2db243535ad7ea88a6668e0", size = 121378, upload-time = "2026-10-07T14:09:05.375Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/0a/9f4643f849e9918eab11983b83928af3aac14bedb04002e28e885ee1936f/orjson-3.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:83705c12b4afde10c62a5dd3fe6fdb21b7900bd0dcd5af1c85612ae94d0ee590", size = 126123, upload-time = "2026-10-07T14:09:07.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/15/d265f2b556c0c7c0b30ea830316d6e5af5b85dde08f234a1ebed60fab386/orjson-3.13.0-cp315-cp315-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5ef4d4157392a0439b74f7e49e5636b4ea43d9616bd0884effc0195fffcaa2d5", size = 223305, upload-time = "2026-10-07T14:09:08.84Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/97/781be8b80a33b8171b3f5acea941af47182c8b4b5827c2b7c3fea706f21c/orjson-3.13.0-cp315-cp315-macosx_15_0_arm64.whl", hash = "sha256:84d87e322e1674408f85adea63f11aa19201eba082755aec20ebc217f493bbd2", size = 123515, upload-time = "2026-10-07T14:09:10.792Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/68/011bb98fa7da7b430b363db1bb7ef9160c438fc5c43e7468fb593c220037/orjson-3.13.0-cp315-cp315-manylinux_2_39_aarch64.whl", hash = "sha256:8c2ac5c09b017c484df1b4c68b2cf250b4e8ba08204cb58e7cd6cbbc71a9c902", size = 129222, upload-time = "2026-10-07T14:09:12.542Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/7f/d96fa2aedaaec14c095ea9cd48d2158fdf33c0f4fd6e7a598d899d536b03/orjson-3.13.0-cp315-cp315-manylinux_2_39_armv7l.whl", hash = "sha256:51d11525bc3ca736fa97ce4e4c7da9999cc00bf261522bede43b4e7531bd7965", size = 113152, upload-time = "2026-10-07T14:09:14.059Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/2d/ee77aa685c54bd920a1f0e2936986b46269adb0d72bf5098c2c694dbeb36/orjson-3.13.0-cp315-cp315-manylinux_2_39_i686.whl", hash = "sha256:ac81530647c3423107cf61c3481e91f57134e9ddfb6ef83f5150ccbdcbc3a3ee", size = 130749, upload-time = "2026-10-07T14:09:15.835Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/eb/3411fbfdad61b3f3af22343b5af7ed5c8a1679e35f442e8f1b229b33040e/orjson-3.13.0-cp315-cp315-manylinux_2_39_x86_64.whl", hash = "sha256:0526a3456db67b264c6d661b5f090077f326b6cd074d0ef53a72763595dec5d7", size = 130471, upload-time = "2026-10-07T14:09:17.463Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/71/abdc2b8c70b8d85a6cb22f404da0f52d7d712f9d49cda039a0cb1adcb973/orjson-3.13.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:dd61e64802d51d1e4f16531c64536354fc3bc67932dc0cff254044f72bf0f187", size = 134793, upload-time = "2026-10-07T14:09:19.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/2e/1c13552d8b0241083116de02b2f284ee38501ef06ebfb79893f741538168/orjson-3.13.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c5e3ccaac3106e8fa6e2f2f6962449d7c757d7b067e41b395a19d6f0d6cec892", size = 126711, upload-time = "2026-10-07T14:09:20.645Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/f8/d4ece953a519d064cf690adaa68cd389d5b64fd261726334841b32978d6a/orjson-3.13.0-cp315-cp315-win_amd64.whl", hash = "sha256:7804dd1d6161da0e53b284c2aebf20f23e78eaac617300803e1467d1828d987f", size = 121496, upload-time = "2026-10-07T14:09:22.359Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/cf/f691388c4a9bc4af7dcc1648c4b40845869908b517d7c0009d005c7d1fa1/orjson-3.13.0-cp315-cp315-win_arm64.whl", hash = "sha256:f5c05a8fee59309f537590a1ff12d3c1009c485e96a50a9ac60dd085c09d0fc0", size = 126260, upload-time = "2026-10-07T14:09:23.928Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pwdlib" +version = "0.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/a6/644f28a21cb68920d06b264e72b864ac9bff2377f4757dd4648b5110b3ad/pwdlib-0.3.1.tar.gz", hash = "sha256:2ffd1955c92190d5bfbdc5071bf7d422568b83f943453ebd3d43d031ccba37b1", size = 216951, upload-time = "2026-08-12T08:38:38.383Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/2e/51979293b2337f508597d13ee20db1ac1d43e48086b2ed7de6da213ed46b/pwdlib-0.3.1-py3-none-any.whl", hash = "sha256:944fa439198bed8ac3ea63dac76ea4ae43e2a35f4f21e9e725e82d3c2b97d6a5", size = 9272, upload-time = "2026-08-12T08:38:37.483Z" }, +] + +[package.optional-dependencies] +argon2 = [ + { name = "argon2-cffi" }, +] + +[[package]] +name = "pycparser" +version = "3.11" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/a8/c5fdbeee588bb8ada9458774f43adf1bdd30bd59157055142183e769a024/pycparser-3.11.tar.gz", hash = "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc", size = 113796, upload-time = "2026-10-09T12:56:59.539Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/11/0e6f11117525ff0eec40ebac3d313376f102df93ca44ad9e893ee85e4f89/pycparser-3.11-py3-none-any.whl", hash = "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", size = 51178, upload-time = "2026-10-09T12:56:58.131Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.24.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/75/b8a9ba9a15b1b190d1fb21e75e921934c9bcd7e63e137f96b56ed274328c/pycryptodome-3.24.1.tar.gz", hash = "sha256:3f9e74444c0ecbec7af232a95d282c74b114d53212ce075ed17b7fd7dca32bb3", size = 4932558, upload-time = "2026-10-11T19:10:34.873Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/40/f6a3d4e209bed5d7429d65753cda325c3b9e26f8334e1f9144d044237629/pycryptodome-3.24.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ebe1534c29606232c8da2331718a6051012b8ed584a3ea5f53a5e88cbf8e93c9", size = 2473441, upload-time = "2026-10-11T19:09:20.305Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/3e/34faa06f57a938807c23f6e8a92c35c70ac7797362fa85d0f3daf2847363/pycryptodome-3.24.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d09d1a9334565a35fcc5866bd4051bf20a596d385c189d783cbd4913d30678e9", size = 1640790, upload-time = "2026-10-11T19:09:22.581Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/3c/4eb2778e702b171b9b6010aa20a7ee104252911ec7633b0e14a66685ba55/pycryptodome-3.24.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:becb84847713a9109c8a7e1e2f4997419a34d1b769bd747753a6025f62f85556", size = 2192054, upload-time = "2026-10-11T19:09:24.729Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/08/71bd6555168364de83621dead0ab4e23cbac10172148d535ce3eae77db3b/pycryptodome-3.24.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0003d83a044639d3f7442bb3282db83ab8cf0b3977bb44d4018aacc2f901e839", size = 2277860, upload-time = "2026-10-11T19:09:27.691Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/1a/5fde65eb7d2a362fdbc7a9cfae00e349d272e4624671b8a7dcf520bfc288/pycryptodome-3.24.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67f6c39d36794a81a50af571eaba13838ad6740da20cfb3f227bbb5c532f72ef", size = 2183415, upload-time = "2026-10-11T19:09:30.262Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/25/6a08e306320e7755d27510258638069c2cf5e54945afa0765e003c4bed42/pycryptodome-3.24.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6ccffd6da4488319439ce9e90e694aff71631444f46fe1fbd4f7c7c12cd049e", size = 2275600, upload-time = "2026-10-11T19:09:32.862Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/df/1c92b63dd51456b372f83f2d1f7ec3ac2a4a5d995ef00b152bc5aea231b1/pycryptodome-3.24.1-cp313-cp313t-win32.whl", hash = "sha256:f9f3231051f23c3779206de45f40396d571a69eabde2905947d5e89421d23acd", size = 1790044, upload-time = "2026-10-11T19:09:34.652Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/c8/7b54500ffeb2b7a0154ce55a28cd442c48b324e1b2d7c99df65e6ce1654a/pycryptodome-3.24.1-cp313-cp313t-win_amd64.whl", hash = "sha256:03cc4a9be177c323425b1204884c1bae3195061d7348e27f6a150833a8e3bf1a", size = 1822575, upload-time = "2026-10-11T19:09:36.573Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/f5/08c3219ee808feb928bf9794679078167006059db92b2dcf1fc3340fed9a/pycryptodome-3.24.1-cp313-cp313t-win_arm64.whl", hash = "sha256:50dda0ca14d65af1a5d648847964df0709752e25b8955c8d3794a61af86748e5", size = 1757001, upload-time = "2026-10-11T19:09:38.381Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/80/25a737a814f602e11568968d712c85a2a6d147d87e62cd3f48f649648cd3/pycryptodome-3.24.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c96ad454e26aa7797d7b49094e9fabd1f1d1716231a78bb8c50dedd9052ac7e1", size = 2474363, upload-time = "2026-10-11T19:09:40.171Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/a5/ea66083f7631e3ce9cdff6b3921551f0a7e5eccbf0400b2ba7abf39e764c/pycryptodome-3.24.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f4bdc3f6b34cf9d05fce5b7ef02c48b767edf75679301f2658bc8f13f328faeb", size = 1642942, upload-time = "2026-10-11T19:09:42.036Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/37/716c716769ba57ae7a51e4a233e1b07aa41c5ca9c4b7f9e3f979992cf86d/pycryptodome-3.24.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94e88c7672b71517d6aa3fc90ec183e6318e523b5f6438be565a841491fe88ee", size = 2192055, upload-time = "2026-10-11T19:09:43.703Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/04/1f64a9c28c02a0eab1db05bc16a87ae99045c899da14f669a1329cde0c54/pycryptodome-3.24.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:848971744559908a515e2dd96bffeb3ace6a2a411cd6cf1016cf84979b409ac2", size = 2277860, upload-time = "2026-10-11T19:09:45.76Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/1f/6c39bc0b2ab02f4f920a78ea8ca99262699decbc990c8768db17e6611c79/pycryptodome-3.24.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7cc28463049657362788e05785bc222765972ca5febd7328e8d85a295d001574", size = 2183416, upload-time = "2026-10-11T19:09:47.794Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/47/399c59fc6bec65600bab07aeed6093af14958469bfae85f58d245ca68a74/pycryptodome-3.24.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:096ffa2fcaf5b98a370e58105ff9f866f5e23cca3736ac6eb95b1216775ad6d5", size = 2275599, upload-time = "2026-10-11T19:09:49.638Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/e3/95f53756db78cc035018d66035ae0bb30a2bc82ee771bb57cbbb68d32778/pycryptodome-3.24.1-cp314-cp314t-win32.whl", hash = "sha256:1c07b5d8ac5f89d7b80dbadf09e34b919f660238843922cfe060aa3f7930d793", size = 1806097, upload-time = "2026-10-11T19:09:51.388Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/41/2e31ed5bb362377148dbce0c27ea63b00523e3f3c3f863bdc01ce8353abf/pycryptodome-3.24.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bf8908252f6b3ff6e860e08a0f7606ea32417ae572c0632e136d3402cd88bccf", size = 1839552, upload-time = "2026-10-11T19:09:53.302Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/15/0b88ff928bc7480a040e4fc9357edc190e98c1e7a337269bd4709a97c1e9/pycryptodome-3.24.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ab77c93385095d1eeb89c81cfa1b47d8f1a0f8b20010b2f6083f8b692d4101c7", size = 1775661, upload-time = "2026-10-11T19:09:55.878Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/08/014128274efca5bc18ae7e4e4f5c593d1fd6d43b77bf7492b233589cef79/pycryptodome-3.24.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:558b9233ff2afb42f92115ae9b4414d08c0e567790619e878cf72947d7c38a11", size = 2474271, upload-time = "2026-10-11T19:09:57.807Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/aa/fc80df50eacea7d3fc53af3617bcce46a245691a76b0193612c9c1e28db8/pycryptodome-3.24.1-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a089e49fcaa978302447b2e63118b2b0f366a25e914c5d7ac8c30b3e5cc61e3a", size = 1641640, upload-time = "2026-10-11T19:09:59.958Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/bd/944bf1725d028a8d1c14b5ba2d3692117fc65dee2af806eca7fdc35feafb/pycryptodome-3.24.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cac508283b5a1126945816613748a92395fbcdc70044b2c0cf2151caac5cdc9", size = 2190505, upload-time = "2026-10-11T19:10:01.927Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/3f/e6a6b5d261746378a9267af50463d6aa01f88f88c98bedfd404c94eb7ec6/pycryptodome-3.24.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:93619c3117a8f14ea1267b427e465d152a66c89c3d3c643262070c05b2855aae", size = 2276644, upload-time = "2026-10-11T19:10:03.869Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/e9/3e0878e25441d0d2b5e13176b239a190e6b4e3da063bd87a49e43355cfe7/pycryptodome-3.24.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a311825b56b6d60169d75e71b68f11d882a77f1d1b042b8f35a80b4943cbd", size = 2181827, upload-time = "2026-10-11T19:10:05.724Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/04/0d53dcb588a9404f7094973a672ca5f24536c7163278c429c3463871e78d/pycryptodome-3.24.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5f0036f664f5ae5f092a0acb8a8afc4b719f60f7c88aad69984a65e49b4a32a4", size = 2274156, upload-time = "2026-10-11T19:10:07.566Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/c0/d017e1b031af3bfabe8a61a522471a7c09d754db65650469ef9210a291c9/pycryptodome-3.24.1-cp37-abi3-win32.whl", hash = "sha256:91c0a79c97bf0c24a608d29423c44c5463e26214b60a685d53fb4de3b69b7fc8", size = 1789929, upload-time = "2026-10-11T19:10:09.194Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/b1/f4b32febb3a88f73744deb4b5c8187e5e5ed5a24fd4ee54d965ccbc569cf/pycryptodome-3.24.1-cp37-abi3-win_amd64.whl", hash = "sha256:c00aa444033bac0379413728e92223c7e2f2b5b85fb3e9284fee19239b6ad8a4", size = 1822462, upload-time = "2026-10-11T19:10:11.023Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/32/5842cf945bec9fd359de8c3a299e1f24c48454be7d39a94448dc97d600e8/pycryptodome-3.24.1-cp37-abi3-win_arm64.whl", hash = "sha256:a1144617199294fa63f03d0b18dc3bc438cf7bf5beb21c2975256a3d9a22d3d7", size = 1757005, upload-time = "2026-10-11T19:10:12.961Z" }, +] + +[[package]] +name = "pydantic" +version = "2.14.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.50.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/94/101b49a6c63f99586755690696eae14532772587d9e39e93f7b8d28a49da/pydantic_core-2.50.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:704075d10b74f2f3c6e15407c696d88701df35fc8953f434a431add0d0074db0", size = 2222428, upload-time = "2026-10-11T18:32:20.228Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/a3/f8b1b39349c33037c73d7a1b794c9c29d8f07d5bd2b74399c1375a529cc6/pydantic_core-2.50.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e8e1d6ce820aa23317e8209a86bd65a540973c12dc7552b48a4f6c8e9926815e", size = 2025779, upload-time = "2026-10-11T18:32:21.723Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/2d/862a7d115305cc621358e51ba13f296e0ffdf4fbd984be9f63bc7862730b/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c18db21573bd2c6489f9a544b7499f0df2853958c568e5e783536ee1f690af41", size = 2048429, upload-time = "2026-10-11T18:32:23.3Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/7d/fed95d18bf2b5abe2d5ab2bbec4184bfc22b6805994a33ccdae8edffd25a/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb57f304525a5e3c13333b772bf9a473f36326e9c821b2e8e1b2fd36f80ae2c3", size = 2173274, upload-time = "2026-10-11T18:32:24.697Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/1e/c6f22ceac65dc3d291ff79bea0eb74cbd1de8b128f01fee48af03bcea1e1/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a27c09d86600f1bf2fe3f37e1ae697faf3143931c09322cd799da94deee923b5", size = 2360403, upload-time = "2026-10-11T18:32:26.281Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/97/190b5b6bfe4c72f265feb657913eb30749193927a5ad7b83cfa7866a25a2/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46b3301d3b5c886f77de7546e47274a5842c622ea2020b8c6524c6b66913b4a6", size = 2439799, upload-time = "2026-10-11T18:32:27.844Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/24/48cd98388e4ac2c08d0800af36d15413db15e20387f1038123a2c86cb2f6/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ba4e9d8210d941c200431a56b2c0400b131865947903937ed3ec5404307d2e", size = 2160157, upload-time = "2026-10-11T18:32:29.553Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/86/d909133d0b9979ed14c20f110f21124a0e346caf071bd66ac5af3009062b/pydantic_core-2.50.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e5faeaee74a57d32b3ab3aebad2e348f06d3ba946fc5d28c1728455f00a3d13a", size = 2229523, upload-time = "2026-10-11T18:32:31.027Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/31/4b43f08131b2098e73a852fd932063385f3103b33839fd9d1abdb89e673d/pydantic_core-2.50.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3cda0e538208e5d722bbf3698b24f19c0a7d05bc8d5f8a7f9b121ea7fa243d9", size = 2242700, upload-time = "2026-10-11T18:32:32.622Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/77/de39658569dc7a4c293d110a89629f61145e7538f00d1e4280c28debefd3/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:57f51b31ff826e2859120cf4737c5a758a48d96f3e97da40ccee1796d58078ff", size = 2278452, upload-time = "2026-10-11T18:32:34.165Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/bd/1eeb9e4e791e81210ef9096f58080b32ba15b5b13c108d50aaa3f8ce9f73/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8daa7ee75245d43ad7d747e5c9ecc1b1d06552f72b14887e9276f787d57375f4", size = 2450098, upload-time = "2026-10-11T18:32:35.547Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/e6/b2accfdff17e5a88d6358536702146de00f9339adaa4d5bbe1ee93cb4832/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:acbf31f37c53a5ac0c34706c80b4f5107ba20b05fdd3816124bf236ef0c57dd2", size = 2476839, upload-time = "2026-10-11T18:32:37.119Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/7e/c8a83eb5d2cd42c2e13507150fb777b1edea5a018adc9cbf2858a1d08285/pydantic_core-2.50.1-cp312-cp312-win32.whl", hash = "sha256:45b11cac094aa25725581d9304eee93c9028516b9ea80dd9e175e13a5a2c840e", size = 2101425, upload-time = "2026-10-11T18:32:38.593Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/17/8bbd530b8659e9d963f5b16f6cb8e159f4cd174b4d6304e48969ab12dba8/pydantic_core-2.50.1-cp312-cp312-win_amd64.whl", hash = "sha256:132529c83901437ff642f585216831bf5fd7a91df66829907e155192ead62498", size = 2172522, upload-time = "2026-10-11T18:32:40.112Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/9f/a79d690b127e4e3cff143d031538ab9aa715f6b2fbeecbd07cd6f2bf9fb7/pydantic_core-2.50.1-cp312-cp312-win_arm64.whl", hash = "sha256:4e834f6a8e4ff772dcc34f58ef5504147a3ea5b0f4eeb13b0f8eb2ca75ac57f1", size = 2161366, upload-time = "2026-10-11T18:32:41.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/57/0e237d7091d2cd44a35d243b7344227f90440166860469cd036afc23a04d/pydantic_core-2.50.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d5e062c01286d861fd6a1c4ff6e063547b3e713067f2df033c0ff97ac2ca006b", size = 2222774, upload-time = "2026-10-11T18:32:43.103Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/b9/c720e56858d4e1539503297ed37063e0c08e0f3541c41b777f6a800f75fa/pydantic_core-2.50.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c003c3b7f49debb893d2d85ae099ac5959c9839e2f330fadb1fcdf7a6594482", size = 2024738, upload-time = "2026-10-11T18:32:44.587Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/b8/fbfc25875219cc060e613170ff10e850c6d8924beb4910da57c2ee3ba1d2/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:409e0ea40ec30d9158f33574fd758e689f6045a0f2596701828c27816ca9687d", size = 2047049, upload-time = "2026-10-11T18:32:46.164Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/43/34210124d504c553688f2f04b48500b131237528ac545b445e0d6d30e0d5/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:131059670f1d2444269b8585cb888963994871932447c08b39ac6a51fcfef658", size = 2173415, upload-time = "2026-10-11T18:32:47.722Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/cf/6e178e8fdc11da5965bef980983bf46326a42f436871dd51ba0a57f39df1/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dbcbee53bf17196a7f745aa9bf5a9603953a1e365b1f020be3207c676a3e7c4", size = 2361719, upload-time = "2026-10-11T18:32:49.217Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/14/bd5169d356aa91bf777e28ba0b281c192d286d03c2812c9c9db023a2f00e/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:325c23f3e35cfbf0fe3486fa5f7260d1e45885173002d30a28ca019994124255", size = 2439803, upload-time = "2026-10-11T18:32:51.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/bc/d79d000e5203ebef39af839f2ce77a777fcad6e08ad26af9a2fcd114ffc8/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e722e156d0444ecaefbe640bdb60928752bf2013e2b7a11cdb099aaae19bec", size = 2158402, upload-time = "2026-10-11T18:32:52.714Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/a5/4902cb5fd599422c130bd3124ab31ee8b771199bd56662702eed07d3fec7/pydantic_core-2.50.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:aa8224f10880d9bf1b5993988ba153d42a8b4f3f4f511f93b1f09c93ff613c72", size = 2229755, upload-time = "2026-10-11T18:32:54.126Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/f5/c1481f8669f6060d89110c9b1374173fc8767ca276b84f4870bd8acd5e3f/pydantic_core-2.50.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41bc8237121bd8dc8d888dfd6279fc166ffc88c1f1bf3a8bf00869680533ca4c", size = 2242303, upload-time = "2026-10-11T18:32:55.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/f9/77fc3c7653ba9b6e42049e17e96b25388187d4a7c274ab1cb07f58ac8419/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:45c6266d071c241f2a168d45bf8c54344f0effce35e7e6b73afdec11f3687568", size = 2278345, upload-time = "2026-10-11T18:32:57.38Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/9b/0579c5d12e7f2b16b27e6782427987341fcce07b0725e02ddb0b74add0c4/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:1deeacb112d14d3f4fcb16b165f7dbaf76c70ba6e82f37ba042bdab51970a0b8", size = 2449947, upload-time = "2026-10-11T18:32:58.896Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/ac/1b677db91eba54cc5922f4de6edc46ba45c2bd712dfdc0130382d158e214/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c96fd793b73d1b92e65570132505498fe7b21eaef73cdf74e67e5dfba7ac9e4", size = 2476889, upload-time = "2026-10-11T18:33:00.665Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/2a/3a9f6624ee3ea9ccba5249dde11418bb4c35780a7a92608f8768bd3fea39/pydantic_core-2.50.1-cp313-cp313-win32.whl", hash = "sha256:06ead20d39ffd6f2f6f2a8f8a6de67ff8bb1b4f14a8a30e058502514ee2ac685", size = 2101118, upload-time = "2026-10-11T18:33:02.329Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/1f/323f78ddd9d9938aac420c1abb4e8ba799fc8bdab0acc67c0593b837c979/pydantic_core-2.50.1-cp313-cp313-win_amd64.whl", hash = "sha256:7816e98acc08119dc0f340ab167048ecc54126316330c1f0caf7c6756c88e28f", size = 2170840, upload-time = "2026-10-11T18:33:03.919Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/09/8497c52a739ae425c3ac2f7f56414cbc711c67d374346174f40fe2062644/pydantic_core-2.50.1-cp313-cp313-win_arm64.whl", hash = "sha256:c17799a62c142d61b8a3c51752a7cbc87fe2ad4ccfab10e628a77b405075c662", size = 2161633, upload-time = "2026-10-11T18:33:05.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/09/1bcf160f3cd6333e2a76982fae5963e5032de0bcaaba38464223dc266bfe/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:76e2e83fa6ec8cdc972d438dafc2522b3a47bee4ec0ae668b29cfb1977ab5242", size = 2217070, upload-time = "2026-10-11T18:35:15.688Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/81/f02de95eec7794dfc7a5bc69a834f0e834b31b3009014ce6de903855f775/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:a51eee75939cf811ac09b278745a6cee7dc873ccfbc8b9af3cc88fe4b7ce25b5", size = 2039027, upload-time = "2026-10-11T18:35:18.07Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/16/227746ed3771d9bf2304568b55134849ece34e13b38b1d76de763015c77f/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae28183297fb0d2b8dc46a1f01d51f5e45825fc5afe76a835a6cb7fb34821295", size = 2109009, upload-time = "2026-10-11T18:35:20.218Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/b9/7664d1592a0e5a74903f357cd5b28ceb7336f3473256c5a4e8c8432f1881/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88e492e8b9d0312e7dc13667c30222abf284dc3b79b5302b3607b41a5784ce61", size = 2269883, upload-time = "2026-10-11T18:35:22.48Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "readme-renderer" +version = "46.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "docutils" }, + { name = "nh3" }, + { name = "pygments" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/d7/9309494fad74ee831d4546f69325b5519f37c6dfb2d9ba495db8c6d4f4ca/readme_renderer-46.0.tar.gz", hash = "sha256:af3e964914f6310a33ff67b72a4bdd940bed8d7c3bdecd2d14f40edf284bfe90", size = 38382, upload-time = "2026-08-28T15:18:32.49Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/72/ac5ca81fe9121fcaa9d828d21017cba00a16a98e4ea5fb60c878f93dda4f/readme_renderer-46.0-py3-none-any.whl", hash = "sha256:d0dae1f74bb273b534770cb4cccb6bb78735540afdb03c2146f4e19dcd412560", size = 14239, upload-time = "2026-08-28T15:18:31.132Z" }, +] + +[[package]] +name = "redis" +version = "8.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/99/604f0b666d4c616d891cf77ebb9db6bb21601344c051aebf1b72b9ff915f/redis-8.1.0.tar.gz", hash = "sha256:6e1a19beef9225c83efd689c7e6b7da2d5215b1f42cd13b7fc3714d0a09c7b25", size = 5254356, upload-time = "2026-07-30T08:51:00.269Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/9d/c5731f6e3608663d4d3656fd8d3aecee8b509c3082818f5a13eae925baea/redis-8.1.0-py3-none-any.whl", hash = "sha256:a4fe1aac3d3b3cc791d4b3d5931c5a956045dc951ee74d1c913ee3ac4d2ee9fb", size = 560618, upload-time = "2026-07-30T08:50:58.497Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.1.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/44/311bac6b6ef81e4dfd0287d04900108b1f5c00c9761dd3c0a2b7b9d0f86b/sqlalchemy-2.1.4.tar.gz", hash = "sha256:7bd7ad604487daa7eab8716471c29a7185f17b5287ce73bb7bc79fea050d8cfd", size = 10544216, upload-time = "2026-10-07T17:33:59.116Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/5e/cb5b078e007340661b010fa8bd31ce27468f88e09b35266544df4e0c52ca/sqlalchemy-2.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f953be9ba26039a24a5205c65d33518b608ce6f4f0f4e9b9c14eaf42a10dfc52", size = 2466469, upload-time = "2026-10-07T18:17:24.049Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/98/44e2fdc5bc053dae559bf4f4eb7967ceecbad162299ecfc8de2edc3fcbe7/sqlalchemy-2.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ac64fce94c5b389062d2e3806db5dc780447591e0dfd5ead218c884f0703f2e", size = 4668213, upload-time = "2026-10-07T18:37:42.294Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/25/ed2262f964687b06f10c2c98b2dc9c9ed211f7cc11702879969a9ac217e4/sqlalchemy-2.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e5045fb6aadbb0f978ab9b9d8822f7b7a97d2281814e7d13d791155664eace3", size = 4720857, upload-time = "2026-10-07T18:24:46.842Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/d4/fab64c61d5d22ddbb077afd1e6b29b498bdacdf6406a03f53566e7e01686/sqlalchemy-2.1.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e3a026436c51f296aa1d01243909a3b76490950e927824b10899a083cc26e7c3", size = 4369351, upload-time = "2026-10-07T18:59:45.483Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/e4/33413f0fafbcf3b332320aac2c1e40f3b4f17e56359a9474cb10de4bee8b/sqlalchemy-2.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71040390ef01c85e9d26e5c83cb0c5942dcc8725c49186430af160ce2f54234d", size = 4591496, upload-time = "2026-10-07T18:37:44.433Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/65/19821440cbd5c93da053d627b3e402eff11ff252bfae37700645b3c155a4/sqlalchemy-2.1.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:07c60abaffb980b7382f2c75be8a5279c2b5df2626a0f5d751dd942799bf3b5c", size = 4362379, upload-time = "2026-10-07T18:59:48.278Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/e3/168a0f93efd6ec40f59645a7e45ab08918e0bc8ecf07656e4ca09acdcc30/sqlalchemy-2.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a577e2127e52b0fe2bc54c73abb375a20ffe6f59fbc5568ccafc233f5bfcf8ef", size = 4667997, upload-time = "2026-10-07T18:24:48.72Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/79/0a852ef65864acd8d577d7aa6f67146167382bd6faee7a7586b9e6e28275/sqlalchemy-2.1.4-cp312-cp312-win32.whl", hash = "sha256:6c79e0c824d51c586757ecd342160bbdede9010df04bb71b9bbfffd5c7b6ee29", size = 2376664, upload-time = "2026-10-07T18:25:00.637Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/b9/a5934263bb1d712f743289ca224ab3b87e3570ac157802291e37ab85d365/sqlalchemy-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:dffa69d2f3ba1933c1c1882dbef8fb3231b33eb19263e8b8c5cea24995071f06", size = 2429357, upload-time = "2026-10-07T18:25:02.565Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/fa/a2323d81384ff214aa189057b7455b63623e66f28208b982e86c3cb042f5/sqlalchemy-2.1.4-cp312-cp312-win_arm64.whl", hash = "sha256:e30524ae24e31d83e1b5f734862882c442f4158e3566f2c5f5e9bd3c659bb517", size = 2388756, upload-time = "2026-10-07T18:22:36.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/e4/23174288ed2c03d6dbd5dfacd69e28303ee95f49642a8ed0544932999fb6/sqlalchemy-2.1.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:70006e9e6157200b795beeee04bd5cb15bccb40a14de595eb9f5dcf5945ed244", size = 2460507, upload-time = "2026-10-07T18:04:40.044Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/ac/254fadc98bfd600445b976e81c6d777b08a728a415c3b77a8c8d35b89a83/sqlalchemy-2.1.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3341ddc430733cd961bc064889f42712a0b4056733a21c83176842aad67d12a6", size = 4594505, upload-time = "2026-10-07T18:16:58.768Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/6f/ac7beddc57c9c87bd77bc1c158fcbcdc20822f1873bf33ea3480d04e865f/sqlalchemy-2.1.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98f7a4bfeaed3722804f737ae2bd4077b35e57d6f4531fe612bac8160cda5acd", size = 4647364, upload-time = "2026-10-07T18:34:51.721Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/82/fc3891f261c4738a8b90cfdd805fe292d1af3b77f680a63b7349304c74e5/sqlalchemy-2.1.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ec5d079935f67febe0ab8a3a203ad591b99508adc34ae0027f696dcb20373537", size = 4311619, upload-time = "2026-10-07T18:38:44.002Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/1a/160c1320ab20e764a29721dc3fe7c31af34e291c652dca875d1ca6022b9a/sqlalchemy-2.1.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3d675b0856b6703b29d023517a4c19fecfbb55214ff5c72cd813527e40aed9b4", size = 4518031, upload-time = "2026-10-07T18:17:05.615Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/2c/15a204333896e5dc63cb089ea20ca3ebc3c892bedf9fa00cc1a65e20d7b5/sqlalchemy-2.1.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a0bb9ee6a38cb36240dc88da11888348f61506047be54de3f09496c3b0ead6f5", size = 4312818, upload-time = "2026-10-07T18:38:46.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/55/5e78d288f198598f278b4b7baef42f18e039b14b1e1045e9df3cf571300d/sqlalchemy-2.1.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61a2c48771cf314b6613d327c795902bbc0eb6d6169deb23b35004ba6ad6cc0d", size = 4608584, upload-time = "2026-10-07T18:34:53.69Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/f6/e83b93ecc6e6528623fd7aa2af27ff0660d22354b78fe6ccad03f9ecbd9f/sqlalchemy-2.1.4-cp313-cp313-win32.whl", hash = "sha256:3fd608a06bafa768ad5711df4e17eb058bdc490e9df7d39b12a90947471e8712", size = 2373880, upload-time = "2026-10-07T18:22:11.722Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/46/afb02975023db6aa4b8608177c2fae17d0b435d9cbfcb5df4fa6e65a8078/sqlalchemy-2.1.4-cp313-cp313-win_amd64.whl", hash = "sha256:b756d74527c56a7e4cfae297f7930c1d75bdf4b23f214c8c13779746d28060cb", size = 2424430, upload-time = "2026-10-07T18:22:23.688Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/e5/76dc82d59186b98b27589b33b01175c0d49512679276170271d9384418e2/sqlalchemy-2.1.4-cp313-cp313-win_arm64.whl", hash = "sha256:a64d54015233f824f171009977bfbb6b08bd0347b700cf17cb047ffb94c4148f", size = 2385057, upload-time = "2026-10-07T18:11:48.248Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/b0/6675a01f4e6215e0a809d28a800953294ab31370fe8c4bb3eb9e28c0b5a6/sqlalchemy-2.1.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7a2f6164c0527cd8fc4cea79a5c9d8369ffee417b8ba444a42342f36b91deb75", size = 2463645, upload-time = "2026-10-07T18:04:41.615Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/24/4630a4009ea08a0769d5ff6517c7fc978f6a63eba32e08c44b98c284d7e4/sqlalchemy-2.1.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6929a11ad26a91a4efd891c1252b373c2e88f056910b83ec6030ed3f2cbcb734", size = 4587911, upload-time = "2026-10-07T18:17:12.512Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/02/953686f44448b92cc628245687a242799b6eb11ef30ad2bc7adacd51986d/sqlalchemy-2.1.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14528d37d7d46a92f2a483f188f7fecd86cdd789254a0412b960c9fc5e9efd6d", size = 4617539, upload-time = "2026-10-07T18:34:55.826Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/23/a44288ab4fa12e51c9d390e7d798d70a45669ddcbddc9dd9b5948eb1aa3f/sqlalchemy-2.1.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d2cb669c6bd1f19caf51db6e3c4fdd4cbb76f9db3ef81c3aeb5e288d9bae101b", size = 4310883, upload-time = "2026-10-07T18:38:50.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/39/1c441ac015767f619a9e6cc306905bb042f94b84f2a1e930e989e9c6e209/sqlalchemy-2.1.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:63dc25b21fd9a41dc09b7aada4b3b0d97cf4b6414f74bced6ac45326bc799ac9", size = 4516511, upload-time = "2026-10-07T18:17:14.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/b9/f54ea5ccb27d9a712d90d1617050bee761df25dc1fb5e0b7d2aa867deb51/sqlalchemy-2.1.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:308f96d24e773d64609a2a0d1161a068f9f6e9165523bc4e07aa9c45f0c4213f", size = 4312314, upload-time = "2026-10-07T18:38:53.249Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/9a/c1e39287ee988e4c2e25c619959b8fb15b297734be040653fe85b57517ee/sqlalchemy-2.1.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93b9416b9011a3b7689a933e04ac9f61d15686b6cb1948ebc1f41467153116c3", size = 4577833, upload-time = "2026-10-07T18:34:57.829Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/78/5f1ae1911d2b20ccdb39ee522118533a4b5262b6e5e06bbcbb1ebd1f4617/sqlalchemy-2.1.4-cp314-cp314-win32.whl", hash = "sha256:89db94855287fdac98d74595cf13ea59fbffa608d6400ff972b0fd4c036d873f", size = 2380724, upload-time = "2026-10-07T18:22:25.374Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/93/4dfa4ce15d082011fb94e06e7c6b4c2957a3f0ddeb8fe9b89d007bc058d7/sqlalchemy-2.1.4-cp314-cp314-win_amd64.whl", hash = "sha256:080f8d853aac5bb5620f0ae6f46527397cf18dce0ec2b478b478469ef3cae2c4", size = 2431689, upload-time = "2026-10-07T18:22:27.144Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/c4/6f6c29eaf459c4c2d9b7d24e300bab32043f8f8a936df863f3b886b5564a/sqlalchemy-2.1.4-cp314-cp314-win_arm64.whl", hash = "sha256:64d41be1dd88f184de1931f0173f4827122a1b49fd1150656641200c0bdf640c", size = 2396106, upload-time = "2026-10-07T18:11:49.528Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/e9/48f851411665e394f60c669d1f9494d660f5f1fe46e275f9615cfc812a98/sqlalchemy-2.1.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84272f329c15081a1e09b4a7261118b4e8a547f43e00fca98e55bbdf19eff3be", size = 2503038, upload-time = "2026-10-07T18:19:41.094Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/ed/bf83068bda4051d7fd719c14cefc15d8466ef1e3656b9f4401b0509b11e0/sqlalchemy-2.1.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b3f58bd26fc010ea28976d401845e4e6ce02e1b7c0288b3ea9c9a3c396f0bcc", size = 4919623, upload-time = "2026-10-07T18:16:45.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/de/57eb70d56b70d22a9360d658b195834ecfdeff7a7bc5c2e3a7fa7a8f7823/sqlalchemy-2.1.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82d728075d42bd457d09655cf22e99d772a648c6f67e86743a4f05b7d063ca18", size = 4830238, upload-time = "2026-10-07T18:37:04.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/3d/c410e9e79a53fff4c04444da609fed6404868d250f11fe8bc53d827bfb0e/sqlalchemy-2.1.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0970394ec5d9e397aafc5bc5fa2b7f8b58cb191f2703006b19a96ef4bf00b8d9", size = 4508380, upload-time = "2026-10-07T18:38:44.277Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/c3/01b93821ba35b5b162e79c613279d960a120767694f656da1c1374dd3ed3/sqlalchemy-2.1.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6005f2f5fcd67fdd721446128e6a2a1d18f77387a604fbd26b0006a086b33096", size = 4775311, upload-time = "2026-10-07T18:16:47.724Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/88/0b40754e4d851d33548792062c23467a3d8dc07f2eff90cb19e4c404fb4c/sqlalchemy-2.1.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0e01a3e199ae219381c4889993c5584b1b905fffe6830f639adb6770036a8913", size = 4508558, upload-time = "2026-10-07T18:38:47.857Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/2f/3916954eca5596d9e93fccd2ec0e45fd8c65981debac0ec4617639ded6ba/sqlalchemy-2.1.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:22129e7d00ac66b291840c4dc83a9c497456ab5bffa682dcbfdc2356f9e49e5a", size = 4767114, upload-time = "2026-10-07T18:37:06.792Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/d6/6a29716aec6ae17cd77e27b5e0dedc68cf9068594f2b601806c1d146427a/sqlalchemy-2.1.4-cp314-cp314t-win32.whl", hash = "sha256:bc33d3e59d4e84b8866cc9ba13732585e37212dbe3542cb09f232682b36f47a5", size = 2439584, upload-time = "2026-10-07T18:22:44.434Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/79/2f0b33647d2d26f098269096c1864c0b4e81095354cdedb95192647f47cd/sqlalchemy-2.1.4-cp314-cp314t-win_amd64.whl", hash = "sha256:346d144e8912ae087b10d3c2081657cb634728600693eee6dbb71d7eb4768101", size = 2507217, upload-time = "2026-10-07T18:22:46.176Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/e5/869c1ac0a21e17e4617b6a7828b50320bedb7074b6d67aec59299be5cdba/sqlalchemy-2.1.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3e5de57c71b3460e2ca6137e82cd3cb8c9f711f301f50d5c77156fdb9c822999", size = 2425329, upload-time = "2026-10-07T18:12:20.595Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/8e/a082a165b473dae45d2f2f79be15f5c405ac579830c64253efbf04695177/sqlalchemy-2.1.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:418786f05387ddb66ee683a1d016c5a8d9bf7be921e6ee8f285c7b6ac961a731", size = 2463572, upload-time = "2026-10-07T18:11:12.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/35/74db254005ecb384533973b157ba1fc3fe5bc41a5bc6e0500ab8369c49e6/sqlalchemy-2.1.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:283914efed30e4d44301e36ac90ad048570538b8a70f072fe01578d9b205d09c", size = 4591740, upload-time = "2026-10-07T18:01:00.314Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/81/5cadd72b0c26b6ee7c1e6950cb9f0cfc383246a842314a1b2a87f455db25/sqlalchemy-2.1.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d2eacdbeb990b80235763860923c60a8393745b66f7149a734980c65896da72", size = 4627040, upload-time = "2026-10-07T18:09:24.836Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/78/aed93cc373f61b57625e1f9f84bbf12358e32e935e64fa098f3a446e1203/sqlalchemy-2.1.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e43fca5fdd5f34a3f8c54107a3648d3139de8bbf596a189f3f0de94bd84949bb", size = 4333103, upload-time = "2026-10-07T18:33:48.275Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/31/ecc6bbd365671cdc512a59d42afa7c34b2833a8d841754918ae3f62d36dd/sqlalchemy-2.1.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2e1b5343d315b10a4a71da481729f66f830a561595e02b61e8a5a65d658325ac", size = 4519962, upload-time = "2026-10-07T18:01:02.268Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/58/9f8f6157c2252aefe73f4a0b3859413bb720d14321aa7f367c691949aaf8/sqlalchemy-2.1.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:42c37c06adcecf444e8c981f7e9237a41bdd445c83da0df9e08b4ad958becbbc", size = 4331935, upload-time = "2026-10-07T18:33:50.334Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/de/a4ae4b95d17607004f01e9a085fb221087c557bbad77a3d87d5d0a5fd8bc/sqlalchemy-2.1.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:bab7f51d38766d6a64da2b41976f1b3f9cc2ff37d3f2f63bdbac876199f3a48e", size = 4589275, upload-time = "2026-10-07T18:09:26.872Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/27/56f69293a01279ac0e6077b8c358eb0f1c2afc6aa17428414a86c8871042/sqlalchemy-2.1.4-cp315-cp315-win32.whl", hash = "sha256:1541ba5bf0f232cd61f9ef3df78c93977c72ba6031506a0e6d057b2a3ddb76e9", size = 2380494, upload-time = "2026-10-07T18:04:25.637Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/7c/ff7e29f95996ed49b950afd531b89e7c8d15addb41735643d07090550090/sqlalchemy-2.1.4-cp315-cp315-win_amd64.whl", hash = "sha256:596a95611c217cb19c21f02f43c637cb507cab71dcf0467c5c7d98fcdd703007", size = 2431760, upload-time = "2026-10-07T18:04:27.275Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/8c/4eaa4978760cd632093ea272e7c4f88223619202f5481f897e67d4377409/sqlalchemy-2.1.4-cp315-cp315-win_arm64.whl", hash = "sha256:0d1ca95e42ce3c18818f170b741d30a33b292c6f6b9a202ffd717e28fc99b8c7", size = 2395807, upload-time = "2026-10-07T18:30:54.962Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/7b/b806fbfc61ade37c4f3aecec0874c345fb297b56a3743116dcefa3e4700d/sqlalchemy-2.1.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0f672ed6972164fec94a8f0b21dcf8545080d0727866335fb8adf9f4764ce6ec", size = 2499195, upload-time = "2026-10-07T18:19:42.835Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/ba/4f9fba8340222f09287e936d7b76e6911a4e507c7d6373ada770e8f697d5/sqlalchemy-2.1.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72e3fa41d1fdab87d4e88bbdd69c9522e2795549fbe7b07bcf4ae9ec175f4b11", size = 4896914, upload-time = "2026-10-07T18:16:53.18Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/34/c4aeec7bee453badd8b0e02c2021a13bd70ef01038303d05326e99f595b6/sqlalchemy-2.1.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb2cb98d056e63e353ed697750004e07c79b054d73059ba3184ca3bb07296bea", size = 4846593, upload-time = "2026-10-07T18:37:08.766Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/54/6dd8504364e5f5efd328e98fea963e5a2e978ff8dcba70d95231314f82a9/sqlalchemy-2.1.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d66fdcc5506e0f8bb8d3f4f95125220a7cd6c46e8b1762750f01e9639973dd8", size = 4494821, upload-time = "2026-10-07T18:38:51.166Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/42/dc584c098bce29578fd0611cd6f36830e06b4dd2505d3020a0b592f4cf08/sqlalchemy-2.1.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:81f802c96dbf96e59c6982fa1b87da7868920fb0c27b9b81e560a62f57c2ccfb", size = 4752545, upload-time = "2026-10-07T18:16:55.711Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/41/69a70c1419bea97e80f65ce09f4f626df464752b276f4f3d69ff6fbf2325/sqlalchemy-2.1.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:acf8982c70471a68aa90d1aba08b48860c55b3357ec84ccb0f09368ead2ce099", size = 4494093, upload-time = "2026-10-07T18:38:54.37Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/bd/d296c2223e8417b350db215d94dcd344bc0dfe9deb7d810a21f7d8cd0b14/sqlalchemy-2.1.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:778094c83e36c430756a7e1a1ac66fc3cffb2c6a1067958fe6b920abcec7bc5a", size = 4771973, upload-time = "2026-10-07T18:37:10.93Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/4c/c3a10d9da10e4e60808ffd1825547b383c0d7ca9e56d15cdae47c04e752e/sqlalchemy-2.1.4-cp315-cp315t-win32.whl", hash = "sha256:963348422b22f760e9462e56bc32bf4d95d224cc5b8c79a3c6e3b786d3d2a2b2", size = 2436383, upload-time = "2026-10-07T18:22:48.162Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/de/8045d4ad1fd3a66c3b9bb576f3734c86015e19ae2f1617af92eb63cf9e58/sqlalchemy-2.1.4-cp315-cp315t-win_amd64.whl", hash = "sha256:fba3500e170d25f581e053009edeb0b158116084d91d465de218718d336b67c3", size = 2502237, upload-time = "2026-10-07T18:22:50.196Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/4b/245e2315d331cc15765a2373e068445fbd28eb63beb23ea862828808c0bf/sqlalchemy-2.1.4-cp315-cp315t-win_arm64.whl", hash = "sha256:0a9a464bc360856b7ea9bf8aa26aab92ca115dd08149cb0e004063d5db13584b", size = 2420917, upload-time = "2026-10-07T18:12:21.876Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/62/dbf11a262f6fbb41390cab2d8e47a30ec0961018b68201607b599dd489f5/sqlalchemy-2.1.4-py3-none-any.whl", hash = "sha256:0b96edcc2cd60fe1e35f67a46f4eb076e57297841b9eae949ac5f196593f00a7", size = 2054935, upload-time = "2026-10-07T18:01:16.403Z" }, +] + +[[package]] +name = "sqlite-vfs" +version = "0.4.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/b2/d4d1dcdbf13d9a9884b30d5cab90d518d86e5f1ec3531acddfbe8ebcdd1a/sqlite_vfs-0.4.0.tar.gz", hash = "sha256:970cc30d0cd16b3bc9f02b08af37eb8fcf89523b6c17740a86cd9cc260d544a4", size = 740965, upload-time = "2026-09-20T08:13:35.884Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/9f/5b32146512efc290df1a7c654f603317346d17b04684ea55dff19affe18d/sqlite_vfs-0.4.0-py3-none-any.whl", hash = "sha256:d2584e8d1cf5908f30aea6db6b95eaa01c62be426bde41e5ff7d226d40128a31", size = 94794, upload-time = "2026-09-20T08:13:34.216Z" }, +] + +[[package]] +name = "sqlmodel" +version = "0.0.48" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/67/b2c0b771c89c023262dd8fb6ba2cdc6a004e5ff75dd5763051b38ccd133a/sqlmodel-0.0.48.tar.gz", hash = "sha256:5582e87e845e23bb1179a7d8b11a4f5e441b4494528a41ee2fe1d129ffe91543", size = 99329, upload-time = "2026-10-06T21:44:36.391Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/76/6f8221eda15f28471c3887e5a75a62cd7338d20ac03e1e78a110a127d61e/sqlmodel-0.0.48-py3-none-any.whl", hash = "sha256:8d389bf735b03a17508e93e888c13a30ef1ddca22dd8e57add12317401e4112e", size = 31468, upload-time = "2026-10-06T21:44:35.326Z" }, +] + +[[package]] +name = "starlette" +version = "1.8.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/0c/6efb252d091ecccd7d62048ae11f0ea35cd75a4fbaeea5e30f9c3bf91d10/starlette-1.8.0.tar.gz", hash = "sha256:1565dc0b35d5737a271ed1e0e04e949f4e81198799f216d2667b0a0fb9cf9522", size = 2730457, upload-time = "2026-10-13T07:54:39.53Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/b0/5742e4ac7af5eb58ec3470a537a49d7aa507e5539413e504b3a65ef50ba8/starlette-1.8.0-py3-none-any.whl", hash = "sha256:dfdd6b29c26483288088d990eee59631dedadd66ce20d203402a7ca8e3c4656f", size = 79612, upload-time = "2026-10-13T07:54:38.019Z" }, +] + +[[package]] +name = "tomli" +version = "2.5.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/78/9ad63712633ed3ab5cc1a648d863d7e7da371e9425e209555a0fe711b695/tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6", size = 17662, upload-time = "2026-10-07T12:23:37.892Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/ed/7baa86f87493646a594de388c7c1c40a39dd0461f7e9c0359cbeefc91fe8/tomli-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df", size = 164854, upload-time = "2026-10-07T12:22:27.444Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/b1/44c0341f2224397855723c7a8a39f718ea6fcbcc3dacc66e5aeca0f334e3/tomli-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b", size = 164074, upload-time = "2026-10-07T12:22:28.679Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/04/e2d5b7d3fba47adedb23de616c16d428ea076c79a3d8e1d95d649ffe197e/tomli-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0", size = 274274, upload-time = "2026-10-07T12:22:29.804Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/90/6090e706ff27a6f89f4a40578e3324b95c3cd8c4150868aabf33a8f414c3/tomli-2.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6", size = 286435, upload-time = "2026-10-07T12:22:31.297Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/9e/a2c40768df16c408f22430afb0a73e9d7e5f79c950884954649d1146b74d/tomli-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc", size = 278119, upload-time = "2026-10-07T12:22:32.601Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/25/3c0cb485b98e9cfac495629b1c93c87ccf0b72fbe9d2689fd8fe62c6d5a3/tomli-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7", size = 286177, upload-time = "2026-10-07T12:22:33.745Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/8b/0144c65f0e37e51c18d04ae15c21b19431c165002d0131fe9aa8b0b8b1e8/tomli-2.5.0-cp312-cp312-win32.whl", hash = "sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2", size = 102760, upload-time = "2026-10-07T12:22:34.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/32/5d6d8f42fc9a05fce69354e00ff256484192f5f2fc9a2165718fa0de61ec/tomli-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7", size = 112722, upload-time = "2026-10-07T12:22:36.162Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/65/df18032218db0fb9b769fb23c8039a051f15c811993995ea04c350273a32/tomli-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea", size = 109534, upload-time = "2026-10-07T12:22:37.296Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/e5/51736d70da209350969e15aca5c5ab6e2ce1ea87a0a892a6c13aec172a86/tomli-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea", size = 163328, upload-time = "2026-10-07T12:22:38.373Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/55/086f80dab4ab497602644274e6dea7ec5dd0b4e262e443a8ad3bb7edee2d/tomli-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043", size = 162246, upload-time = "2026-10-07T12:22:39.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/eb/3ecc94459f3635c92321f4e7bde571323fdb2267c50e19e3188a281eae3b/tomli-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0", size = 272655, upload-time = "2026-10-07T12:22:41.08Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/d7/494fd1f0c37a621f1ad9975c2efadb523e8101f144ed6edb2e7fe64738f2/tomli-2.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b", size = 283595, upload-time = "2026-10-07T12:22:42.222Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/51/bb8d62b1317e6640866f6949b2d5855e5300f2c99d46de1cd245570bba65/tomli-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066", size = 276253, upload-time = "2026-10-07T12:22:43.625Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/f4/f46bd7f0763cd47de2db697dca9257c6a4adfd1a93b018cc75c8190ed5a8/tomli-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b", size = 283582, upload-time = "2026-10-07T12:22:44.983Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/03/70f2bcb2923a6db37818d917e124270a7f4cfd38ea576f5aa753a91c0ef5/tomli-2.5.0-cp313-cp313-win32.whl", hash = "sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68", size = 102628, upload-time = "2026-10-07T12:22:46.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/98/d52024bb5b0ff68b4f0d276d867f634c84a67319a7e9f6b7708a37742333/tomli-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc", size = 113301, upload-time = "2026-10-07T12:22:47.647Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/f2/540db3a70572a8c23a28aba3e9c358ce0ffffbafc990905c1343aa265b31/tomli-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84", size = 109744, upload-time = "2026-10-07T12:22:48.925Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/49/caf6b307766eb9567664a8707e9d6be5fcc0e8903f18781c6677a60d80c7/tomli-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105", size = 162899, upload-time = "2026-10-07T12:22:50.088Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/c8/68cfce773a2733a49c74f99d627fb461bd990756860099eac25617889585/tomli-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646", size = 162080, upload-time = "2026-10-07T12:22:51.558Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/b2/e5bb8651fdad593f670501a7d718b1a7f73f064d44dea15e04c04dfef45d/tomli-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b", size = 273380, upload-time = "2026-10-07T12:22:52.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/d2/9e2d7f8b1dfe0e2b34c245986ebd55c4c553ea4ce6c47c443b332673253f/tomli-2.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75", size = 283228, upload-time = "2026-10-07T12:22:54.173Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/df/ec7b876b7b1a2718bd74a3743c076fff565b04029ba33e8f61fac262739f/tomli-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb", size = 277189, upload-time = "2026-10-07T12:22:55.342Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/7b/e192d9eed0b9cb80da799f4d77052297fb9a2c3cc9b19f571f56ea88add6/tomli-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3", size = 283632, upload-time = "2026-10-07T12:22:56.735Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/50/ff94454e75461d75623e47401ed323d65c10aab8fe9033242c20cd2fdf32/tomli-2.5.0-cp314-cp314-win32.whl", hash = "sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b", size = 103535, upload-time = "2026-10-07T12:22:58.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/0b/bdacf05f963bd6026ebf6eeb0beda847d1d60e03e440725c64a4e08a0afd/tomli-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a", size = 114621, upload-time = "2026-10-07T12:22:59.2Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/99/53f438fa6ae4f9d4ed0ddde3e7242b3bdc34b48c8f9948b72b9e9b127676/tomli-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3", size = 111572, upload-time = "2026-10-07T12:23:00.479Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/20/1f88f19427d380a40e90a770e087489eaafe4aeee070ae88ed2bbec00acd/tomli-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4", size = 171814, upload-time = "2026-10-07T12:23:01.914Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/56/cbe5079c9f9a54b9b3e27fc82f08f3cb36edee75561679f53d2380c801d6/tomli-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d", size = 171324, upload-time = "2026-10-07T12:23:03.18Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/30/1d53fd3b0f1cb3ba542e345ec32c26aefdddc4e829e4f3429af8a4f27782/tomli-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9", size = 297441, upload-time = "2026-10-07T12:23:04.345Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/d9/0800acb6a111686f764c1b91ef15cc42a20a66a46013bb42220f1d2c61c1/tomli-2.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f", size = 307476, upload-time = "2026-10-07T12:23:05.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/63/30a8f3cd51b5bec37f04744bad0b0dc6160df84aad4f27b0e9283d66f221/tomli-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374", size = 296113, upload-time = "2026-10-07T12:23:07.202Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/18/0b9ffc597e69c5a1e20a7823cb60d54b39a9f54e91edcb8574f022186758/tomli-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442", size = 307725, upload-time = "2026-10-07T12:23:08.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/c7/18f8baae0b5607a60e8e19b4a7fedee43a8ff6458e3896dcbbadeeac9c22/tomli-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03", size = 108546, upload-time = "2026-10-07T12:23:09.956Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/34/4cca9739254130627bde87500b3f2b512154fe2f278efa7e2a5e10ad4bcb/tomli-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1", size = 117814, upload-time = "2026-10-07T12:23:11.486Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/fb/afa530d47dd80a78fce43beac6bc6e00f84558eafcffbc6f37b21e80d056/tomli-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0", size = 115188, upload-time = "2026-10-07T12:23:12.728Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/98/316fdc00f8c0939e6fe50461dd343c162d3ad51d1286eb25b7db54361d50/tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc", size = 162775, upload-time = "2026-10-07T12:23:13.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/22/7b10fa5bb01c9539f53f69b619361b19350acc73657772ea7ac70ba309a8/tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276", size = 161406, upload-time = "2026-10-07T12:23:15.215Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/e7/1a069d86dfd20f1f84f71c63faed9f83c1d890bc06c27d82dc7d888fb573/tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52", size = 273855, upload-time = "2026-10-07T12:23:16.471Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/83/d1ef43d1687d092ab9c235455c76e6e709483b346b056f086095c7c263a5/tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7", size = 284910, upload-time = "2026-10-07T12:23:18.166Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/05/f4d9cf7de61822ece0c3873f30d291e324911c71a378b8bfe5ced13fd9f5/tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391", size = 277723, upload-time = "2026-10-07T12:23:19.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/28/78262493141fa543151cf005760c3cb01d09fc28a11f993c05109902cb8c/tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859", size = 285115, upload-time = "2026-10-07T12:23:20.698Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/b9/e1dab9a30bcb677b5cc5cee810609cfd64f24306a3055767dd3fda00b1e0/tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb", size = 103475, upload-time = "2026-10-07T12:23:21.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/bd/31a3790c11d6ea95fcf5e6022ac0f8d0543c9b61120b730fc481bd43d3b4/tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5", size = 114589, upload-time = "2026-10-07T12:23:23.098Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/a2/4f6310fa699364f0e3af7ee3af88dddd9af066d33e716a0265bbe2b3ea84/tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd", size = 111493, upload-time = "2026-10-07T12:23:24.233Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/14/00853f0b396d8971107ae1921bb5b322fdee1650d2f16bf06c20adb532e5/tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57", size = 171380, upload-time = "2026-10-07T12:23:25.512Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/ad/fa6949321dadee46b27363974fb197b94c911c3b0f7a5fd26d7dc18fc2a0/tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd", size = 170553, upload-time = "2026-10-07T12:23:26.855Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/aa/3056c919eb3e084df3752b2cf5f865dcc04af0b27dba2f66d7b28af4633a/tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01", size = 294428, upload-time = "2026-10-07T12:23:28.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/b2/faeeb5d8769ea3832021d73e892c8391eae7b4b4f8b55a789127bd8b18a9/tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f", size = 304909, upload-time = "2026-10-07T12:23:29.381Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/52/f094c09e73fb654b621716d019acb5d29bdfd1be01df80c281d552bda48d/tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a", size = 293220, upload-time = "2026-10-07T12:23:30.608Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/f5/0c30541078ca4b505ce3bd76ed931facbfec524dd018535d691d1af0a6d2/tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142", size = 305705, upload-time = "2026-10-07T12:23:32.181Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/74/590e7d19d6a118fc5cc5704ff358e21d95b8573f6b9443b1519f29ca8825/tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5", size = 108432, upload-time = "2026-10-07T12:23:33.496Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/b8/63a75cfb27a17c38550e44025d3a6e7be64516fd8608a3b75703bf37d81b/tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571", size = 117281, upload-time = "2026-10-07T12:23:34.648Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/01/e8c1debb2173973372934c68fc8e46170ab60ef23ed4592dff4dec6e8993/tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7", size = 115069, upload-time = "2026-10-07T12:23:35.77Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/3f/3e3f8fd0919249b0200c80fbc4f9a1e70be19f9883da71dfb7f8b9ab8aca/tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b", size = 14765, upload-time = "2026-10-07T12:23:36.875Z" }, +] + +[[package]] +name = "transitions" +version = "0.9.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/83/9e90f3494057d73c9e6bd8de8488af7c9fa714666ccc2db30fe07d147818/transitions-0.9.3.tar.gz", hash = "sha256:881fb75bb1654ed55d86060bb067f2c716f8e155f57bb73fd444e53713aafec8", size = 1191029, upload-time = "2025-07-02T10:49:03.153Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/05/fea3020e90aa378941ff10de8860fb3b1288313c27efd10fa0c476498f7e/transitions-0.9.3-py2.py3-none-any.whl", hash = "sha256:02463248f2b668d86f66636b1e3c9e8de84d93e22915247f4e1aa9ee1cae28aa", size = 112792, upload-time = "2025-07-02T10:49:01.415Z" }, +] + +[[package]] +name = "twine" +version = "7.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "id" }, + { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, + { name = "packaging" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "rich" }, + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz", hash = "sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size = 215032, upload-time = "2026-07-27T15:59:00.825Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/08/ddcdc06225eaad6de0e48e1002b06d919dbde20582d0662c7af51308e5d6/twine-7.0.0-py3-none-any.whl", hash = "sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7", size = 43204, upload-time = "2026-07-27T15:58:59.26Z" }, +] + +[[package]] +name = "typer" +version = "0.27.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/51/d33db42cc72ffd8c30777547b42d01f0cbf9d95a770457698d0174b3ed71/typer-0.27.3.tar.gz", hash = "sha256:d0396f770a560ab1b0a8504e13b5f254b728cedb05c61cf0359e944e50ce8901", size = 205303, upload-time = "2026-10-06T17:24:16.61Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/ea/2e31b67051e91a133189e9c000c222502ddc6969856416de0d095de4c0b0/typer-0.27.3-py3-none-any.whl", hash = "sha256:e50022f28b82a86313e54501317a1db64bf8f8d036ff8cfe5ca7e47675454aff", size = 123312, upload-time = "2026-10-06T17:24:15.054Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.54.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/34/30e9280707135d2cfc589dfff3cb796bd07a3aeb1a3e415ba09dd89d7bb4/uvicorn-0.54.0.tar.gz", hash = "sha256:a2e33cbfaa0306f8e6b0c13e0cb89d7d7a2da3e62b90c66e18c33d9807b28620", size = 112283, upload-time = "2026-09-25T06:52:37.601Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/0c/b54a4fdd7f90a3af8b02ebc9ce6712c2c208b7926a2f7bad95c33ebbe943/uvicorn-0.54.0-py3-none-any.whl", hash = "sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf", size = 87427, upload-time = "2026-09-25T06:52:35.829Z" }, +] + +[[package]] +name = "uvloop" +version = "0.23.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/42/02c739ce85fb2ee8d99212c61417da8140c6b87e9d97c430bea520d76044/uvloop-0.23.0.tar.gz", hash = "sha256:28d160f51ab4da3b187063652e643dea6831072add4adc1e6d62afbe73b6be27", size = 2559185, upload-time = "2026-10-01T03:17:04.4Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/98/04e766a6de99e6f7f955ecb7829e8d5a557de3427cb85be2236de54dda0c/uvloop-0.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:93935ab27b6eaef4c3e5489aebc84284f0644592f7ab516df60ee1b27eaf5eb3", size = 1393055, upload-time = "2026-10-01T03:15:42.526Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/8a/499e7b863a848ede009539bce39806b66205da5f8779354228e785601144/uvloop-0.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4448e9124537620f9c25d004c227bb5104440b58955c19bbd312d910af919a63", size = 768909, upload-time = "2026-10-01T03:15:43.974Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/95/a880f8ce3b87ac5b307c354e8ee480be4658d24bf01f87921d57e3530b4a/uvloop-0.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7548ede3ee908cfabc0d068106e303a9a2d811af959cdf6ab85676344cedcda", size = 4419106, upload-time = "2026-10-01T03:15:45.551Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/27/c1d2f9fa977f8f42ea294604166df10e0027e6dc6cd17f85ede386c9bf36/uvloop-0.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:090865d8ce7a03986755a3ce711b7dd0d4b44eb14ab74368b717f3fad1180208", size = 4532597, upload-time = "2026-10-01T03:15:47.258Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/dd/2cb6a2c8a30ca55c07a882dd4ae4ceae0fa7d8c15b25b3b7cb9a4b6cf4ca/uvloop-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd6f2f81c7b9da99d301c0b16b82044e76fe887086e42e1590ecf520b94dbdac", size = 4230048, upload-time = "2026-10-01T03:15:49.119Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/52/29989cbaa4022dc4ef35c1dd60a4ab989e4c2065f341ed483ae71d2bd950/uvloop-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a6ac96da66c35bf789bdcde78a88dc7d56b7907d8379648c54adc1c61594575d", size = 4394152, upload-time = "2026-10-01T03:15:50.829Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/83/eb980d64e6dd5da46d4dc35755fa6afd6b5b47141437cf89615f1117c5a6/uvloop-0.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2dcff2d69be43e6559e5dad2c5a7a2dbfb60e05a77311b6c4b7a4a8123d86c65", size = 1412726, upload-time = "2026-10-01T03:15:52.49Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/c1/02a725e7698134c647904bdee6589e2be14a0e7fc9942c74f86e2b90d48b/uvloop-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19c64108b507cd0bc140e400e3396bacebd9d504956aa7726272bf6de7d9aabb", size = 779071, upload-time = "2026-10-01T03:15:54.02Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/1d/cde53c79e8c01884ad1cdca8e407e086d523362cfe4139e2c2a8dde27304/uvloop-0.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1748321e3c59a14a75404b1ae8d5a8d81c4e201803ea0e14c1b6fd84421024b5", size = 4395323, upload-time = "2026-10-01T03:15:55.549Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/54/b12915bebbf99d7ae0796211e7f5977b95f069830dca45dc1a346d84125d/uvloop-0.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2cba180d6451822763eda8364f342435a873bcfb3849cbd82fdeca248ca65eb", size = 4480449, upload-time = "2026-10-01T03:15:57.362Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/8e/da6de68c31549a052a105fc76f5a9a204f6df22cb0909440aa4dbb06f9a2/uvloop-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dc61e4f9e37b507069dc7e659ae28bca7adcb04c993c3508214315d12c63f848", size = 4219177, upload-time = "2026-10-01T03:15:59.351Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/c3/1b53c6a89dc9c9d5cb75eb9a0b891ad69b32e1421ad3aa01617a9cbdcc78/uvloop-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7337b06a9f9ed9ea3049f04b76f65819db9b19bb832ee598e97b388eadf25e5f", size = 4346132, upload-time = "2026-10-01T03:16:01.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/a4/00e85345871c59c834a23c136c1771205856028ecc8ba940b3951178e59b/uvloop-0.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b90397a50ad6332ed3e459c648ac20d182cce24a557354363ad85fc9ea4a17cd", size = 1421363, upload-time = "2026-10-01T03:16:02.599Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/a9/e5f0f3cfde30af3ec32eba8ec07bccdba2b5116afbd1ecc53edfeb0a0790/uvloop-0.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:be53e1d5f83de43dc175c87612ecc128d444b38e5c56cb3f807f5a73d6887476", size = 785177, upload-time = "2026-10-01T03:16:04.018Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/79/9ddf78f8cd75a15c14a09a57f59c587b8cd9d82802c5c8368b9c3ebefa0b/uvloop-0.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b3cbc4f96ddfa1fb88a78a69dd851369825b7816d9702eee8c4461505ba172e", size = 4381060, upload-time = "2026-10-01T03:16:05.642Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/20/57d63c44d32326878fcad5c63854afc9deb394ed95673c1b1a429178c79d/uvloop-0.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31e0cf90bc8fd88784f6802cdba968a51fb1aec1cc3feec74d862b2d371d1330", size = 4418891, upload-time = "2026-10-01T03:16:07.326Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/c5/0795abecda2cc3dfe41033f880a32a9ff103be4e6b177ac736833c153a0e/uvloop-0.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8ed556fcc87a4091cf61587ef172fa104323dc89ecc085a618ba7ff8629a8f", size = 4214811, upload-time = "2026-10-01T03:16:09.13Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/18/9010dacd5221eec1bd79a4a83ac68f3db6a42d7bb657f7b640c4838ca6b6/uvloop-0.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f3fbfe82829d8e381426a289b87e59e585278728361db9ce975b88b51f64f410", size = 4294876, upload-time = "2026-10-01T03:16:10.875Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/08/f6384a03c771d00067cba4f542a69b2fc1a982e9fd78b357c2f788678d72/uvloop-0.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7e35c9bc977760981693e1a7a51493b58ee5a501f9ebb1e547565ee40b6c6208", size = 1494811, upload-time = "2026-10-01T03:16:12.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/01/756a4fb24a449f313cf4a153eb0c6210b49cfe5539255ec9fb1e17d2c4ef/uvloop-0.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5bb9be71d9ee39b4359b832f9569518ec9bc08704194034e79e4958e6bc4d46d", size = 819396, upload-time = "2026-10-01T03:16:14.094Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/45/e314b0c600b14f53dad3a3c2d7a922a249a88225fd727652b53e1854b9dd/uvloop-0.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e84575f11873c109cf3962ad0bdf679094466184125f4cadcc41a73febff41f", size = 4734966, upload-time = "2026-10-01T03:16:15.815Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/0d/8686a7f0b1b2d55ebd770ba21f8e0e4ffa0cde5ab738f43ffb8264499052/uvloop-0.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbbdb8fcd5e7062e546eec1ac78c28bb21ae7df54c18f8e4b06e15a18d661a49", size = 4584963, upload-time = "2026-10-01T03:16:18.198Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/b2/034a2d47e435ac02357c42956246887167bdc0357bdd6ad31c5f6d94497b/uvloop-0.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76345f51367fb1f23e08605c6efb18374f669be5b223658fbab6b17627950507", size = 4421388, upload-time = "2026-10-01T03:16:19.953Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/77/131f4b583e6b4b715c404a66b51c812d701db20f25c9018b188a2b00062c/uvloop-0.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7ef4701a96553514b2688e342ef1bf2beae6cfd172d89a76c768292aabf405", size = 4402414, upload-time = "2026-10-01T03:16:21.716Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/3d/ee11f4718ea1280595c67ed25c83d4c92115dc100bbdfd192d3ed9339168/uvloop-0.23.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f1341c6abcee1c31277cfe28d34e46196f2143ec3d755e6efe7452126e1f626d", size = 1418095, upload-time = "2026-10-01T03:16:23.241Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/0c/7ca516a0671418517d79a09d3ff2ccbb44af94c75711afa6e4cf58aa6f65/uvloop-0.23.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:e095f9e105af76593b4c183bb0bcbdae64bd913a59ec595732dc108b48730ab5", size = 784837, upload-time = "2026-10-01T03:16:24.666Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/95/75d4e28e596d505b7ae11de517646b4ca3d369fb8537ba755410380da11a/uvloop-0.23.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f673d835bdb1a60229cc3609a113fd2c9ce3f4a3c75ad4eaed111180c00199d2", size = 4380276, upload-time = "2026-10-01T03:16:26.389Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/99/68daf827ad62efaf4667d1f3fda127046d42161178396bdd93aab3684082/uvloop-0.23.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f23f403a273900d57de6ee5ca0614c650f7f58563065dad1a4744498960e53", size = 4451496, upload-time = "2026-10-01T03:16:28.364Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/69/f67e696ee688f426a96f99099bae26fec14a1d0fa75dccdd6518ee267c0c/uvloop-0.23.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cbe8d03d4efcccdb7fcedecbaa1e1fa02913eaf3a74cb933634a6bc6d2ea9e2a", size = 4212541, upload-time = "2026-10-01T03:16:30.014Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/6a/c8c436a9d7453297b4be70bdf6a9f9fc9400da45e0059ddf7b28ab63f4c7/uvloop-0.23.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:4f1798f56c6f4ba5ac11fa2869e5717926e4470d97a1dd42b4f59219d43b5027", size = 4319377, upload-time = "2026-10-01T03:16:31.705Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/2c/8fc15a03489299aab8a6212dfe0f137dc39836f915c87f7fd9d9ddd814de/uvloop-0.23.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:098a85e1393ef5202767b7e5fb41a32cd8bd81e6ee4af364c179801c4aa3f6d4", size = 1493428, upload-time = "2026-10-01T03:16:33.859Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/7c/05e4a210790229607f71460fcb2ed4a2c7bc72668d8a928ce577c22e38f8/uvloop-0.23.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:5a2bbad3a63007f7e9524d4903ba04fee252557c2acd86f9a3d4f91786695254", size = 818115, upload-time = "2026-10-01T03:16:35.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/14/a40b11c6c024213803b13955664a15754c72f64c873a33d986b26ec9ff5b/uvloop-0.23.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a08875543bbd4519faf30497506c9cda8a48470467ffdf967c7313c7a5981a8", size = 4734149, upload-time = "2026-10-01T03:16:37.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/83/f421a077712c1e87603bfec62744c3cd3a2f4b47378025db3d740df9af0d/uvloop-0.23.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12634f15e6625f78b3f2922f91404c4d7173487eba11746764153f556e9852dc", size = 4661763, upload-time = "2026-10-01T03:16:38.719Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/62/25dcaa6b7e7b48f82ce633854ce96597ab768f9650931f4f86c572de392c/uvloop-0.23.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:378188efbb1524f2219d05246a3e1e5907217848d2882144dff59585f1b81d55", size = 4421324, upload-time = "2026-10-01T03:16:40.488Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/46/04628239b43dcef703af314202a3307d6060918e2d76aa86c5b1188f5551/uvloop-0.23.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4b8e207c67d207a8608fec57e116511030af3495dc0109b8c333cf9cb412b16f", size = 4462501, upload-time = "2026-10-01T03:16:42.359Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.3.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/68/e6aa0b77d217b31f8f486ec0cdfe5e00e6e38dc0be657e7d85819b9faf0a/watchfiles-1.3.0.tar.gz", hash = "sha256:99aee4a07847c06820765fd7b1b49ceac4f3f711ccb7d104655a33231de1c207", size = 108119, upload-time = "2026-09-21T09:08:55.664Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/fa/c0b840d5d8bafe640925408ce11095948c8945bb61be1e67d0ab629b872a/watchfiles-1.3.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:000b9688fc8133037a8b075c8ebf98f32844ff8964dda61e85c1db547dafc441", size = 399703, upload-time = "2026-09-21T09:08:13.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/8a/894799b485fe9473ad10422a0d9668e53fb78e3a2b5cc6061159572844ad/watchfiles-1.3.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:bbc1198edfdc90fda0600f825aa94150f428dfcbf8138746f55998e0e660d64c", size = 397647, upload-time = "2026-09-21T09:08:15.16Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/61/f277cd5ead05f5d37b98b41fbdd0c943ee0c04d49738f62e902cb291cf43/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4af9c464d410e8c44b58ddf1cbec7c3a02c1cb2c6c80df9c74659ba2fbda0ab5", size = 454994, upload-time = "2026-09-21T09:08:16.587Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/66/ea4c01382975e53b1ac1b0a0ea1b353416b8127fb6e04f5c1c313119aab6/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb94e40b9a0db19636b5e3fed0a9803452ecc7381df13b385272abb779ee83ca", size = 460105, upload-time = "2026-09-21T09:08:17.961Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/c3/1a501b597817096c9831c255f6270c124ce0e2faeb74124be24dcc93b86d/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7398930a2b76b9dc67a4e6b8bfc7baf30946892a86f417a3920c07bdd6febc30", size = 493761, upload-time = "2026-09-21T09:08:19.327Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/16/77352d05e152c7786a016e9f1decdb4507e8aa5bcf3343c72142a0413d14/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:025b108f2d5cc2799cb941f79380df3e8b22bfb589c218e801767fe78d598c95", size = 577500, upload-time = "2026-09-21T09:08:20.723Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/fe/81001c55b24466fe9911135e50269fd3a29f43e446627a92adfab54553b6/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9ef3cd403d756a304a0a08a112163c63da7ec72c8c9d083c98818291ff8f29", size = 469209, upload-time = "2026-09-21T09:08:22.346Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/cf/e3ad894ed5909a7c44b0707e89bc25d19a3d78b3a7aec846119443b6ae56/watchfiles-1.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5768b49e426fd5b550b012c866db347cdf15c398ef98dc557b6e6b72fa74cd1", size = 458353, upload-time = "2026-09-21T09:08:23.7Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/f5/2f106829e6e2bc91ad74614730f6df484202b9a4d20d927dbf2f08d37d44/watchfiles-1.3.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:d36c72fcdc08f143d87316a4c4c3b064e340da2c7383d39803950740ed418935", size = 468707, upload-time = "2026-09-21T09:08:25.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/37/2741ab776b978669c1f3bba1e573f1a184f727ab8b4d3a6d9c61090800ed/watchfiles-1.3.0-cp310-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c9865240e065a2247f4b5248528ae3d01e0e8ae071462c250c884dc16e3ee3", size = 632510, upload-time = "2026-09-21T09:08:26.532Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/64/db4a4684275fe9b696ee5118c194705c1423b3d42181c3f8ee70194d5c73/watchfiles-1.3.0-cp310-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:380f513d26cc2e598266b88d66456e07d67ef4be8f0f435a1154d9c44b43d509", size = 661723, upload-time = "2026-09-21T09:08:28.004Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/80/95ddb24f6a2f595c031c24432ab7b8aa7d6d1ada0c6d63a552166e06e8d1/watchfiles-1.3.0-cp310-abi3-win32.whl", hash = "sha256:509d9f74d2bec5c1f4cd868ddcfe0c9601f7ea53066dfcb19d8ede742c4de661", size = 277761, upload-time = "2026-09-21T09:08:29.335Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/a1/1c4a1b3c8030cd03206232612be8a6e8799093490c220b0fcb935d02b75a/watchfiles-1.3.0-cp310-abi3-win_amd64.whl", hash = "sha256:1acabde19b67e673274e89a04d613b7fb1f122e2c4b8ec9a581e59c149264b61", size = 290643, upload-time = "2026-09-21T09:08:30.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/e0/d2db577c632d26d08078c4390dd870b8555c4e558df25d22270f0166c71f/watchfiles-1.3.0-cp310-abi3-win_arm64.whl", hash = "sha256:d978cc1dd7ba44f5590d7de74f7a9c8be7262d9e469c2c0efb8bbfc2574321dc", size = 285510, upload-time = "2026-09-21T09:08:31.561Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/55/a122743607939429ab6a7272672f49303aa02bbd34c3f06676765a3861de/watchfiles-1.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:e0204f90677b7fb8d3a824279a71a01166bea9f6e53923b8056592d87b33b0b6", size = 395978, upload-time = "2026-09-21T09:08:32.747Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/9d/0ee1b0ea42acfe72afa2484f0222e2386dd09992e9034a81e63cc3cdab0a/watchfiles-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c1125e99f84934a92996ae343501285617373acd66f16b59fd5654b27ad8d80", size = 387997, upload-time = "2026-09-21T09:08:33.953Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/f2/4319c6b683e8bd28465dfc53c750617a2a07f072eb2e3ab0182ccf812229/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a85e243c87109d9f48a2e0d10d6abdee8768cc114848b32281ec83d7f81d86ef", size = 448067, upload-time = "2026-09-21T09:08:35.104Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/71/9f37ac192a51a24de3a2d7fe9aac1e99b2b2668300674ff7a9f85d95beb1/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd538c71766c59c732e2c5cedc39323c99c11897b99f93eac773438e54af206a", size = 452881, upload-time = "2026-09-21T09:08:36.238Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/07/1224667c32a4a96876ddd642da69733cbfe6a15897db0865aec5a882cebc/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da213822ec9082b62cadf3008e07a8c690ec7a3c89f6dc5e55a8cf07b56a1b26", size = 484802, upload-time = "2026-09-21T09:08:37.717Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/4d/7f29cd87e1f826df86fc0418d0b8f370ca0311c29c618406dc492aad9951/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4593392e87669836670f87d62fc754059eaeb0e6157af44060a46558cebe5a3", size = 567522, upload-time = "2026-09-21T09:08:38.94Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/ac/3aa1f84abd4dbbfbae99a817dd931f77b439261c911189cad4c30fdfd470/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26b81bc515a0f03f66a69a6fea86eea54c8eca4173c46dd7222569f79dd2f977", size = 461997, upload-time = "2026-09-21T09:08:40.142Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/1c/27c66764d5b0dbb70a36770a32eca217eba739c28b24e8583a035087a3d7/watchfiles-1.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca39661934749df580d89f3dde266f2af7c6968f4c21c72ce2dc387d299e6aa7", size = 451856, upload-time = "2026-09-21T09:08:41.305Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/b3/a45827084d953c1d0dd3d75abf85dcac26a0f5d115ad2fe0b7e9e931573b/watchfiles-1.3.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:04c5500faa725d69a99b0f63750e80795e455bb951849e9c98a811b4324997c4", size = 459889, upload-time = "2026-09-21T09:08:42.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/3d/07740d23ce2bb23419bb24055c435a9abe5fe03efdaea1ba0720b831aab6/watchfiles-1.3.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:ec3cd4bb181a7b6329134204c2277c7144ab5f62cce9c5335d6f901ee7ce8d6a", size = 624482, upload-time = "2026-09-21T09:08:44.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/46/9540f8123eaec86df989f26d7704cad0448d9fb7271763a4f8f9fe23617d/watchfiles-1.3.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e5871d4a7f788a9f64fb06f5793389d29d171e5c32d37c3ebfd520b017cc7694", size = 653724, upload-time = "2026-09-21T09:08:45.257Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/9d/b9deb52f1e4528ffe44338c5ebab95c32ed79203082da661de50288e130a/watchfiles-1.3.0-cp314-cp314t-win32.whl", hash = "sha256:81c989d7267bfb7b676cd32c83bec97914e396870983cbf493e29415037bff5e", size = 270863, upload-time = "2026-09-21T09:08:46.518Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/59/8b1881afccbafda06248be4185e28034dd73c80c85e4da24f2cb0c1321eb/watchfiles-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d754e049006e81a9be49dc78ba7802081cee426e968cbc63ee19e164fa2d0e39", size = 286879, upload-time = "2026-09-21T09:08:47.874Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/dd/41c12ae9745d5d72f709333287679d71cbba68ad7678fed872207bba94f4/watchfiles-1.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a5b631db08fd3032db57ec6a92d1777cd60f88803287e4e941a4858c53aab1e0", size = 276924, upload-time = "2026-09-21T09:08:48.976Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +]