modify: worker 管理端点

This commit is contained in:
2026-08-27 09:49:47 +08:00
parent 2270f2f6e8
commit 378128fc16
10 changed files with 967 additions and 143 deletions
@@ -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("副本 <type>/<id> 未响应管理指令(可能不在线)")`(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-<index>-<pid>`,无 epoch 段)。新版 runtime 才生成 `worker-<index>-<epoch>-<pid>` 并声明 `__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/<type>/<id>/__admin__`。
+110 -2
View File
@@ -28,6 +28,7 @@
| **流媒体代理** | ffmpeg + MediaMTX RTSP 流转发 | | **流媒体代理** | ffmpeg + MediaMTX RTSP 流转发 |
| **构建打包** | PyInstaller / Nuitka 一键打包为独立可执行文件 | | **构建打包** | PyInstaller / Nuitka 一键打包为独立可执行文件 |
| **Vue 3 前端** | Element Plus UI、Pinia 状态管理、类型安全的 API 客户端 | | **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 会话 2. setup_zenoh() — Zenoh peer 会话
3. setup_mqtt_broker() — 内嵌 amqtt Broker(子进程 + 文件锁单例; 3. setup_mqtt_broker() — 内嵌 amqtt Broker(子进程 + 文件锁单例;
`use_embedded_broker=false` 时跳过,改用外部 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 分派: 5. load_workers() — 按插件 [workers].load_mode 分派:
inline → 进程内 import 插件 → 启动 Worker 协程 inline → 进程内 import 插件 → 启动 Worker 协程
external → 跳过(独立进程运行,盘扫条目仅观测) external → 跳过(独立进程运行,盘扫条目仅观测)
5.1 setup_route_registry() — 常驻订阅 chongming/routes/**,external 插件路由动态挂载/摘除 5.1 setup_route_registry() — 常驻订阅 chongming/routes/**,external 插件路由动态挂载/摘除
6. validate_contracts() — inline 插件三方契约校验,失败终止启动 6. validate_contracts() — inline 插件三方契约校验,失败终止启动
7. register_routes() — inline 插件盘扫生成 HTTP 路由 7. register_routes() — inline 插件盘扫生成 HTTP 路由
8. setup_mqtt() — MQTT Bridge(inline 主题静态收集 + external 主题动态推送) 8. setup_mqtt() — MQTT Bridge(inline 主题静态收集 + external 主题动态推送;基于共享缓存的分布式锁保证全局单实例)
``` ```
--- ---
## Worker 管理系统
通过 Zenoh 向 `worker/<type>/<worker_id>/__admin__` 管理端点下发指令,**精细化控制单个 Worker 副本**(inline 与 external 进程插件共用同一管理协议)。提供 REST 管理端点 + Vue 前端页面,操作需超级管理员鉴权。
### 管理协议
每个用 Worker 框架(`chongming_worker.runtime.create_workers`)创建的副本都会注册一个管理 Queryable:
```text
worker/<type>/<worker_id>/__admin__
```
| 指令 | 说明 |
| ----------- | ---------------------------------------------------------------------- |
| `status` | 查询副本状态 |
| `restart` | 重启该副本,副本槽位(index)由管理循环驱动,重新生成新 `worker_id`(`worker-<index>-<epoch>-<pid>`)并经 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/<type>/stop` | 停止 single 副本 / 某插件全部副本 |
| `GET` | `/api/v1/admin/workers/<type>/config` | 读取该插件的现有 `[workers.config]`(展示当前值) |
| `POST` | `/api/v1/admin/workers/<type>/config` | 更新配置(body `{updates:{k:v}}`),下发并重启 |
| `POST` | `/api/v1/admin/workers/<type>/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:<type>`,在线网关 id 列表存 `worker:cm:gateways`,各网关分摊配额存 `worker:cm:quota:<type>:<gw>`(可观测)。
- **网关登记/注销 + 心跳租约**:网关启动时 `register_gateway()` 加入在线列表,并每 15s `heartbeat_gateway()` 刷新自身时间戳;超过租约(60s)未心跳的死网关会被自动清理(crash/kill -9 也不残留,避免配额失真)。当前由 `CHONGMING_GATEWAY_ID` 环境变量指定(多实例需唯一,尤其**同宿主多网关必须显式指定**;缺省回退 `hostname`,稳定且重启不变,不残留);关闭时 `unregister_gateway()` 移除,其余网关实时重算分摊。
- **分摊算法**:`own_quota()` 将全局总量按在线网关 sorted(id) 均分,余量按序补前若干。`/admin/workers/<type>/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/<type>/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/<type>/config.toml`,external 容器需挂载与网关同款配置目录才能写回网关侧配置(含 `[workers].count` 持久化);
3. 纯自定义语言(如 Rust `rsdemo`)进程插件需自行实现 `__admin__` Queryable + liveliness + 数量管理,否则只能发现、无法改写配置或扩缩容。
---
## 端点类型与路由注册 ## 端点类型与路由注册
### 端点类型 ### 端点类型
@@ -482,6 +572,23 @@ response_topic = "/board_ping/response" # 可选,响应主题;默认 "<
- 多条自定义主题按配置顺序匹配,先匹配者优先;内置 `chongming/...` 接口最后兜底。 - 多条自定义主题按配置顺序匹配,先匹配者优先;内置 `chongming/...` 接口最后兜底。
- 全局 `[<env>.mqtt].custom_topics` 仍可作为通用兜底(不依赖具体插件时使用,可选)。 - 全局 `[<env>.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 请求负载为信封结构,控制字段与业务数据分离: MQTT 请求负载为信封结构,控制字段与业务数据分离:
@@ -655,6 +762,7 @@ uv pip install -e utils/<name> # 安装/更新工具包子包
| 字段 | 位置 | 说明 | | 字段 | 位置 | 说明 |
| -------------------- | --------------------- | --------------------------------------- | | -------------------- | --------------------- | --------------------------------------- |
| `count` | `<插件>/config.toml` | Worker 副本数(`[workers]` 节) | | `count` | `<插件>/config.toml` | Worker 副本数(`[workers]` 节) |
| `launch_worker_count`| `根 config.toml` | 启动时副本数来源:`"toml"`=用 toml count 覆盖 cache(默认);`"cache"`=以 cache 为单一事实来源(`[cache]` 节) |
| `router_prefix` | `<插件>/config.toml` | API 路由前缀 | | `router_prefix` | `<插件>/config.toml` | API 路由前缀 |
| `defaults.timeout` | `<插件>/config.toml` | 默认超时(秒) | | `defaults.timeout` | `<插件>/config.toml` | 默认超时(秒) |
| `defaults.cacheable` | `<插件>/config.toml` | 是否缓存 RPC 结果 | | `defaults.cacheable` | `<插件>/config.toml` | 是否缓存 RPC 结果 |
+8 -1
View File
@@ -17,6 +17,11 @@ job_store_path = "scheduler_jobs.db"
backend = "diskcache" # "diskcache"(默认) | "redis" backend = "diskcache" # "diskcache"(默认) | "redis"
cache_store_path = "./cache_data" cache_store_path = "./cache_data"
# 启动时该类 worker 的全局副本数优先来源(指 config/workers/<type>/config.toml 的 count):
# "toml" (默认) = 经典模式:启动时用 toml 的 count 覆盖 cache 并写回为新的全局总量
# "cache" = 全局模式:chongming-cache 为单一事实来源,toml 仅在 cache 缺失时引导
launch_worker_count = "toml"
# redis 后端专用(backend = "redis" 时生效,均有默认值) # redis 后端专用(backend = "redis" 时生效,均有默认值)
# redis_host = "127.0.0.1" # redis_host = "127.0.0.1"
# redis_port = 6379 # redis_port = 6379
@@ -50,7 +55,9 @@ access_sample_rate = 0.1
allow_credentials = true allow_credentials = true
allow_headers = ["*"] allow_headers = ["*"]
allow_methods = ["*"] 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] [development.mqtt]
enabled = true enabled = true
-2
View File
@@ -56,8 +56,6 @@ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/"
[[tool.uv.index]] [[tool.uv.index]]
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
[tool.uv.sources]
[project.scripts] [project.scripts]
gunicorn = "chongming:gunicorn_serve" gunicorn = "chongming:gunicorn_serve"
serve = "chongming:serve" serve = "chongming:serve"
+8 -1
View File
@@ -22,7 +22,7 @@ from chongming_config import get_config
from .core.logger import get_logger from .core.logger import get_logger
from .core.static_files import SVFSStaticFiles from .core.static_files import SVFSStaticFiles
from .core import lifespan_setup, call_stats 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() config = get_config()
@@ -49,6 +49,9 @@ async def lifespan(app: FastAPI):
# 4. WorkerManager # 4. WorkerManager
manager = lifespan_setup.setup_worker_manager(app, session) manager = lifespan_setup.setup_worker_manager(app, session)
# 4.1 全局副本数协调:把当前网关登记进在线列表,供按在线网关分摊 quota
manager.register_gateway()
# 5. 插件加载(混合模式:inline 进程内加载;external 由独立进程运行, # 5. 插件加载(混合模式:inline 进程内加载;external 由独立进程运行,
# 其路由经 Zenoh 动态注册——进程编排交给 Docker/K8s,网关不拉起进程) # 其路由经 Zenoh 动态注册——进程编排交给 Docker/K8s,网关不拉起进程)
worker_tasks, custom_routers, inline_types, external_types = ( worker_tasks, custom_routers, inline_types, external_types = (
@@ -117,6 +120,9 @@ async def lifespan(app: FastAPI):
# 4.5 取消后台统计/审计任务 # 4.5 取消后台统计/审计任务
await call_stats.shutdown() await call_stats.shutdown()
# 4.6 全局副本数协调:关闭时把当前网关从在线列表移除(其余网关将重新分摊)
manager.unregister_gateway()
# 5. 关闭 Zenoh 会话 # 5. 关闭 Zenoh 会话
session.close() session.close()
logger.info("Zenoh 会话已关闭") 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(storage_router)
app.include_router(stats_router) app.include_router(stats_router)
app.include_router(worker_admin_router)
+24 -10
View File
@@ -8,7 +8,7 @@ import asyncio
import socket import socket
import sys import sys
from chongming_cache import Cache from chongming_cache import Cache, DistributedLock
from fastapi import FastAPI from fastapi import FastAPI
import zenoh import zenoh
@@ -259,7 +259,10 @@ def setup_zenoh(app: FastAPI) -> zenoh.Session:
def setup_worker_manager(app: FastAPI, session: zenoh.Session) -> WorkerManager: def setup_worker_manager(app: FastAPI, session: zenoh.Session) -> WorkerManager:
"""创建 WorkerManager,启动定期刷新任务,并存入 app.state。""" """创建 WorkerManager,启动定期刷新任务,并存入 app.state。"""
cache = Cache.from_config(config["cache"]) 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 app.state.worker_manager = manager
refresh_task = asyncio.create_task(manager.refresh_workers()) 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: for wc in inline_plugins:
module_name = wc["module"] module_name = wc["module"]
factory_name = wc["factory"] 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_type = wc["type"]
worker_config = wc["config"] 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 子进程已在 # Broker 来源:use_embedded_broker=true 时内嵌 amqtt Broker 子进程已在
# lifespan 第 3 步启动(setup_mqtt_broker + 文件锁单例);false 时连接外部 Broker。 # lifespan 第 3 步启动(setup_mqtt_broker + 文件锁单例);false 时连接外部 Broker。
# 原子锁确保只有一个 worker 启动 MQTT Bridge # 排他锁:基于共享缓存(redis / 共享卷 diskcache),保证全局只有一个 Bridge 消费。
if not _acquire_singleton_lock("mqtt_bridge"): # 相比原文件锁,可跨多台主机收敛——多实例共享同一个外部 Broker 时,
logger.info("MQTT Bridge 已由其他 worker 启动,跳过") # 避免一条 MQTT 请求被 N 台网关上各自消费、各回一份响应。
bridge_lock = DistributedLock(worker_manager.cache, "mqtt_bridge")
if not bridge_lock.acquire():
logger.info(
"MQTT Bridge 已由其他网关实例持有(分布式锁),本网关跳过,"
"以规避共享 Broker 下单请求多响应"
)
return None return None
# 记录本进程持有锁,关闭时由持有者释放(避免误删其他 worker 的锁) app.state.mqtt_bridge_lock = bridge_lock
app.state.mqtt_bridge_lock_acquired = True
bridge = MQTTBridge( bridge = MQTTBridge(
worker_manager=worker_manager, worker_manager=worker_manager,
@@ -625,6 +635,7 @@ async def setup_mqtt(app: FastAPI, worker_manager: WorkerManager) -> MQTTBridge
) )
app.state.mqtt_bridge = bridge app.state.mqtt_bridge = bridge
await bridge.start() await bridge.start()
bridge_lock.start() # 后台续约租约,防止持有方宕机/长暂停导致锁被错误抢走
# 把 Bridge 挂接给常驻路由注册表(动态主题 + 补推已到达的元信息) # 把 Bridge 挂接给常驻路由注册表(动态主题 + 补推已到达的元信息)
registry = getattr(app.state, "route_registry", None) registry = getattr(app.state, "route_registry", None)
if registry is not 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) bridge: MQTTBridge | None = getattr(app.state, "mqtt_bridge", None)
if bridge and bridge.is_running: if bridge and bridge.is_running:
await bridge.stop() 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) await shutdown_mqtt_broker(app)
+420 -1
View File
@@ -1,7 +1,10 @@
import asyncio import asyncio
import concurrent.futures import concurrent.futures
import hashlib import hashlib
import os
import random import random
import socket
import time
import orjson import orjson
from typing import Any, AsyncGenerator, Dict, List, Optional from typing import Any, AsyncGenerator, Dict, List, Optional
import zenoh import zenoh
@@ -10,6 +13,17 @@ from .logger import get_logger
logger = get_logger("worker_manager") 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" _SENTINEL = object() # 哨兵值,用于标记"No reply received"
# Zenoh RPC 专用线程池:隔离于 asyncio.to_thread 的全局默认池 # Zenoh RPC 专用线程池:隔离于 asyncio.to_thread 的全局默认池
@@ -50,13 +64,133 @@ class WorkerManager:
2. 本地:通过 register_worker() 直接注册(用于同进程 worker) 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.session = session
self.cache = cache 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._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._local_workers: Dict[str, List[str]] = {} # type -> list of locally registered workers
self._lock = asyncio.Lock() self._lock = asyncio.Lock()
self._refresh_interval = 15 # seconds 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): def register_worker(self, worker_type: str, worker_id: str):
""" """
@@ -79,6 +213,11 @@ class WorkerManager:
self._local_workers[worker_type].remove(key_expr) self._local_workers[worker_type].remove(key_expr)
except ValueError: except ValueError:
pass 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): async def refresh_workers(self):
"""定期刷新存活 worker 列表(合并 Zenoh liveliness + 本地注册)""" """定期刷新存活 worker 列表(合并 Zenoh liveliness + 本地注册)"""
@@ -105,6 +244,11 @@ class WorkerManager:
async with self._lock: async with self._lock:
self._workers_cache = new_cache self._workers_cache = new_cache
logger.debug(f"Worker 存活列表更新: {self._workers_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: except Exception as e:
logger.error(f"刷新 worker 列表失败: {e}") logger.error(f"刷新 worker 列表失败: {e}")
await asyncio.sleep(self._refresh_interval) await asyncio.sleep(self._refresh_interval)
@@ -448,3 +592,278 @@ class WorkerManager:
f"Worker '{worker_type}' 调用失败 (已尝试 {len(tried_workers)} 个 Worker, " f"Worker '{worker_type}' 调用失败 (已尝试 {len(tried_workers)} 个 Worker, "
f"共 {retries + 1} 次, timeout={timeout}s): {last_error}" f"共 {retries + 1} 次, timeout={timeout}s): {last_error}"
) )
# ------------------------------------------------------------------
# 管理能力:盘点 worker 实例 + 精细化控制单个副本(admin Queryable)
#
# 单副本协议(worker 框架声明):worker/<type>/<worker_id>/__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/<type>/<id>"
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,
}
+2 -1
View File
@@ -1,4 +1,5 @@
from .storage import router as storage_router from .storage import router as storage_router
from .stats import router as stats_router from .stats import router as stats_router
from .worker_admin import router as worker_admin_router
__all__ = ["storage_router", "stats_router"] __all__ = ["storage_router", "stats_router", "worker_admin_router"]
+238
View File
@@ -0,0 +1,238 @@
"""
Worker 管理端点(插件 / 单副本重启)
提供插件实例盘点与"精细化重启单个 worker 副本"的能力:
- 经 Zenoh 下发管理指令到目标的 admin Queryable(worker/<type>/<id>/__admin__)
- 仅超级管理员(JWT payload.is_superuser)可访问
协议说明(见 chongming_worker.runtime 单副本管理协议):
- 每个 worker 副本在 worker/<type>/<worker_id>/__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
Generated
+115 -125
View File
@@ -431,20 +431,20 @@ wheels = [
[[package]] [[package]]
name = "chongming-cache" name = "chongming-cache"
version = "0.1.0" version = "0.2.0"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "diskcache" }, { name = "diskcache" },
{ name = "redis" }, { 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 = [ 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]] [[package]]
name = "chongming-cli" name = "chongming-cli"
version = "0.1.0" version = "0.1.2"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "chongming-config" }, { name = "chongming-config" },
@@ -452,9 +452,9 @@ dependencies = [
{ name = "sqlite-vfs" }, { name = "sqlite-vfs" },
{ name = "tomli" }, { 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 = [ 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]] [[package]]
@@ -489,7 +489,9 @@ name = "chongming-jwt"
version = "0.1.0" version = "0.1.0"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
wheels = [ 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-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" }, { 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" version = "0.1.0"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
wheels = [ 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-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" }, { 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]] [[package]]
name = "chongming-worker" name = "chongming-worker"
version = "0.3.0" version = "0.4.0"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
dependencies = [ dependencies = [
{ name = "eclipse-zenoh" }, { name = "eclipse-zenoh" },
{ name = "minio" }, { name = "minio" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "tomli" }, { 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 = [ 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]] [[package]]
@@ -635,14 +640,11 @@ dev = [
[[package]] [[package]]
name = "click" name = "click"
version = "8.4.2" version = "8.5.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [ 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" }
{ 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" }
wheels = [ 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]] [[package]]
@@ -656,52 +658,52 @@ wheels = [
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "50.0.0" version = "50.0.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [ dependencies = [
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { 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 = [ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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]] [[package]]
@@ -1009,15 +1011,15 @@ wheels = [
[[package]] [[package]]
name = "jwcrypto" name = "jwcrypto"
version = "1.5.8" version = "1.5.9"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [ dependencies = [
{ name = "cryptography" }, { name = "cryptography" },
{ name = "typing-extensions" }, { 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 = [ 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]] [[package]]
@@ -1102,7 +1104,7 @@ sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/d8/bd
[[package]] [[package]]
name = "openai" name = "openai"
version = "3.3.1" version = "3.5.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@@ -1112,9 +1114,9 @@ dependencies = [
{ name = "sniffio" }, { name = "sniffio" },
{ name = "typing-extensions" }, { 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 = [ 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]] [[package]]
@@ -1246,71 +1248,50 @@ wheels = [
[[package]] [[package]]
name = "pyarmor-cli-core" name = "pyarmor-cli-core"
version = "8.1.1" version = "8.1.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
wheels = [ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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" },
{ 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" },
] ]
[[package]] [[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" }, { 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]] [[package]]
name = "transitions" name = "transitions"
version = "0.9.3" version = "0.9.3"