From 378128fc16219e9d543fceeb5a8bc2682b0b8b9f Mon Sep 17 00:00:00 2001 From: chakcy <947105045@qq.com> Date: Thu, 27 Aug 2026 09:49:47 +0800 Subject: [PATCH] =?UTF-8?q?modify:=20worker=20=E7=AE=A1=E7=90=86=E7=AB=AF?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .trae/documents/修复worker重启500与未响应.md | 42 ++ README.md | 112 ++++- config.toml | 9 +- pyproject.toml | 2 - src/chongming/app/__init__.py | 9 +- src/chongming/app/core/lifespan_setup.py | 34 +- src/chongming/app/core/worker_manager.py | 421 ++++++++++++++++++- src/chongming/app/routes/__init__.py | 3 +- src/chongming/app/routes/worker_admin.py | 238 +++++++++++ uv.lock | 240 +++++------ 10 files changed, 967 insertions(+), 143 deletions(-) create mode 100644 .trae/documents/修复worker重启500与未响应.md create mode 100644 src/chongming/app/routes/worker_admin.py diff --git a/.trae/documents/修复worker重启500与未响应.md b/.trae/documents/修复worker重启500与未响应.md new file mode 100644 index 0000000..b534083 --- /dev/null +++ b/.trae/documents/修复worker重启500与未响应.md @@ -0,0 +1,42 @@ +# 修复 Worker 单副本重启 500:优雅处理「副本未响应管理指令」 + +## 摘要 +单副本重启端点 `POST /api/v1/admin/workers/restart` 在目标 worker 不在线/无 `__admin__` 管理能力时,`_send_admin_cmd` 抛 `RuntimeError`,端点未捕获,被全局异常处理器记录后 re-raise,最终 ASGI 返回 500 并伴随 `net::ERR_FAILED`。需让该端点对「未响应/不在线/运行旧版」返回明确的非 500 结构化错误,同时改进错误文案以便运维定位。 + +## 现状分析 +- [worker_admin.py](file:///home/chakcy/chongming-zenoh/src/chongming/app/routes/worker_admin.py#L60-L80):`restart_worker` 直接 `await manager.restart_worker(...)`,无 try/except。对比 `restart_all`(L107 已捕获单副本失败不中断)、`update_worker_config`(L169-178 已捕获映射 4xx),唯独单副本端点无兜底。 +- [worker_manager.py](file:///home/chakcy/chongming-zenoh/src/chongming/app/core/worker_manager.py#L507-L553):`_send_admin_cmd` 在 Zenoh QUERY 超时/无回复时 `raise RuntimeError("副本 / 未响应管理指令(可能不在线)")`(L544-547)。 +- [__init__.py](file:///home/chakcy/chongming-zenoh/src/chongming/app/__init__.py#L244-L255):全局处理器仅记录 `logger.error(f"全局异常: {exc}")` 后 re-raise → 500。 +- 底层:报错实例 `worker_id=worker-0-52047` 是**旧版命名**(`worker--`,无 epoch 段)。新版 runtime 才生成 `worker---` 并声明 `__admin__`。故该副本极可能仍运行**旧版 chongming_worker**(未加载管理能力),管理层才会未响应。 + +## 变更方案 + +### 1. 单副本重启端点兜底 [worker_admin.py](file:///home/chakcy/chongming-zenoh/src/chongming/app/routes/worker_admin.py) +- 在 `restart_worker` 中 `try: result = await manager.restart_worker(...)`。 +- `except Exception as e:`(noqa BLE001)→ `logger.warning(...)` 记录;若错误含「未响应/不在线」,抛 `HTTPException(status_code=502, detail=f"重启失败: {e}(该副本可能不在线或运行旧版 Worker,未加载管理能力,请先以新版重启其进程)")`;其余错误保持映射 502/500 语义(统一用 502 + detail=str(e) 即可)。 +- 效果:不再触发全局异常 handler / ASGI 500;前端 `restartOne` 已在其 catch 中 `ElMessage.error` 展示 `detail`,用户得到明确提示。 + +### 2. 优化下发错误文案 [worker_manager.py](file:///home/chakcy/chongming-zenoh/src/chongming/app/core/worker_manager.py) +- 将 L545-547 的超时错误文案改为: + `"副本 {worker_type}/{worker_id} 未响应管理指令(可能不在线,或该 Worker 运行旧版运行时未声明 __admin__ 管理能力)"` +- 便于网关日志与前端 detail 直接反映根因。 + +### 3. 前端(可选,本次不强制改) +- `restartOne` 已能在非 2xx 时展示后端 `detail`,无需改动。仅确认错误提示文案来自后端 detail 即可。 + +## 假设与决策 +- 「未响应」属**预期可解释的失败**,应返回 5xx(502)而非 200+ok:false:前端只对非 2xx 弹错,保持「消息清晰」。 +- 不修改全局异常处理器:它是通用兜底,无需为此特化。 +- 不修改 alive 判定/列表逻辑:`alive=true` 仅代表 liveliness token 存在,无法区分旧版进程;根因修复依赖「用新版 runtime 重启 worker 进程」,属运维动作,不写代码。 + +## 验证步骤 +1. `.venv/bin/python -m py_compile src/chongming/app/routes/worker_admin.py src/chongming/app/core/worker_manager.py`。 +2. 重启网关(让其加载 CORS 与最新代码)。 +3. 用仍为旧版命名/未响应的 worker(如 `aibox_sophon_adapter/worker-0-52047`)调用 `POST /api/v1/admin/workers/restart?worker_type=aibox_sophon_adapter&worker_id=...`: + - 期望返回 JSON `{"detail":"重启失败: 副本 ... 未响应管理指令 ... 请先以新版重启其进程"}` 且 HTTP 502,不再打印「全局异常」,前端弹明确错误。 +4. 对在线且加载新版 runtime 的 worker 重启,验证仍返回 `{"success":true}`(未破坏正常路径)。 +5. `cd src/chongming-web && npx vue-tsc --noEmit`(如需改前端才跑;本次预计无前端改动)。 +6. (可选)确认 Worker 管理重启按钮不再触发浏览器 `net::ERR_FAILED`,而是展示后端错误消息。 + +## 前置/运维提示(不写代码) +- 要让 `aibox_sophon_adapter` 等 external worker 能被单副本精细化管理(重启/停止/config_set),需用本项目最新 `chongming-worker`(`uv pip install -e utils/worker` 已确保)**重启该 worker 进程**,使其加载新版 runtime 并声明 `worker///__admin__`。 \ No newline at end of file diff --git a/README.md b/README.md index df0ae8d..d285e5b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ | **流媒体代理** | ffmpeg + MediaMTX RTSP 流转发 | | **构建打包** | PyInstaller / Nuitka 一键打包为独立可执行文件 | | **Vue 3 前端** | Element Plus UI、Pinia 状态管理、类型安全的 API 客户端 | +| **Worker 运维管理** | 前端经 Zenoh 向单个副本下发 restart/stop/改配置指令(超管),前端可视化列表 + 分页管理 | --- @@ -207,18 +208,107 @@ uv run serve → FastAPI lifespan: 2. setup_zenoh() — Zenoh peer 会话 3. setup_mqtt_broker() — 内嵌 amqtt Broker(子进程 + 文件锁单例; `use_embedded_broker=false` 时跳过,改用外部 Broker) - 4. setup_worker_manager() — WorkerManager + 定期 liveliness 刷新 + 4. setup_worker_manager() — WorkerManager + 定期 liveliness 刷新 + 网关登记(register_gateway,参与全局分摊)+ 该插件副本数经 resolved_count 按 launch_worker_count 从 cache/toml 解析 5. load_workers() — 按插件 [workers].load_mode 分派: inline → 进程内 import 插件 → 启动 Worker 协程 external → 跳过(独立进程运行,盘扫条目仅观测) 5.1 setup_route_registry() — 常驻订阅 chongming/routes/**,external 插件路由动态挂载/摘除 6. validate_contracts() — inline 插件三方契约校验,失败终止启动 7. register_routes() — inline 插件盘扫生成 HTTP 路由 - 8. setup_mqtt() — MQTT Bridge(inline 主题静态收集 + external 主题动态推送) + 8. setup_mqtt() — MQTT Bridge(inline 主题静态收集 + external 主题动态推送;基于共享缓存的分布式锁保证全局单实例) ``` --- +## Worker 管理系统 + +通过 Zenoh 向 `worker///__admin__` 管理端点下发指令,**精细化控制单个 Worker 副本**(inline 与 external 进程插件共用同一管理协议)。提供 REST 管理端点 + Vue 前端页面,操作需超级管理员鉴权。 + +### 管理协议 + +每个用 Worker 框架(`chongming_worker.runtime.create_workers`)创建的副本都会注册一个管理 Queryable: + +```text +worker///__admin__ +``` + +| 指令 | 说明 | +| ----------- | ---------------------------------------------------------------------- | +| `status` | 查询副本状态 | +| `restart` | 重启该副本,副本槽位(index)由管理循环驱动,重新生成新 `worker_id`(`worker---`)并经 liveliness 更新 | +| `stop` | 停止该副本 | +| `drain` | **优雅排空**:摘除 liveliness/业务 Queryable 隔绝新请求,等待正在处理的在途请求完成后再退出,管理循环不重建(数量-1) | +| `rescale` | 按 `data.scale` 把**当前进程内**该类型副本数调整为指定值(`data:{"scale":N}`),并把 `[workers].count` 写回本进程侧配置 | +| `config_set`| 更新 `[workers.config]` 运行时配置(路由配置除外);成功则写回配置并重启该类型全部对应副本,任一失败返回失败信息 | + +### REST 管理端点(仅超管) + +| 方法 | 路径 | 说明 | +| ------ | --------------------------------------- | -------------------------------------- | +| `GET` | `/api/v1/admin/workers` | 盘点全部副本(inline 本地注册 + external liveliness 发现) | +| `POST` | `/api/v1/admin/workers/restart` | 重启单个副本(`worker_type` / `worker_id` 查询参数) | +| `POST` | `/api/v1/admin/workers//stop` | 停止 single 副本 / 某插件全部副本 | +| `GET` | `/api/v1/admin/workers//config` | 读取该插件的现有 `[workers.config]`(展示当前值) | +| `POST` | `/api/v1/admin/workers//config` | 更新配置(body `{updates:{k:v}}`),下发并重启 | +| `POST` | `/api/v1/admin/workers//scale` | 调整该类型副本数(body `{count:N}`),扩缩容 + 持久化 | + +> 旧版运行时未声明 `__admin__` 管理能力时,管理指令会超时(HTTP 502,见日志提示)。 + +### 配置热更新(路由配置除外) + +- worker 回写插件 `config.toml` 的 `[workers]` 段(保留其他段),**拒绝写保留键**(路由/加载控制键如 `type` / `module` / `count` 等)。 +- `Plugin.reload(new_config)` 在互斥锁下先 `on_cleanup()` 释放旧资源、再 `on_init()` 用新配置重建客户端(如 AI/DB client),使配置**立即生效、无需重启网关**。 +- 仅 index0 副本落盘避免并发写冲突,其余副本同样应用新配置;任一失败则返回失败明细且不触发重启方式的成功确认。 + +### 副本数量管理(扩缩容) + +同类 worker 的副本数可随时调整,并**持久化到配置**(重启网关后保持): + +- **扩容**:新增副本槽位(`WorkerPool` 新增 `_managed_worker` 管理循环,新副本注册 liveliness 后可被负载均衡)。 +- **缩容**:随机挑选多余副本触发 **`drain`(优雅排空)**——先摘除 liveliness/业务 Queryable **隔绝新请求**,等待正在处理的**在途请求完成**后再关闭该副本(`in-flight` 计数实现),避免中断业务;管理循环不重建。 +- **目标数量持久化**:`rescale` 把 `[workers].count` 写回 `config.toml`,网关/进程重启后按新数量加载。 +- **统一协议**:inline 与 external 共用。网关对 inline 直接调整本进程 `WorkerPool`;对 external 经 Zenoh 向任一存活副本下发 `rescale`,由其**所在进程内**的池扩缩并写回自身配置(无需重启整个进程/容器)。 + +### 全局副本数协调(多网关) + +多个网关共享同一份 `chongming-cache`(根 `config.toml` 的 `[cache]` 段)时,副本数「count」语义为**该类 worker 的全局目标总量**,由 chongming-cache 作为**单一事实来源**,各网关按在线数量自动分摊。这样只在任意一台网关执行扩缩容,即可全局生效。 + +- **单一事实来源**:`count` 存于 cache 键 `worker:cm:counts:`,在线网关 id 列表存 `worker:cm:gateways`,各网关分摊配额存 `worker:cm:quota::`(可观测)。 +- **网关登记/注销 + 心跳租约**:网关启动时 `register_gateway()` 加入在线列表,并每 15s `heartbeat_gateway()` 刷新自身时间戳;超过租约(60s)未心跳的死网关会被自动清理(crash/kill -9 也不残留,避免配额失真)。当前由 `CHONGMING_GATEWAY_ID` 环境变量指定(多实例需唯一,尤其**同宿主多网关必须显式指定**;缺省回退 `hostname`,稳定且重启不变,不残留);关闭时 `unregister_gateway()` 移除,其余网关实时重算分摊。 +- **分摊算法**:`own_quota()` 将全局总量按在线网关 sorted(id) 均分,余量按序补前若干。`/admin/workers//scale`(body `{count:<全局总量>}`)写全局 count 后,各网关把自己的 session 副本调到自己的 quota。 +- **共享方式与后端**:单实例本机 `diskcache` 即可;**多实例必须共享同一 cache 后端**(同一共享卷的 diskcache 或 redis),否则各写各的 cache 无法收敛成全局量。 +- **启动优先级开关**:根 `config.toml` 的 `[cache].launch_worker_count` 决定网关启动时副本数的来源: + + ```toml + [cache] + backend = "diskcache" # "diskcache"(默认) | "redis" + cache_store_path = "./cache_data" + launch_worker_count = "toml" # "toml"(默认)=启动用 toml 的 count 覆盖并写回 cache;"cache"=cache 为单一事实来源,toml 仅在缺失时引导 + ``` + +- **进程插件(external)不受该开关影响**:网关只对 **inline** 插件用 cache 解析启动副本数(见「启动流程」第 5 步 `load_workers`);external 插件由外部编排按其自身配置 `config/workers//config.toml` 的 `count` 启动,不经网关、也不读 cache。运行时 `scale` 对 external 仍走「向存活副本下发 rescale」路径,同样与该开关无关。 + +### 前端管理页 + +`Worker 管理`(`/workers`,深色主题): + +- **登录**:经 auth 插件 `POST /auth/login` 领取管理 Token(无需手填 JWT),从 JWT 解析当前用户与超管标识。 +- **鉴权**:仅 `is_superuser=true` 可管理;权限不足时前端/后端弹出对应提示(403)。 +- **列表分页**:展示副本(插件类型 / 副本 ID / 模式 inline|external / 状态),内置分页。 +- **操作**:单副本重启、批量重启(选中)、插件级重启全部副本、更新插件配置(弹窗展示现有配置值,点行带入编辑)、**调整副本数量**(当前数 + `el-input-number` 设目标 + 应用,可扩缩容)。 +- **auth 核心插件**:配置不可更改,不在列表中展示(`Worker` 列表自动过滤 `worker_type == "auth"`)。 +- 调小副本数会**触发优雅排空**并在前端确认提示中说明。 + +### external(进程)插件同样可管理 + +external 副本走同一 `__admin__` Queryable,路由发现、指令下发与扩缩容(`rescale`)与 inline 一致。**前提**: + +1. external 容器运行的是含管理能力(`__admin__` + `reload` + `rescale`)的新版 `chongming-worker` 框架; +2. `config_set` / `rescale` 由副本在**其进程 cwd** 下写 `config/workers//config.toml`,external 容器需挂载与网关同款配置目录才能写回网关侧配置(含 `[workers].count` 持久化); +3. 纯自定义语言(如 Rust `rsdemo`)进程插件需自行实现 `__admin__` Queryable + liveliness + 数量管理,否则只能发现、无法改写配置或扩缩容。 + +--- + ## 端点类型与路由注册 ### 端点类型 @@ -482,6 +572,23 @@ response_topic = "/board_ping/response" # 可选,响应主题;默认 "< - 多条自定义主题按配置顺序匹配,先匹配者优先;内置 `chongming/...` 接口最后兜底。 - 全局 `[.mqtt].custom_topics` 仍可作为通用兜底(不依赖具体插件时使用,可选)。 +### MQTT Bridge 单例(多实例免重复响应) + +MQTT Bridge 是**网关进程内**的 MQTT 客户端组件,负责订阅主题并把请求经 `WorkerManager` 转发给 Worker、回发 `/response`。为了让「同一条请求只被消费一次、只回一份响应」,Bridge 的启动采用**基于共享缓存的分布式锁**(`chongming-cache` 提供,`DistributedLock`),而不再使用本机文件锁。 + +- **抢锁机制**:启动时 `set_nx`(SET NX + TTL 租约)原子抢锁,key 为 `mqtt_bridge`;抢到锁的网关才启动 Bridge,其余网关跳过。 +- **租约续约**:持锁网关后台定期 `touch` 续约;若持锁网关宕机/失联,租约到期后锁自动释放,其他网关可重新抢到(重启后生效)。 +- **受保护释放**:关闭时仅当 token 仍匹配(仍是自己持有)才删除,绝不误删其他网关抢到的锁。 + +```toml +[cache] +backend = "redis" # 跨主机收敛必须用 redis(或共享卷 diskcache) +``` + +- **关键条件**:跨机器收敛依赖**共享缓存后端**(与「全局副本数协调」同一 `[cache]`,单一事实来源)。仅 `redis`(或共享卷的 `diskcache`)后端时,不同物理机上的网关才会看到同一把锁,真正全局单 Bridge。 +- **`use_embedded_broker = true`(各机内嵌独立 Broker)**:各机 Broker 互不相连、本身不产生重复响应;此时全局单 Bridge 只会连到持锁网关那台机器的 Broker。 +- **`use_embedded_broker = false`(共享外部 Broker)**:原先 N 台网关的 N 个 Bridge 会共同订阅同一批主题,导致一条请求被各消费一次、各回一份响应;改用分布式锁后全局只有一个 Bridge 消费,彻底规避重复响应。 + ### 请求负载格式(信封协议) MQTT 请求负载为信封结构,控制字段与业务数据分离: @@ -655,6 +762,7 @@ uv pip install -e utils/ # 安装/更新工具包子包 | 字段 | 位置 | 说明 | | -------------------- | --------------------- | --------------------------------------- | | `count` | `<插件>/config.toml` | Worker 副本数(`[workers]` 节) | +| `launch_worker_count`| `根 config.toml` | 启动时副本数来源:`"toml"`=用 toml count 覆盖 cache(默认);`"cache"`=以 cache 为单一事实来源(`[cache]` 节) | | `router_prefix` | `<插件>/config.toml` | API 路由前缀 | | `defaults.timeout` | `<插件>/config.toml` | 默认超时(秒) | | `defaults.cacheable` | `<插件>/config.toml` | 是否缓存 RPC 结果 | diff --git a/config.toml b/config.toml index 0faee59..cef57f3 100644 --- a/config.toml +++ b/config.toml @@ -17,6 +17,11 @@ job_store_path = "scheduler_jobs.db" backend = "diskcache" # "diskcache"(默认) | "redis" cache_store_path = "./cache_data" +# 启动时该类 worker 的全局副本数优先来源(指 config/workers//config.toml 的 count): +# "toml" (默认) = 经典模式:启动时用 toml 的 count 覆盖 cache 并写回为新的全局总量 +# "cache" = 全局模式:chongming-cache 为单一事实来源,toml 仅在 cache 缺失时引导 +launch_worker_count = "toml" + # redis 后端专用(backend = "redis" 时生效,均有默认值) # redis_host = "127.0.0.1" # redis_port = 6379 @@ -50,7 +55,9 @@ access_sample_rate = 0.1 allow_credentials = true allow_headers = ["*"] allow_methods = ["*"] -allow_origins = ["*"] +# 注意:allow_credentials=true 时 allow_origins 不能为 ["*"](starlette 会丢弃该 header), +# 需显式列出来源;本地前端 dev 为 http://localhost:5173。 +allow_origins = ["http://localhost:5173", "http://127.0.0.1:5173"] [development.mqtt] enabled = true diff --git a/pyproject.toml b/pyproject.toml index a4d58f9..c7862df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,8 +56,6 @@ 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" -[tool.uv.sources] - [project.scripts] gunicorn = "chongming:gunicorn_serve" serve = "chongming:serve" diff --git a/src/chongming/app/__init__.py b/src/chongming/app/__init__.py index 0eb071f..108394f 100644 --- a/src/chongming/app/__init__.py +++ b/src/chongming/app/__init__.py @@ -22,7 +22,7 @@ from chongming_config import get_config from .core.logger import get_logger from .core.static_files import SVFSStaticFiles from .core import lifespan_setup, call_stats -from .routes import storage_router, stats_router +from .routes import storage_router, stats_router, worker_admin_router config = get_config() @@ -49,6 +49,9 @@ async def lifespan(app: FastAPI): # 4. WorkerManager manager = lifespan_setup.setup_worker_manager(app, session) + # 4.1 全局副本数协调:把当前网关登记进在线列表,供按在线网关分摊 quota + manager.register_gateway() + # 5. 插件加载(混合模式:inline 进程内加载;external 由独立进程运行, # 其路由经 Zenoh 动态注册——进程编排交给 Docker/K8s,网关不拉起进程) worker_tasks, custom_routers, inline_types, external_types = ( @@ -117,6 +120,9 @@ async def lifespan(app: FastAPI): # 4.5 取消后台统计/审计任务 await call_stats.shutdown() + # 4.6 全局副本数协调:关闭时把当前网关从在线列表移除(其余网关将重新分摊) + manager.unregister_gateway() + # 5. 关闭 Zenoh 会话 session.close() logger.info("Zenoh 会话已关闭") @@ -270,3 +276,4 @@ if vfs_db_path := config[env].get("file_system", {}).get("path", None): app.include_router(storage_router) app.include_router(stats_router) +app.include_router(worker_admin_router) diff --git a/src/chongming/app/core/lifespan_setup.py b/src/chongming/app/core/lifespan_setup.py index 2ff0ae6..b69aa1a 100644 --- a/src/chongming/app/core/lifespan_setup.py +++ b/src/chongming/app/core/lifespan_setup.py @@ -8,7 +8,7 @@ import asyncio import socket import sys -from chongming_cache import Cache +from chongming_cache import Cache, DistributedLock from fastapi import FastAPI import zenoh @@ -259,7 +259,10 @@ def setup_zenoh(app: FastAPI) -> zenoh.Session: def setup_worker_manager(app: FastAPI, session: zenoh.Session) -> WorkerManager: """创建 WorkerManager,启动定期刷新任务,并存入 app.state。""" cache = Cache.from_config(config["cache"]) - manager = WorkerManager(session, 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()) @@ -429,7 +432,9 @@ async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerMana for wc in inline_plugins: module_name = wc["module"] factory_name = wc["factory"] - count = wc["count"] + # count 以 chongming-cache 为单一事实来源:cache 有值时用 cache 的全局总量, + # 否则用配置值引导并写回 cache(多网关共享同一 cache 时自动对齐)。 + count = manager.resolved_count(wc["type"], wc["count"]) worker_type = wc["type"] worker_config = wc["config"] @@ -608,12 +613,17 @@ async def setup_mqtt(app: FastAPI, worker_manager: WorkerManager) -> MQTTBridge # Broker 来源:use_embedded_broker=true 时内嵌 amqtt Broker 子进程已在 # lifespan 第 3 步启动(setup_mqtt_broker + 文件锁单例);false 时连接外部 Broker。 - # 原子锁确保只有一个 worker 启动 MQTT Bridge - if not _acquire_singleton_lock("mqtt_bridge"): - logger.info("MQTT Bridge 已由其他 worker 启动,跳过") + # 排他锁:基于共享缓存(redis / 共享卷 diskcache),保证全局只有一个 Bridge 消费。 + # 相比原文件锁,可跨多台主机收敛——多实例共享同一个外部 Broker 时, + # 避免一条 MQTT 请求被 N 台网关上各自消费、各回一份响应。 + bridge_lock = DistributedLock(worker_manager.cache, "mqtt_bridge") + if not bridge_lock.acquire(): + logger.info( + "MQTT Bridge 已由其他网关实例持有(分布式锁),本网关跳过," + "以规避共享 Broker 下单请求多响应" + ) return None - # 记录本进程持有锁,关闭时由持有者释放(避免误删其他 worker 的锁) - app.state.mqtt_bridge_lock_acquired = True + app.state.mqtt_bridge_lock = bridge_lock bridge = MQTTBridge( worker_manager=worker_manager, @@ -625,6 +635,7 @@ async def setup_mqtt(app: FastAPI, worker_manager: WorkerManager) -> MQTTBridge ) app.state.mqtt_bridge = bridge await bridge.start() + bridge_lock.start() # 后台续约租约,防止持有方宕机/长暂停导致锁被错误抢走 # 把 Bridge 挂接给常驻路由注册表(动态主题 + 补推已到达的元信息) registry = getattr(app.state, "route_registry", None) if registry is not None: @@ -640,8 +651,11 @@ async def shutdown_mqtt(app: FastAPI): bridge: MQTTBridge | None = getattr(app.state, "mqtt_bridge", None) if bridge and bridge.is_running: await bridge.stop() - if getattr(app.state, "mqtt_bridge_lock_acquired", False): - _release_singleton_lock("mqtt_bridge") + # 停止续约并受保护释放分布式锁(仅本网关仍持有时删除) + 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) diff --git a/src/chongming/app/core/worker_manager.py b/src/chongming/app/core/worker_manager.py index 6af4239..a3e46bc 100644 --- a/src/chongming/app/core/worker_manager.py +++ b/src/chongming/app/core/worker_manager.py @@ -1,7 +1,10 @@ import asyncio import concurrent.futures import hashlib +import os import random +import socket +import time import orjson from typing import Any, AsyncGenerator, Dict, List, Optional import zenoh @@ -10,6 +13,17 @@ from .logger import get_logger logger = get_logger("worker_manager") +# ── 全局副本数(chongming-cache 单一事实来源)── +# 多网关共用同一 cache(单实例=本地 diskcache,多实例=共享卷/redis), +# 「count」语义 = 该类 worker 的**全局目标总量**,各网关按在线数量分摊。 +_SCALE_DESIRED_KEY = "worker:cm:counts:{type}" # 全局目标总量(int) +_SCALE_GATEWAYS_KEY = "worker:cm:gateways" # 在线网关列表:[[id, 最近心跳 ts], ...](JSON) +_SCALE_QUOTA_KEY = "worker:cm:quota:{type}:{gw}" # 各网关分摊配额(int) + +# 在线网关租约(秒):网关心跳每 refresh_interval(15s) 更新一次自身 ts,超过 LEASE 未刷新 +# 视为离线的死网关,在下一次读写时被清理。容忍多次漏跳,且 crash/kill -9 不残留。 +_GATEWAY_LEASE = 60 + _SENTINEL = object() # 哨兵值,用于标记"No reply received" # Zenoh RPC 专用线程池:隔离于 asyncio.to_thread 的全局默认池 @@ -50,13 +64,133 @@ class WorkerManager: 2. 本地:通过 register_worker() 直接注册(用于同进程 worker) """ - def __init__(self, session: zenoh.Session, cache: Optional[Cache] = None): + def __init__(self, session: zenoh.Session, cache: Optional[Cache] = None, + launch_worker_count: str = "toml"): self.session = session self.cache = cache + # 启动时全局副本数的优先来源:"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 self._lock = asyncio.Lock() self._refresh_interval = 15 # seconds + # 当前网关心跳标识:优先环境变量(多实例需唯一,尤其同宿主多网关须显式指定), + # 否则回退 hostname(稳定,重启不变——避免用 PID 导致每次重启标识变化、旧条目 + # 残留撑大在线列表,进而让配额计算失真把副本全排空)。用于全局副本数按在线网关分摊。 + self._gateway_id: str = ( + os.environ.get("CHONGMING_GATEWAY_ID") or socket.gethostname() + ) + + # ── 全局副本数协调(chongming-cache 单一事实来源)──────────── + + def _scale_desired_key(self, worker_type: str) -> str: + return _SCALE_DESIRED_KEY.format(type=worker_type) + + def _scale_quota_key(self, worker_type: str) -> str: + return _SCALE_QUOTA_KEY.format(type=worker_type, gw=self._gateway_id) + + 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) + + def resolved_count(self, worker_type: str, default: int) -> int: + """获取该类 worker 的全局目标总量。 + + 依 [cache].launch_worker_count 决定启动优先级: + - "toml"(经典):始终用 toml 的 count(default)覆盖 cache 并写回为新的全局总量; + - "cache"(全局):cache 为单一事实来源,缺失时才用 toml 引导并写回。 + """ + if self.cache is None: + return int(default) + key = self._scale_desired_key(worker_type) + total = max(0, int(default)) + if self._launch_worker_count == "toml": + # 经典模式:toml count 覆盖并写回 cache + self.cache[key] = total + return total + if key in self.cache: + return int(self.cache[key]) + self.cache[key] = total + return total + + def own_quota(self, worker_type: str, total: int) -> int: + """在「count=全局总量」语义下,计算当前网关应承担的副本数(分摊)。 + + 单网关(本场景)即总量本身;多网关按在线 id 排序均分,余量按序补前若干。 + 结果写回 cache(observability),并在全局下发给各网关后各自按其 quota 调整。 + """ + total = max(0, int(total)) + gws = sorted(self._gateways()) + if not gws: + quota = total + else: + base, rem = divmod(total, len(gws)) + idx = gws.index(self._gateway_id) if self._gateway_id in gws else 0 + quota = base + (1 if idx < rem else 0) + if self.cache is not None: + self.cache[self._scale_quota_key(worker_type)] = quota + return quota def register_worker(self, worker_type: str, worker_id: str): """ @@ -79,6 +213,11 @@ class WorkerManager: 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 + 本地注册)""" @@ -105,6 +244,11 @@ class WorkerManager: 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}") except Exception as e: logger.error(f"刷新 worker 列表失败: {e}") await asyncio.sleep(self._refresh_interval) @@ -448,3 +592,278 @@ class WorkerManager: f"Worker '{worker_type}' 调用失败 (已尝试 {len(tried_workers)} 个 Worker, " f"共 {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,可直接调用其 set_target(进程内扩缩, + 无需经过 Zenoh 往返); + - external:类型由独立进程/容器运行,向该类型任一存活副本经 Zenoh 下发 + rescale 指令,由其**所在进程内**的 WorkerPool 扩缩并写回自身配置。 + + 缩容走优雅排空:摘除 liveliness/业务 Queryable 隔绝新请求,等正在处理的 + 在途请求完成后再关闭副本(避免中断业务)。 + + Args: + worker_type: 插件类型 + count: 目标副本数(>= 0) + timeout: 下发超时(秒) + + Returns: + {"worker_type", "target"(全局总量), "quota", "mode"..., "persist"|...} + + Raises: + RuntimeError: count 非法 / 类型无可用副本或本进程无其池 + """ + count = max(0, int(count)) + # 「count=全局总量」:写全局 cache 单一来源;本网关按在线网关分摊承担 quota。 + if self.cache is not None: + self.cache[self._scale_desired_key(worker_type)] = count + quota = self.own_quota(worker_type, count) + logger.info( + "[scale] %s 全局目标=%d,本网关分摊 quota=%d", worker_type, count, quota + ) + # inline:优先走本进程内池(按分摊配额),直接且即时应答 + try: + from chongming_worker.runtime import get_process_pool + pool = get_process_pool(worker_type) + except Exception: # noqa: BLE001 - 运行时缺依赖时退回 Zenoh 下发 + pool = None + if pool is not None: + await pool.set_target(quota) + persist = "未持久化" + if self.cache is not None: + persist = f"count={count}(全局)已写 cache,本网关 quota={quota}" + else: + persist = f"cache 未启用,quota={quota}(重启后将按配置还原)" + logger.info( + "[scale] %s inline 池已调整至 %d 副本(全局目标 %d)", + worker_type, quota, count, + ) + return { + "worker_type": worker_type, + "target": count, + "quota": quota, + "mode": "inline", + "reply": {"status": "accepted", "cmd": "rescale", "persist": persist}, + } + + # external(或本进程无池):下发 rescale 给任一存活副本,由其进程内池扩缩+持久化 + 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"], "rescale", + timeout=timeout, data_extra={"scale": count}, + ) + return { + "worker_type": worker_type, + "target": count, + "mode": "external", + "sent_to": candidate["worker_id"], + "reply": reply, + } + + 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 写入插件 config.toml 的 [workers.config](原子写回, + 路由等保留键会被拒绝); + - 所有副本合并 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 index dabae44..d9a7391 100644 --- a/src/chongming/app/routes/__init__.py +++ b/src/chongming/app/routes/__init__.py @@ -1,4 +1,5 @@ from .storage import router as storage_router from .stats import router as stats_router +from .worker_admin import router as worker_admin_router -__all__ = ["storage_router", "stats_router"] \ No newline at end of file +__all__ = ["storage_router", "stats_router", "worker_admin_router"] \ No newline at end of file diff --git a/src/chongming/app/routes/worker_admin.py b/src/chongming/app/routes/worker_admin.py new file mode 100644 index 0000000..51ad069 --- /dev/null +++ b/src/chongming/app/routes/worker_admin.py @@ -0,0 +1,238 @@ +""" +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 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] 现有值(供前端展示)。 + + 直接从插件配置文件读取,反映最近一次落盘结果(含 worker 写回后的值)。 + 路由配置(add.toml 等)不在此范围。 + + - worker_type: 插件类型(如 example) + """ + 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 {}, + "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 index a2e81aa..f0a8f76 100644 --- a/uv.lock +++ b/uv.lock @@ -431,20 +431,20 @@ wheels = [ [[package]] name = "chongming-cache" -version = "0.1.0" +version = "0.2.0" source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } dependencies = [ { name = "diskcache" }, { name = "redis" }, ] -sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.1.0/chongming_cache-0.1.0.tar.gz", hash = "sha256:e81e046b3b9cf05699a579aac697c29a647c0ecbb310a5d5ffa17a14b2a6eb5c" } +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.0/chongming_cache-0.2.0.tar.gz", hash = "sha256:7b092c6b96d67f50aac1754c364e4a647efa089bf9dbbc989d0f5993925dec66" } wheels = [ - { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.1.0/chongming_cache-0.1.0-py3-none-any.whl", hash = "sha256:7c04f3ad5cbfd9d6a99541e6d7ab14423c2851c2cde50fca5b70f5c2f4ad031e" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.0/chongming_cache-0.2.0-py3-none-any.whl", hash = "sha256:4e95eddbb691479b23559020375c8d041a03b4a4c6163df1a7f6b5150b9e469e" }, ] [[package]] name = "chongming-cli" -version = "0.1.0" +version = "0.1.2" source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } dependencies = [ { name = "chongming-config" }, @@ -452,9 +452,9 @@ dependencies = [ { name = "sqlite-vfs" }, { name = "tomli" }, ] -sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.1.0/chongming_cli-0.1.0.tar.gz", hash = "sha256:bef08a46b4ecbb6d3a92ccb7afc3d4d7f77a6f09b9a09ab056dd6c9bf04e8361" } +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.1.2/chongming_cli-0.1.2.tar.gz", hash = "sha256:e169b81cb8c5dc6198c685b963b862f7cd27a2ac273da789fc23cbe027323265" } wheels = [ - { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.1.0/chongming_cli-0.1.0-py3-none-any.whl", hash = "sha256:94bcd6dd0967d6403b56bdc0e23c9861e3d4797edf66c949889f998a34fc55ce" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.1.2/chongming_cli-0.1.2-py3-none-any.whl", hash = "sha256:f72edb901ba1b355e2937f7fb1e9a9da4144c328bba59ea97421b5041b565e67" }, ] [[package]] @@ -489,7 +489,9 @@ 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_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" }, ] @@ -510,7 +512,9 @@ 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_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" }, ] @@ -537,17 +541,18 @@ wheels = [ [[package]] name = "chongming-worker" -version = "0.3.0" +version = "0.4.0" source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } dependencies = [ { name = "eclipse-zenoh" }, { name = "minio" }, { name = "pydantic" }, { name = "tomli" }, + { name = "tomli-w" }, ] -sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-worker/0.3.0/chongming_worker-0.3.0.tar.gz", hash = "sha256:5c9c9c92fca65ba4836460e81d31f31fdaf3f01407ecfa342d950262d02b6195" } +sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-worker/0.4.0/chongming_worker-0.4.0.tar.gz", hash = "sha256:c2b13fc1c2eb262f0ca5a1b64035bff1881a912c5a95446a9ce77b1ba968b03b" } wheels = [ - { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-worker/0.3.0/chongming_worker-0.3.0-py3-none-any.whl", hash = "sha256:2886b2ed3e44897489e99af2a3ce6ffebcea7210dce131760cc5e802c472be82" }, + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-worker/0.4.0/chongming_worker-0.4.0-py3-none-any.whl", hash = "sha256:96e1dd6b8c22a294b83b54d8ac3723f4555865625ed210a6e1974bc6ac3c4a9d" }, ] [[package]] @@ -635,14 +640,11 @@ dev = [ [[package]] name = "click" -version = "8.4.2" +version = "8.5.0" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +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/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, + { 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]] @@ -656,52 +658,52 @@ wheels = [ [[package]] name = "cryptography" -version = "50.0.0" +version = "50.0.1" 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/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" }, ] [[package]] @@ -1009,15 +1011,15 @@ wheels = [ [[package]] name = "jwcrypto" -version = "1.5.8" +version = "1.5.9" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } dependencies = [ { name = "cryptography" }, { name = "typing-extensions" }, ] -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/7e/53c14813521693e931e420d9db00efe317419ff194495903bce03402275e/jwcrypto-1.5.8.tar.gz", hash = "sha256:c3d7114b6f6e65b52f6b7da817eb8cb8423e1da31e1ef13508447c81ecbdcc34", size = 90772, upload-time = "2026-06-24T19:36:50.782Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/c7/00b87b49ffec758a5a957e448c863f6c4bb6223b3fab22c8c5161362ddcf/jwcrypto-1.5.9.tar.gz", hash = "sha256:dbbbfcdad7a6fc40cf3d5a635b11115fc42591b7ab869a36ad347a516a6e1c5d", size = 115793, upload-time = "2026-08-26T12:54:09.689Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/7c/f87c5db042c4f7b4476346fc0d22f1025a777fee08eebde3720d3b9c4eb5/jwcrypto-1.5.8-py3-none-any.whl", hash = "sha256:85aeb475f808d56bbc2f2ed1f6f73e6a317c4011a4321505f02f0aed695a3742", size = 96133, upload-time = "2026-06-24T19:36:49.519Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/54/b819eb2f8caedb1bccef0bf6188c051f0d898a177b271e1ed3a5cc0a8b66/jwcrypto-1.5.9-py3-none-any.whl", hash = "sha256:30b1c9afc898eefd112c31d836eb2d7a4c6febcac95d4aa727e963cd42911d8e", size = 120772, upload-time = "2026-08-26T12:54:08.637Z" }, ] [[package]] @@ -1102,7 +1104,7 @@ sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/d8/bd [[package]] name = "openai" -version = "3.3.1" +version = "3.5.0" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } dependencies = [ { name = "anyio" }, @@ -1112,9 +1114,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/9c/ba0c292b4032ede74c249ca314ad64eb1bb5a03a843f6e01facb02f80cd8/openai-3.3.1.tar.gz", hash = "sha256:6f22807de1a976c932cecda620e8172a8c3fdbaeed29c7f21564e0c2410edf56", size = 1282113, upload-time = "2026-08-19T16:31:35.006Z" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/d3/50ffb9a7bce5097ffeb476905c0661f4468a3ca7bb489b152542f14fdd8e/openai-3.5.0.tar.gz", hash = "sha256:743738bb458a586d0d02d173bf398d29d7d7a80d182d167aa74f1c08814ecc78", size = 1355486, upload-time = "2026-08-27T01:00:49.113Z" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/db/2b7a1b3de659bb82aef979116c74e809982b13e42c057759767552b5155f/openai-3.3.1-py3-none-any.whl", hash = "sha256:9652df7fdf8ee6f5bd58e0a12f2b1d414a18e0f06bb7a9a57c8643a5f5469bd3", size = 1690337, upload-time = "2026-08-19T16:31:32.812Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/9f/03bba05ade050f306c9b9e8b99e3681791dbb30148cd76a793c277fed494/openai-3.5.0-py3-none-any.whl", hash = "sha256:7c0873d379655f65fa515c5692c8620e73bf6a6dce3e63f37589bc99bda3fdfd", size = 1697968, upload-time = "2026-08-27T01:00:47.131Z" }, ] [[package]] @@ -1246,71 +1248,50 @@ wheels = [ [[package]] name = "pyarmor-cli-core" -version = "8.1.1" +version = "8.1.2" source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } wheels = [ - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/b4/c0b6f09ea1dc87c00839908d31fd902075496feb319ffd1b03b665a65477/pyarmor_cli_core-8.1.1-2-cp312-cp312-win32.whl", hash = "sha256:790f0957124d44d1527edf7a677ffc006cddc827998e4c7981f3b20fd3db14a9", size = 3743797, upload-time = "2026-05-20T09:27:01.65Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/0d/0bdccf84af431405b1e52d5a8cd6e4bf37a84e04520276da372d0ae2b577/pyarmor_cli_core-8.1.1-2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:f1fe8ec72cd1a1aeffcb39b6323f2ae3e5a2ba45d02bc67ef938e46bc7e207bc", size = 522710, upload-time = "2026-05-20T15:25:57.965Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/6c/6f6bc236208a4e3577ccf8b4849b520234294f804a33a77f0b91ead0027e/pyarmor_cli_core-8.1.1-2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6d22b879c1a2fa5a81de4de504e244c808e49e7911f95aed6d3329e38f8326ec", size = 414409, upload-time = "2026-05-20T15:26:04.24Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/e9/e9f8a52202ee74b9eec6a318f0fb8ed9849c7dbf47573a05d891f39f2476/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux1_i686.whl", hash = "sha256:8b481c807197f3cd2a15857adf2937cec5ae5a178c6008225b9ff94b83ff5ccb", size = 495375, upload-time = "2026-05-20T15:26:13.74Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/97/9e70a68220e190a886b74dabd080f6b8995266e2130ec79d28f6f6e31d20/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:5b5fd2301e628873be9f3ec32788d52e32625c414bc8fb5f4e93136e64a3f58d", size = 462070, upload-time = "2026-05-20T15:26:18.961Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/1a/07859e23cfa07d249da1bff11ee9beda8431f5a978d1a16256c325fe592c/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:6187ca1d4b2260332f74e5e60a4c9b0fc178dee9e69155c5cbb4befe3babccec", size = 609497, upload-time = "2026-05-20T15:26:24.233Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/0f/1a074362b19dd18a3f4ea27910cb1d08589609e4ec1ef9c181999bec015a/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:a2c0d67975d669a7045c809299a6dcdab305541948c24955fad737f5349bf26f", size = 1004965, upload-time = "2026-05-20T15:26:31.519Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/98/70b210c8bb2cf27c12eed161c6441f753aa68b0739a177f5bbf5a002be20/pyarmor_cli_core-8.1.1-2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3c6c748f98231dcdb75bcce9cf6b8fe86d61674c4a44ba49c12c4a8c478348cf", size = 655390, upload-time = "2026-05-20T15:26:38.625Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/e0/10b373cbb4fe1b140adb804b156fa7c7483f3c24e76a283cfb4047361b1f/pyarmor_cli_core-8.1.1-2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:20162349d62ec09d22299c9bedfe0b13e22c2d27757cb53c523eb950adcd7140", size = 450993, upload-time = "2026-05-20T15:26:44.049Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/97/f1a395a177ab833d560fcf56d3f97625a708669cdc567b46abe286c64ce8/pyarmor_cli_core-8.1.1-2-cp314-cp314-win32.whl", hash = "sha256:999a25c17a09af3e9481a9b2bc3f011526ef3333077cfa703a8014f899d528eb", size = 465426, upload-time = "2026-05-20T15:26:52.662Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/58/44112ea2938c023f8f94335deaae40c53a925cd5cb37386cbb904e4a97a5/pyarmor_cli_core-8.1.1-2-cp314-cp314-win_amd64.whl", hash = "sha256:add3f37e0fe78ca4678788c4c4042761aa9db7948a4395ccf9db9091fc7e07c6", size = 424587, upload-time = "2026-05-20T15:26:57.955Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/b4/48b1b05625ed48f2d786ea0c50a9b446b0d105c554a85e9eccb81bd98ec0/pyarmor_cli_core-8.1.1-2-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:119a0be4d75fc6c4f1c36a7beeee74a55819a86f4cedf3f0cab7afaf7f509d3a", size = 522845, upload-time = "2026-05-20T15:27:04.646Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/04/a09dd4b72dca9e43e02597aabf6213f27c2f12fafb477992fe8276a82d7b/pyarmor_cli_core-8.1.1-2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e2b656a703146507d5d800c4d952a656c9f4d68a4dd96a0c39eca28b63642914", size = 414405, upload-time = "2026-05-20T15:27:16.212Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/91/7e3a773c6e1756682db9c11783dd0e4a0894b9642771118ae2a4d1c436db/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux1_i686.whl", hash = "sha256:e0210f89acae597c574a5a58290b20feb94952dc6c002f8e280cc1b372db0189", size = 495327, upload-time = "2026-05-20T15:27:21.122Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/4d/fd2e36ecf479c9fe57b52c19c3f6bca0913c85fa980447af29a2ba9339ba/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:1675d2c7e157dbeb096fab72d8c06c5fda0ff68e468a28d3d9624aaaf90ff237", size = 461865, upload-time = "2026-05-20T15:27:27.146Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/8c/967d1ac0908d8c2c9ea73fbb2cb8ba31e4ec4fc290b7e3532ca0400d9115/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:bbe9429f19c4d6e70cec183cc78abb2f7ed483789fc17af6059871149d722e62", size = 609349, upload-time = "2026-05-20T15:27:33.218Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/50/2b2f5db7f6c28c22a2a67e1483dfda65e2285a94456dcbc6122fe9402849/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:5396c158a32cc6e2dc9ae317d819bb7c28ac83d832014e2db3e062679380ce35", size = 1004843, upload-time = "2026-05-20T15:27:40.199Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/c6/ff8d539dddc7e28a8f987a16fa85b9c27b142b64e54b644461f1e1bd8ed1/pyarmor_cli_core-8.1.1-2-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:5625035d8026ad905701f8c9c56140357776112cdd533ac614d3427c3084fbe8", size = 655278, upload-time = "2026-05-20T15:27:46.939Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/52/a8586c25e8ba3ec9c69556d6bc153097b8e065f87753e40bec9e1bea7f09/pyarmor_cli_core-8.1.1-2-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:a6ebbcbde0d650ef5f11e8c614dabde12636c7277c22ea9176f03aa8733c2cdb", size = 450928, upload-time = "2026-05-20T15:27:53.671Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/fb/d9df3bdce9c7480df3d91139539fd27332db3f5a984551201db0189dfdd3/pyarmor_cli_core-8.1.1-2-cp315-cp315-win32.whl", hash = "sha256:4fdd33615b75302fd48a0851a3f7ace1f4a4f515cb09edce5fd0d5d4e3919bf1", size = 465363, upload-time = "2026-05-20T15:27:58.894Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/e7/39a4c74668fdb7ad536264aa73d0439a2d98f816452e1955c2cce36b65bf/pyarmor_cli_core-8.1.1-2-cp315-cp315-win_amd64.whl", hash = "sha256:3c430c33fde5f36305c6e90356d7136da532166b20b9ad69bb5aefdbaea2e9cc", size = 424487, upload-time = "2026-05-20T15:28:06.382Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/b4/33fd5192cedc4600e9de8c9c7519205f421840da1b0df6e9491afca0a072/pyarmor_cli_core-8.1.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:4a6e09545fe74dc304992a0b002cc8d426c16ac3670d6f602d51838f080a2f31", size = 515480, upload-time = "2026-04-09T01:27:47.282Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/e5/3310cb2e29bab80bd923d7e0749c2163550effe1d1a6c6d5d95f5c58d183/pyarmor_cli_core-8.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df035854a6603d4861a50156484027a03c01a7b9385b5365cabea4871ac72e3e", size = 407703, upload-time = "2026-04-09T01:27:51.869Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/66/1aa494d360a05fd90677db8c4b8385dedd2083fe96fe6607ea555f800c87/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux1_i686.whl", hash = "sha256:ec304457ff31b4939c8527640d7f525e78e9c91cc8307a11f068ef1291710b40", size = 489280, upload-time = "2026-04-09T01:27:56.492Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/ad/5f246234ce778dd3b7bd4faa2bded71690bbcddbf8f29506cba131ae4f85/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:9b65a62a71a88fb7534002067b972aac2f363e871ad34563579550fec1a260cb", size = 457685, upload-time = "2026-04-09T01:28:01.313Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/5a/cbaa2088483837ded4bb6301993de16743c633f230bf29947cdc10f7c307/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:d1c4e623dbb8bd93fc4273a7ca79d192618ca36cf74aceaeca9d36c83608ed77", size = 604340, upload-time = "2026-04-09T01:28:06.227Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/39/2686a96a3d80c5ef61fea10066677ddf3678bdd48afcec04a0d268203fa0/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux2014_armv7l.whl", hash = "sha256:0c10ad18ca2cb285b4b6c2bfdba6bc0234e84272f52bf692dce25ae7c6a21d56", size = 1001029, upload-time = "2026-04-09T01:28:11.426Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/e9/da903ea517387ac3d560b324fd03a7f50267cc2f68e99f84934fd9ee3ef9/pyarmor_cli_core-8.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:812201887aa2fb90f8b865a5dd443aa841cb951bd25d14dad843ce11b3ceef57", size = 649535, upload-time = "2026-04-09T01:28:16.373Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/eb/fcd742456b3a475e16eb5c3468132a9950095169cf1e6a64e4fb4bac208d/pyarmor_cli_core-8.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88e129603512326ce1440e3aa105506304424ee40366cc19f0a72a9aeeabecf7", size = 446443, upload-time = "2026-04-09T01:28:21.054Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/87/b58aed090a6a9debf65045f37e93b779e330c22106aa4cbb9aaaf7bb12b8/pyarmor_cli_core-8.1.1-cp312-cp312-win32.whl", hash = "sha256:b645d69c88f55a08d34fdd547759079ac1722e246d8e994ce3eb07937bdf318b", size = 460155, upload-time = "2026-04-09T01:28:25.665Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/b8/1dc8b15bc1607a27ed78025d5f50c97c78542930cab15a6d6d457b664bb3/pyarmor_cli_core-8.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:09df045973b5de85ecb950f3fffc23686caa5b43749d4d966ccd150719639e2b", size = 420620, upload-time = "2026-04-09T01:28:30.36Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/35/4b2969ebaea72869a5ba13847d47682ec77a1ff894fc83450e23e7257845/pyarmor_cli_core-8.1.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:86a576252f26752cfed5a1f0d6a12352f7ca8f38aadfd7e1df44453d933bf70c", size = 517354, upload-time = "2026-04-09T01:28:35.086Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/0f/c688f09d158d5d4bf3c83289b9d16d454d5ac001ae962a7501d4209dbcad/pyarmor_cli_core-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef4cd7ecf1aeca5748d221e548a6203f58ede46e3b1f31cc1fbba5170f1dc5c5", size = 409747, upload-time = "2026-04-09T01:28:40.159Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/f1/c97c393fa0f6431ac6537acd21f117025488b1254c255ae42da1809d2ae3/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux1_i686.whl", hash = "sha256:e95cfbaafee2eec3a3ef918c80f6ff6d6e26535c0b9f5ea615a97cf419a45603", size = 489761, upload-time = "2026-04-09T01:28:45.023Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/78/5874a8cecc9123475bcf5884037a6abd5859e4b93e89bc5eacc06dfd493f/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:2f42447c95f0de7df586a5899b395dcbf3193b5288a290f77dde90d4459700a6", size = 458105, upload-time = "2026-04-09T01:28:49.719Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/a0/73eb8557f0dd6f9910dfb1ff70ad31550d5b037dff78f80a006732efb085/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:635144791ddcf3a7bbd31ac10181828d40636e4b6a477ffefa0c6f4073524604", size = 604874, upload-time = "2026-04-09T01:28:54.871Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/6c/3eeab3e37748ae5354eb629ed5e03ee09c85c6abac8d77926d4d27507f52/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux2014_armv7l.whl", hash = "sha256:248bfb04d86f38851bdc13652d5e447e0415128c0cdfbb04498802e624a88475", size = 1001331, upload-time = "2026-04-09T01:29:00.195Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/f3/9736f5049e0fff5a333a8e14058e5b6b89ef68f469bcc23f9001fd47ab42/pyarmor_cli_core-8.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a71b3b4ab8d07259b82c9fdad18e099d86b0880bce352949b834db825cbd697", size = 651229, upload-time = "2026-04-09T01:29:05.072Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/e5/988ebde164710de231a93127a1df25a278307bb8cd31e75b207266a3ae8b/pyarmor_cli_core-8.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:bc51089e37602a4e4c75b990448c44e25d7b1c7048805ad789863c889e80793f", size = 447196, upload-time = "2026-04-09T01:29:09.99Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/5c/4be2fe6a77477c6a82b776c147487030978fb3c6c825dfcecafe4cdaf1d1/pyarmor_cli_core-8.1.1-cp313-cp313-win32.whl", hash = "sha256:689356f95fec04a53f91a624b0d1f4bfc8547e2ddede6799fb1c728377a51e19", size = 460848, upload-time = "2026-04-09T01:29:15.702Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/d1/80a1085e94a8bcc3f1d8c6d1ef703c1fc35afe9a8f2b5f0676d74fc1a80d/pyarmor_cli_core-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:2e7b259c4dda700360030cf212b2cca625aaf9e7498dc9bfcbee9ef85acfec9b", size = 421343, upload-time = "2026-04-09T01:29:20.354Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/57/12936c3ea69e88481e71d4f61a878aa49b26181c0fed9282b590f46b9132/pyarmor_cli_core-8.1.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:4bfbfe564881bd467b2103b47b4fdfe883746e39651545280f4c815d409e45f7", size = 522708, upload-time = "2026-04-09T01:29:25.471Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/4c/ba326b377d0d698fa80864a478822e56acd0b1dd74db7b2645ca66e3c732/pyarmor_cli_core-8.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0ff3494acdacbec1565a1d98065ab2a2b47683778ad02b89b643008a1ffe90bd", size = 414399, upload-time = "2026-04-09T01:29:30.291Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/15/f6121674cca8487665bc4803da745a1f4e7f98d233bf33e659ca3dbfec29/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux1_i686.whl", hash = "sha256:2d905427cd23226f86998a28f6c79c849136dfb85e569e32b208ad7d75b24853", size = 495367, upload-time = "2026-04-09T01:29:35.223Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/6a/67121dc6f489120322bf4742774a3ebd05eb4b6d1ebcf971b34ea91ec2b4/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:4de138aacd465e0a0d4601b290d0555d47657f59b86d5eeec1bc660fd01e149a", size = 462060, upload-time = "2026-04-09T01:29:39.799Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/87/09d5c023474495dd3dbb789ecf538af1bcbe53c49cd07a703cfa5da03bde/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:1777c7c2c7130a34b7c7125ddd828569e3f938fc91efc0db99fb0ee1eba88aa4", size = 609493, upload-time = "2026-04-09T01:29:44.322Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/55/c6f403c7a33900d1a194839e55677814a35ab33d10c4234394f7ad12c311/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:5172681e95bfbf4eb250d53e1ede75b270e7ec7c29a4accfb0acf766bc7b6351", size = 1004962, upload-time = "2026-04-09T01:29:49.616Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/bc/ce7146830a08306884929b53f785bb708df7c360a73a2a3687ff73533918/pyarmor_cli_core-8.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:06de0a47cb8ae86fe9270f3930d8e245fff73f87bd3c49cdc2af47c8ce1a5b06", size = 655381, upload-time = "2026-04-09T01:29:54.994Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/05/0fac5b9e9d37e1ea46ceae2733ab07d348b199257d99f69d290eae363ac0/pyarmor_cli_core-8.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7ae1c265c6560e869d355373fd86c2880a951db6bbf2bbdffec698d47c269be9", size = 450987, upload-time = "2026-04-09T01:30:00.065Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/84/173c89753b13037ad45fd6c220aeb12e055de261c8453025df9dc0c82b2c/pyarmor_cli_core-8.1.1-cp314-cp314-win32.whl", hash = "sha256:18a7f8aa4054395be9fe5b051727f2c32bdce74d609ef75cb869a9fd0396da90", size = 465415, upload-time = "2026-04-09T01:30:04.793Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/ce/32fb60e0971791c7e102ab87140d8c5da444e8b22b05614d7b63b7a90113/pyarmor_cli_core-8.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:9e50864097734b039060074b4913ffca73069162ff3c32556f3b594888129661", size = 424573, upload-time = "2026-04-09T01:30:09.704Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/02/7632897343d0b1ac3faa6e882f62880c4910d1331a46f1796816b99e9f8d/pyarmor_cli_core-8.1.1-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:eb3a63691a67aca10e2af7937baf0c88effc459c78c3c4ef1c48cf562376b830", size = 522819, upload-time = "2026-04-09T01:30:14.265Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/aa/09cce8ed725419d8a3c1a4a4b588b5f369094ef0b9768bc7e2d7fe855897/pyarmor_cli_core-8.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:75328d808fb790546272ba92440c8ae9e80f3c84cce3f423fefb00a9af230af0", size = 414398, upload-time = "2026-04-09T01:30:18.603Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/a9/844a2fcb09dc30994e03b5469ed80d6066e794828cd48af437da33029f8d/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux1_i686.whl", hash = "sha256:e0e0965e8a1d779485f747b93d6f4c7a20395553851921b0fd22ea89fb09287e", size = 495322, upload-time = "2026-04-09T01:30:23.59Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/1b/b169c06628dc9ac375c5d3f270cd742e344ddfa6dab5e00216a984ccfa01/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:3562142724c8cd48a6b161b35a7b4ebfc30820f08ca0adc28f8729077531c3f4", size = 461861, upload-time = "2026-04-09T01:30:28.655Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/98/8ab28d635e03d269234243c4ac2fd82d722435640d9be89e78cb6f4ba15f/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:27c6e269131da709bcf1aac04ed8285eaa8a328f7682e98c4e93a81f97e2e4db", size = 609340, upload-time = "2026-04-09T01:30:33.652Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/68/f6e381d81e988077c6fdddcfa659ed30953640e863928575886815e1eefa/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:0cc8d7c337663eb9d108649ba296973284ff8b6fc0f83ae0ce347697533a983d", size = 1004837, upload-time = "2026-04-09T01:30:38.944Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/83/4958e7eb4a4dd27b024a034443636e7db418d06ee743eb09eecab2360e18/pyarmor_cli_core-8.1.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:00cccda40da819c45550625c910f836b9c768699ea40b04c058c3221270b514b", size = 655276, upload-time = "2026-04-09T01:30:44.053Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/d8/c8992fc6f66fcd8b29f8497ca352165bd9e0972a1f5371eb8c01aaca7a3e/pyarmor_cli_core-8.1.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:488be5b88a0c434fb17457972f6b7f681b38650ce078c3f19dced5b15091146d", size = 450926, upload-time = "2026-04-09T01:30:49.022Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/61/6cb081493c75662dd979a6cf8f6092d81605a9a59c2439a2ab77f3abfc1b/pyarmor_cli_core-8.1.1-cp315-cp315-win32.whl", hash = "sha256:a0f087fe8c388d178859dbc2a14e11e12bf8efc694f035e41d1dfc5b4a630052", size = 465352, upload-time = "2026-04-09T01:30:53.861Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/87/9daf968e6e6add10d089eef86b46a26c9908bfae74820bc97aacbf37d081/pyarmor_cli_core-8.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:c1b96471bb1d9fc575ba526841829c6bf03cd9db5caabf5688518a29ed389870", size = 424475, upload-time = "2026-04-09T01:30:58.457Z" }, - { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/90/89c95f00699600a2c476b1c80e97df5ea1f09b995a8abfc0a8f118f0bf38/pyarmor_cli_core-8.1.1-py3-none-any.whl", hash = "sha256:06eb9771c7d1f0ad7c00884f13ea42edfbffab8b28ef718ce197c0b4c69d97d5", size = 5177, upload-time = "2026-04-09T01:33:22.702Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/f2/3dbe618b9a2a21e7eb731b238c297cfd4da8301d2d8cf79ab4ed89537c73/pyarmor_cli_core-8.1.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0bfabe4d8db7510a09419a8cbf38d1952759ae48044854666b2a9e7d1e0bfc16", size = 515507, upload-time = "2026-08-26T04:42:40.407Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/47/de7e83d98d15882810bd865ca782101cf7c29b69ea0db42ed5e3b3f2a0a5/pyarmor_cli_core-8.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce0275112a7e502b18db4a67d292ee7a764dc48cd422334c02377b797a2ce91", size = 407727, upload-time = "2026-08-26T04:42:45.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/ce/326cb3f7ed988c6bfd8c7365837d2bc99f1105d8a5f029ca591201a962ac/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux1_i686.whl", hash = "sha256:d68e86e01b47f91c1f7b97ed7ff4079159d813624873e4e5d5c660a089f32432", size = 489298, upload-time = "2026-08-26T04:42:51.84Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/71/ce177e470033c27d58d519d5f2547edd09b3415ffaa3ac9fa60fa5c7b846/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:05212ececa5195d1c72113528b9379f691d5f7671ef3c0b5e1c72908c7a46ae8", size = 457692, upload-time = "2026-08-26T04:42:56.947Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/8b/3f68f677ddadf7e3730a91c98de10f048146a359e76542207eac7744b621/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:2cf87045cc73fe096721d0e7fd972849bca84da8dde3ba478de75b4abc85e3fe", size = 604351, upload-time = "2026-08-26T04:43:07.276Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/f3/1f26f562840586fdcccde66c602f44f3695bcda9b23efaa39227f39e6614/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux2014_armv7l.whl", hash = "sha256:18adeee02d6d829741d28a2ac7a4229020583c491eea475d4164b1d311cc540e", size = 1001047, upload-time = "2026-08-26T04:43:12.975Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/4b/50a847ef3a0b8fa12d1891cae2383b6efe04cfafef5bd2137ab4468cb29a/pyarmor_cli_core-8.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f5f4c573a435619273442f1b9f8938d31c7d0f26100a80453e6d0ae054d1e0a2", size = 649548, upload-time = "2026-08-26T04:43:20.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/2d/0ca6b9036195971200975c69c0a65a2049304458ebfa73caeeb860a94414/pyarmor_cli_core-8.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35ac4f6ba07b20391ab9624c70bbc133537560e227579de6db51fe45535ab93c", size = 446449, upload-time = "2026-08-26T04:43:25.667Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/de/6969ae804814dc1842f363847484dfb05664c4ae2ecb60ac9a2ef69a1e3c/pyarmor_cli_core-8.1.2-cp312-cp312-win32.whl", hash = "sha256:609f5a63d0135d3723203f859e89f7a28c45e09474250705e995d59845d8c307", size = 459858, upload-time = "2026-08-26T04:43:30.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/b7/2f3de91992fd22f7d62d7f8c59cc47898e6a260e195c1131da1e7b6a9c25/pyarmor_cli_core-8.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:952558497cbe06bc12beaf014e2c53b20203e4ddef3c8b6028744abf72e14505", size = 420611, upload-time = "2026-08-26T04:43:35.533Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/7e/3d79d27c130736326503abc0ef358e4fb1b102c4a21f2e9affbfbdbd25f2/pyarmor_cli_core-8.1.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5dc5cbf1089a503544cadd63baf4a65953983459718d4e1aeb2954a80e8f73dd", size = 517370, upload-time = "2026-08-26T04:43:40.387Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/64/bd3419284798569f60ad9db0766cb7c0e3a9ea882489bad0fa8c5a754daa/pyarmor_cli_core-8.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b068b77395b3f74d4384e3c18d4684c56a4360c8fde848692444d5e6dac8d1e", size = 409769, upload-time = "2026-08-26T04:43:45.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/af/77fa373c09063b91b13579c7321c6e1a5f6f6be1851b3c72ec3c2a4cad32/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux1_i686.whl", hash = "sha256:405405e44855307ee26264ea253358009dc73ad08440ba89935efa4fde87bae4", size = 489760, upload-time = "2026-08-26T04:43:51.707Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/8e/b411da54be4bc2143dd35dd6b3423376c052906eb7d0cb63c58dc01a31f6/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:66d6d9c15aabb52f87ff98d4819a5ecdc32b13100348be2872c373354470d8d5", size = 458108, upload-time = "2026-08-26T04:43:56.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/69/82cd83355e1470eb5f7fb505bc8ff85a8f8caf67c0da02de86beb31505be/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e77c780dce6c0a50824927a03e0f9c39c642e1ca8fa1671c70c03b01c2e90cef", size = 604916, upload-time = "2026-08-26T04:44:01.603Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/ff/8be81c5d943cc14b8bae44780249b657565690bea65ff52ee692548e8bc0/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux2014_armv7l.whl", hash = "sha256:49ecb1d3f76bedee50be3047d18ce50609db16bff8702a7f096531503d737785", size = 1001331, upload-time = "2026-08-26T04:44:06.741Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/6e/cc65256fa20d49dc704231530c693122e31c8c18783b2f7985624d3c2c1b/pyarmor_cli_core-8.1.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9b1f648743a32f417bf908acc5800711175edf411fa24bf729a6d2aeb24a2617", size = 651236, upload-time = "2026-08-26T04:44:12.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/69/5c6bf32fed77aed5b50aac0b97b7df0f1f72b3343e9bef4a014392e85cdd/pyarmor_cli_core-8.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d890021c640fb01dda0b7a4c8580b12e103bd349817bc86f01c04f3c9358cae1", size = 447212, upload-time = "2026-08-26T04:44:17.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/b6/58410290dae04ebcf85aa3084d31ba07930121a44ccc7714c738fdfe6810/pyarmor_cli_core-8.1.2-cp313-cp313-win32.whl", hash = "sha256:b3f2d55db32e6453a3e070fadeef827c147f21106e801a7f0688158f665c0726", size = 460848, upload-time = "2026-08-26T04:44:25.4Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/e8/d748641756c7a3353ce161b3316eae46fa5f7355a4208b7a3488e18764b1/pyarmor_cli_core-8.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:c61a86ab3f3a85550e148dd874e1ad2f2295870ad3204b1d92287c3fded3d087", size = 421322, upload-time = "2026-08-26T04:44:30.229Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/ce/bbfe583c3e7ee7b76b51173c0ba9a073b222cd3a3e7d28b13e6a5373d5d9/pyarmor_cli_core-8.1.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:3416df1e56f10a12f42c124ed4c29a849fca1fe33bbc70e725fd0bb395d2301b", size = 522725, upload-time = "2026-08-26T04:44:43.116Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/16/9bbc4f0591bf28b481e079c1c38deae5fa78d23b4eade36f4fa6bcdf1956/pyarmor_cli_core-8.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c5c06fee0a63cc8c3326f6f7e44a3c39d88dfdcc64465c50e584e4fdda6c4da6", size = 414438, upload-time = "2026-08-26T04:44:47.886Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/26/e24b62ad18e4c8f18abdae1b12915dbdd9a7e08118082a7f427a7922a93f/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux1_i686.whl", hash = "sha256:0b2a1d47555d750f6c9cc9a7883c12c4f6b81966a85815ba309ae887a0866209", size = 495370, upload-time = "2026-08-26T04:44:52.993Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/43/5d9dd141e4bd463092fa2c1b2da524f23e7f78d1a5180664ea9810ac7a85/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:27b5246ec4ef12cf9190da8d0caf4c69ea09c3082ca3b9317b8b27eadb665044", size = 462068, upload-time = "2026-08-26T04:44:58.693Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/91/84110437e6cef14eccc4555ddeed0964e36a4d33cde2c6827dbd059d743a/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:f83c6a2062aaba0a905c9274e53eb89a3054cf5c6d53ec25873dc8e8991408a5", size = 609512, upload-time = "2026-08-26T04:45:04.128Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/bf/bef14c1746e0a1ada92e066c899cbb8fc20878d8eeeff9c2893eade22333/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:ed08a46cc52e9c1d22f1472452f7d0b02eb1cc3d8bd3494f20ecbb20179c8696", size = 1004951, upload-time = "2026-08-26T04:45:11.196Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/14/677a5246b700a30b541c4d3c83b654641946a5dc11ba8bb9efc3f647e973/pyarmor_cli_core-8.1.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6ee50a3307ad6c69cdd4873a32591fea6791cbcc040f9271678b6597d9214dc2", size = 655375, upload-time = "2026-08-26T04:45:17.309Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/26/8b33b3f3808f792fd35847346814709a722bb981096a785aa0c8dac35deb/pyarmor_cli_core-8.1.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b18735b1f3d26a6b8f3776d82a4457596972f7a2f986d729740b6443812b0f62", size = 451004, upload-time = "2026-08-26T04:45:23.568Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/ee/2b8260c1fafd042282eac62dad02285b2088049e6e1a8a1771e87891f707/pyarmor_cli_core-8.1.2-cp314-cp314-win32.whl", hash = "sha256:1baf50f8efde7817899313092de743129da063db2265c66c7ac06003a7b7e8bb", size = 465410, upload-time = "2026-08-26T04:45:28.365Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/d5/357b7850aa061eddf14b5fff3b0230eff65d62e3005e008b28f4807aeeed/pyarmor_cli_core-8.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:84a398bc0873890010f812b3190d4518bb393d3c0c17d7b2cdf2f547e65ce119", size = 424558, upload-time = "2026-08-26T04:45:32.967Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/8a/873ffc711607601460fffbf91895c04b754ab934bd29e8cb4de6c8cfb785/pyarmor_cli_core-8.1.2-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:794bf98430e6ac28ff2ab61a9771a9c30736182c098eb930d2b31d8cec8782c0", size = 522825, upload-time = "2026-08-26T04:45:39.577Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/3c/8f5b61f254324d308ba6164ee11c3970974356d0928bfaaed76d0c2a6cab/pyarmor_cli_core-8.1.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:d1bf8f4d9e66af72dcbd14dba919e64e3f415227d159b97813efd728728e8ca8", size = 414438, upload-time = "2026-08-26T04:45:44.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/33/f8a746bf9dfb634ee7b189668acd58eb730c09305be86fb70149c1c2c442/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux1_i686.whl", hash = "sha256:ed12d37d5be2d8f03248541285bfed0a5545495d7e6d8352652e40f3ff320725", size = 495326, upload-time = "2026-08-26T04:45:50.413Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/92/5a1e5fa2fecd0819844678bfa1b3ee3a1358922d47d4925b4f3d4d1f3f5d/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:3ba57f26a78a5ef82fc8b1a79176de9c0c32082e2874b3e71c32178cc3028d75", size = 461872, upload-time = "2026-08-26T04:45:55.631Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/45/770ab7dbf55e6af6b38511f14c691a26bfe60940a2e1138ab66f986fe44e/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:2f0b11a99e348567dc2f7d7ff32fa4c4aec5656db5369eaa8417422c654b9d00", size = 609345, upload-time = "2026-08-26T04:46:01.045Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/c7/e4ef2d2c43dff6ead90fb212605725646234b3d440fc38e49cf8d349b1d3/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:135388b7b20054be4111f78c21fb171f7718bc763edd2395bbf00760d3b059ad", size = 1004824, upload-time = "2026-08-26T04:46:07.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/11/77da23b337acb01a6a2dcb372eea995bb8bcba4cdd7772cefccb5daf4eb9/pyarmor_cli_core-8.1.2-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:748c2906e29ef4de3bc83e7d39014d4416fa6a76ab1d07770067982fefcc1246", size = 655255, upload-time = "2026-08-26T04:46:13.011Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/45/c24cd03929ef50bb34407e58393121179b628c2561fecd7acda062408df7/pyarmor_cli_core-8.1.2-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:9ae31de935d673420cf3b5592a00ff2546d040c65923431f799966ff6e2976e5", size = 450939, upload-time = "2026-08-26T04:46:17.739Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/7a/31ea2e2983da14c6d41f4b350d2e127942c504e082b2c46aee0a836ba254/pyarmor_cli_core-8.1.2-cp315-cp315-win32.whl", hash = "sha256:816b34c1565c26d1a302ceae71e1791b4447606bfe8f6548ed4b0ed49ab4b372", size = 465364, upload-time = "2026-08-26T04:46:22.582Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/88/18309ad305f0ae6f462d62128d7cb882222ff0a29e8aa3461c60cad14c25/pyarmor_cli_core-8.1.2-cp315-cp315-win_amd64.whl", hash = "sha256:1a288d24fb3ca77f5060976eff3de70fc0a916074fdbe2485649a3cf9c383864", size = 424465, upload-time = "2026-08-26T04:46:27.282Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/58/7758001a43902bf162a41d6950cae17bf46432250c37800092b0aea7d815/pyarmor_cli_core-8.1.2-py3-none-any.whl", hash = "sha256:1f07c1cabe45511a4a91a615ded15adabeb8b5b93c4732dd633c4913299185bd", size = 5178, upload-time = "2026-08-26T04:49:14.149Z" }, ] [[package]] @@ -1802,6 +1783,15 @@ wheels = [ { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + [[package]] name = "transitions" version = "0.9.3"