From 170e4a10a4d9428db26e04d8ab4173db9b20821a Mon Sep 17 00:00:00 2001 From: linaro Date: Fri, 16 Oct 2026 16:15:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4=EF=BC=9Aal?= =?UTF-8?q?gpkg-sdk=EF=BC=88=E7=AE=97=E6=B3=95=E5=8C=85=E5=BC=80=E5=8F=91?= =?UTF-8?q?=20SDK=EF=BC=89=E2=80=94=E2=80=94=20=E4=BB=8E=20sophon-edge=20?= =?UTF-8?q?=E4=BB=93=E5=BA=93=E6=8B=86=E5=88=86=E7=8B=AC=E7=AB=8B=E5=BB=BA?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本仓库原先内嵌在 sophon-edge 的 `algpkg-sdk/`(该仓库历史上的三次相关提交: e8101ba「算法开发能力独立成项目」、090c406「契约名落地为真实模块 + 多模型 ctx」、 b666161「sail 存根 + 编写风格修正」)。现拆出独立建库,便于算法作者单独 clone/迭代、 也便于单独发版(打成 wheel 分发给第三方或进 worker 交付包的 vendor/)。 - 内容:`src/algpkg_sdk/`(api/manifest/loader/builder/testkit/cli)+ 契约名模块 `src/video_algpkg_api/`、`examples/person-count`(完整示例)、`tests/`(pytest,无设备自动跳过)、 `README.md`(已改成独立仓库口径:命令在项目根执行,配套 worker 用路径依赖/`ALGPKG_SDK_DIR`)、 `pyproject.toml`(console script `algpkg`)、`uv.lock`; - 不含 `.venv/`(`.gitignore` 已覆盖,clone 后 `uv sync` 重建)。 配套 worker 仓库(sophon-edge): `workers/video/pyproject.toml` 以路径依赖指向本仓库(`path = "../../../algpkg-sdk"`,可用 `ALGPKG_SDK_DIR` 覆盖);交付打包 `deploy/pack_worker.py` 会把本仓库打成 wheel 放进 worker 包 `vendor/`。 --- .gitignore | 5 + README.md | 169 ++++ .../alg_demo_person_count/__init__.py | 146 ++++ examples/person-count/assets/labels.txt | 80 ++ examples/person-count/manifest.json | 41 + pyproject.toml | 50 ++ src/algpkg_sdk/__init__.py | 39 + src/algpkg_sdk/api.py | 247 ++++++ src/algpkg_sdk/builder.py | 299 +++++++ src/algpkg_sdk/cli.py | 448 ++++++++++ src/algpkg_sdk/loader.py | 400 +++++++++ src/algpkg_sdk/manifest.py | 350 ++++++++ src/algpkg_sdk/py.typed | 0 src/algpkg_sdk/testkit.py | 306 +++++++ src/video_algpkg_api/__init__.py | 35 + src/video_algpkg_api/py.typed | 0 tests/test_sdk.py | 388 +++++++++ uv.lock | 789 ++++++++++++++++++ 18 files changed, 3792 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 examples/person-count/alg_demo_person_count/__init__.py create mode 100644 examples/person-count/assets/labels.txt create mode 100644 examples/person-count/manifest.json create mode 100644 pyproject.toml create mode 100644 src/algpkg_sdk/__init__.py create mode 100644 src/algpkg_sdk/api.py create mode 100644 src/algpkg_sdk/builder.py create mode 100644 src/algpkg_sdk/cli.py create mode 100644 src/algpkg_sdk/loader.py create mode 100644 src/algpkg_sdk/manifest.py create mode 100644 src/algpkg_sdk/py.typed create mode 100644 src/algpkg_sdk/testkit.py create mode 100644 src/video_algpkg_api/__init__.py create mode 100644 src/video_algpkg_api/py.typed create mode 100644 tests/test_sdk.py create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9d7908 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# 本项目自己的 venv 与构建产物 +.venv/ +dist/ +*.egg-info +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..28b1178 --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ +# algpkg-sdk —— 算法包开发 SDK + +独立项目:**算法作者只依赖本项目**,即可完成「写算法 → 本地跑通 → 打包 → 自检 → 交付 `.mbank`」, +不需要 worker 仓库,也不需要了解 worker 内部结构。 + +```text +算法包开发(本项目) 设备运行(worker) +────────────────────── ────────────────── +写代码 / 本地测试 / 打包 / 签名 → .mbank → 上传 → 挂载 → 推理 + 用的接口 = algpkg_sdk.api 同一份接口(别名 video_algpkg_api) +``` + +## 1. 安装 + +> 本项目是**独立仓库**(2026-10-16 从 sophon-edge 仓库拆出): +> `git clone <本仓库地址> && cd algpkg-sdk`(目录名即项目名;下文命令都在**本项目根**执行)。 + +```bash +# 从本项目根安装(开发态;也可打成 wheel 分发给第三方) +uv pip install -e . # 只做静态检查/打包的机器 +uv pip install -e '.[device]' # 设备上(含 sophon-arm,可跑推理) +``` + +配套的 **worker(视频推理系统)仓库**只在使用 `algpkg` 工具链做集成联调时才需要: +worker 侧 `workers/video/pyproject.toml` 以**路径依赖**指向本项目(`path = "../../../algpkg-sdk"`), +交付打包(`deploy/pack_worker.py`)会把本项目打成 wheel 放进 worker 交付包的 `vendor/`; +路径可用环境变量 **`ALGPKG_SDK_DIR`** 覆盖(三个开发脚本与打包脚本都认它)。 + +依赖很轻:`module-bank`(`.mbank` 读写)、`orjson`、`numpy`。 +**设备推理包(sophon-arm)是可选的** —— 没有 NPU 的机器照样能打包与做静态校验。 + +## 2. 三步上手 + +```bash +# ① 生成脚手架(目录骨架 + manifest 模板 + 可运行示例 + 自测说明) +algpkg init /tmp/smoke-detect --id demo.smoke-detect --name 抽烟检测 --version 1.0.0 + +# ② 实现 /tmp/smoke-detect/<包内代码目录>/__init__.py 的 load() / infer() +# 模型放 /tmp/smoke-detect/models/,或打包时用 --model 指定 + +# ③ 打包 + 自检(不需要设备) +algpkg build /tmp/smoke-detect --out demo.smoke-detect-1.0.0.mbank +algpkg inspect demo.smoke-detect-1.0.0.mbank # 清单/模型/快照/签名 +algpkg verify demo.smoke-detect-1.0.0.mbank # 结构与模型校验 +``` + +## 3. 本地测试(不需要 worker) + +```bash +# 静态检查:manifest 合法性 / 包结构 / 模型文件 / 代码可导入(试加载) +algpkg check demo.smoke-detect-1.0.0.mbank +algpkg check demo.smoke-detect-1.0.0.mbank --deep # 再实例化入口类(需设备) + +# 设备上真跑:图片或视频,warmup 后统计单帧耗时,可画框存图 +algpkg run demo.smoke-detect-1.0.0.mbank --source clip.mp4 --frames 5 --save-annotated out/ +algpkg run a-1.0.0.mbank --source a.jpg --params '{"conf":0.3}' + +# 两个包/版本对拍(逐帧检出数 + 逐位一致性) +algpkg compare a-1.0.0.mbank b-1.0.0.mbank --source clip.mp4 +``` + +同样能力在 Python 里直接调(适合写进自己的 pytest): + +```python +from algpkg_sdk import testkit + +report = testkit.check("demo.smoke-detect-1.0.0.mbank") # 任意机器 +runs = testkit.run("demo.smoke-detect-1.0.0.mbank", source="clip.mp4", + params={"conf": 0.3}, save_annotated="out/") # 设备 +print(runs.summary()) +print(testkit.compare(runs, testkit.run("old.mbank", source="clip.mp4"))) +``` + +## 4. 写算法:接口契约 + +包内代码只应导入 `video_algpkg_api`(接口契约名,= `algpkg_sdk.api` 的同一批对象)+ 设备框架: + +> 装好本 SDK 后,这个导入名在**任何工具里都直接可用**:IDE 补全 / mypy / pyright / +> 本地 `python -c "import video_algpkg_api"`(SDK 自带同名真实模块 + PEP 561 `py.typed`)。 +> 设备运行时 worker 仍注册同名别名,两条解析路径拿到的是同一批对象; +> **不要**自己 vendor/复制一份同名模块(两份实现分叉 = "本地过、设备挂")。 + +```python +from video_algpkg_api import Algorithm, AlgoContext, AlgoResult, AlgoError + +class Algorithm(Algorithm): # 类名与 manifest.entry 一致 + kind = "detector" + + def load(self) -> None: + data = self.ctx.model_bytes() # 包内模型字节(不落盘,直接给引擎) + conf = float(self.ctx.param("conf", 0.25)) + self._labels = self.ctx.read_asset("assets/labels.txt").decode().splitlines() + self._engine = MyEngine(data, self.ctx.dev_id, conf) # 例如 sail.Engine(bytes, …) + + def infer(self, frame) -> AlgoResult: # 单帧;抽帧由运行方控制 + return AlgoResult(boxes=self._engine(frame)) # (N,6): x1,y1,x2,y2,score,cls + + def draw(self, frame, result): # 可选:自定义叠加 + return None + + def close(self) -> None: # 实例回收时调用 + self._engine = None +``` + +| 用法 | 说明 | +| ---- | ---- | +| `ctx.model_bytes(name)` / `ctx.read_asset(rel)` | 读**包内**文件(模型/类别表/阈值表),不落盘;不给名 = 选中模型,给名 = 任意已声明模型(多模型包,见下) | +| `ctx.param(key, default)` | 任务参数(`params_schema` 默认值已合并,越界在装载时被拒) | +| `ctx.dev_id` / `ctx.logger` / `ctx.meta` | 设备号 / 日志 / 选中模型的元信息(精度、分辨率…) | +| `infer` 返回 | `AlgoResult(boxes=(N,6), counts={"person": n}, value=n, labels=[...])` | +| `load()` 抛异常 | 任务转 failed,错误信息带包 id/版本(先在本地 `algpkg check --deep` 拦住) | + +约定:同一实例不会被并发调用(运行方串行化),但 `infer` 不要长时间阻塞; +线程与内存预算见《算法包开发指南》§5。 + +**多模型包**(如"检测 + 二次判定/误报过滤"):一个包声明多份模型(`manifest.models[]`), +代码里 `ctx.model_bytes("fp_filter")` 按名字取第二份——不必硬编码包内路径: + +```python +def load(self): + self._det = ctx.model_bytes() # 选中模型(manifest 里 default 的那份) + self._cls = ctx.model_bytes("fp_filter") # 第二份:裁检测框 → 分类判误报 +``` + +两级管线写在 `infer()` 里:返回**过滤后**的 boxes 即可,告警/统计/叠加图自动跟着走 +(运行时不做级联,这就是"包内级联"的标准形态;细节与算力旋钮见《算法包开发指南》§5)。 + +## 5. 打包选项 + +```bash +algpkg build <源目录> --out x.mbank \ + --model <模型名>=<磁盘路径> \ # 模型不在源码目录时显式指定(包内路径以 manifest 为准) + --encrypt \ # 加密包内代码(明文源码会被删除;模型不加密) + --pyarmor \ # PyArmor 混淆(需授权) + --sign --key-id # HMAC-SHA256 签名(对称;设备侧配相同密钥验签) +algpkg list <目录> # 列目录内的包(id/版本/大小) +``` + +## 6. 目录结构 + +```text +algpkg-sdk/ +├── pyproject.toml 项目定义(scripts: algpkg) +├── src/algpkg_sdk/ +│ ├── api.py 接口契约(Algorithm / AlgoContext / AlgoResult)+ 别名注册 +│ ├── manifest.py 清单字段与校验(models / params_schema / alarm / legacy…) +│ ├── loader.py AlgoPackage:读 svfs、挂代码(试加载)、构造实例 +│ ├── builder.py 打包(module_bank 打代码 + svfs 放模型/资源 + 快照=版本 + 签名) +│ ├── testkit.py 本地测试台:check / run / compare(无需 worker) +│ └── cli.py algpkg build|inspect|verify|init|list|check|run|compare +├── examples/person-count/ 完整示例:人员计数(自带实现,只依赖本 SDK) +│ └── … +└── tests/ pytest(无设备跑静态,有设备跑推理,自动跳过) +``` + +## 7. 自测 + +```bash +cd algpkg-sdk +uv run pytest # 静态用例;标记为 device 的用例在无 NPU 机器上自动跳过 +uv run pytest -m device # 设备上跑推理用例 +``` + +## 8. 与设备的对接 + +1. `algpkg build` 产出单文件 `.mbank`; +2. 在设备「算法管理」页上传(或 `POST /api/v1/video/algorithm/install` 分片上传 → `commit`); +3. 任务里选择该算法(参数表单由 `manifest.params_schema` 生成),可与其他算法并行; +4. 现场排障与完整字段表见仓库文档《算法包开发指南》;设计依据见《算法包与多算法任务设计》。 diff --git a/examples/person-count/alg_demo_person_count/__init__.py b/examples/person-count/alg_demo_person_count/__init__.py new file mode 100644 index 0000000..42966c7 --- /dev/null +++ b/examples/person-count/alg_demo_person_count/__init__.py @@ -0,0 +1,146 @@ +"""人员计数示例算法包(M8.2/M8.4)——**自带推理实现**,不依赖 worker 内部模块。 + +这是"第三方算法包"的样板:只依赖 +- `video_algpkg_api`(worker 提供的稳定接口:Algorithm / AlgoContext / AlgoResult); +- 设备框架 `sophon.sail` 与 `numpy`; +- 包内模型(`ctx.model_bytes()` 从 `.mbank` 读字节,**不落盘**)。 + +算法语义:YOLOv26s 检测 → 只保留 person(COCO 类 0)→ 置信度/最小人高过滤 → +人数作为 `AlgoResult(counts={"person": n}, value=n)` 返回,并把人员框一并给出(供绘制/告警)。 + +与内置包(`builtin.yolov26-detect`)的区别:kind=counter、默认抽帧更慢(0.5s)、 +只报人员、带最小人高参数 —— 用来验证一个任务同时挂"检测 + 计数"两个算法。 +""" +from __future__ import annotations + +import time +from typing import List + +import numpy as np +import sophon.sail as sail + +from video_algpkg_api import Algorithm as AlgorithmBase, AlgoResult + +PERSON_CLASS = 0 # COCO 类别索引(person) +_BATCH_SUPPORTED = (1, 2, 3, 4, 8, 16, 32, 64, 128, 256) + + +class Algorithm(AlgorithmBase): + """画面人员计数(单 bmodel,SAIL 引擎 + bmcv 前处理)。""" + + kind = "counter" + + #: 引擎与输入张量:load() 赋值、close() 释放。类级声明为非 Optional,infer() 里可直接用 + #: (不必判空);close() 置 None 与声明冲突,那两处显式 `type: ignore`——运行时语义就是"丢引用"。 + _net: sail.Engine + _input_tensor: sail.Tensor + + # ------------------------------------------------------------------ 生命周期 + + def load(self) -> None: + self._conf = float(self.ctx.param("conf", 0.3)) + self._min_height = int(self.ctx.param("min_height", 0)) + data = self.ctx.model_bytes() + self._bytes = data # 引擎构造后仍持有引用(不依赖 SDK 拷贝语义) + self._handle = sail.Handle(self.ctx.dev_id) + self._bmcv = sail.Bmcv(self._handle) + self._net = sail.Engine(data, len(data), self.ctx.dev_id, sail.IOMode.SYSO) + self._graph = self._net.get_graph_names()[0] + + name = self._net.get_input_names(self._graph)[0] + self._input_name = name + self._input_dtype = self._net.get_input_dtype(self._graph, name) + self._img_dtype = self._bmcv.get_bm_image_data_format(self._input_dtype) + self._input_scale = self._net.get_input_scale(self._graph, name) + self._input_shape = self._net.get_input_shape(self._graph, name) + self._input_shapes = {name: self._input_shape} + if self._input_shape[0] not in _BATCH_SUPPORTED: + raise RuntimeError(f"batch={self._input_shape[0]} 不受支持") + self._net_h, self._net_w = self._input_shape[2], self._input_shape[3] + + self._output_names = self._net.get_output_names(self._graph) + self._output_tensors = {} + for out in self._output_names: + shape = self._net.get_output_shape(self._graph, out) + dtype = self._net.get_output_dtype(self._graph, out) + self._output_tensors[out] = sail.Tensor(self._handle, shape, dtype, True, True) + # scale/255,均值 0(与训练一致的 letterbox 归一化) + self._ab = [v * self._input_scale / 255.0 for v in (1, 0, 1, 0, 1, 0)] + self._input_tensor = sail.Tensor(self._handle, self._input_shape, + self._input_dtype, False, False) + self.ctx.logger.info("[%s] 模型就绪(%.1f MB, conf=%.2f, min_height=%d)", + self.ctx.algo_id, len(data) / 1048576.0, self._conf, self._min_height) + + # ------------------------------------------------------------------ 推理 + + def infer(self, frame) -> AlgoResult: + started = time.perf_counter() + pre, ratio, tx, ty = self._preprocess(frame) + self._bmcv.bm_image_to_tensor(pre, self._input_tensor) + self._net.process(self._graph, {self._input_name: self._input_tensor}, + self._input_shapes, self._output_tensors) + preds = self._output_tensors[self._output_names[0]].asnumpy()[:1] + persons = self._postprocess(preds[0], frame.width(), frame.height(), ratio, tx, ty) + boxes = np.zeros((len(persons), 6), dtype=np.float32) if not persons else \ + np.stack(persons).astype(np.float32) + count = int(boxes.shape[0]) + return AlgoResult(boxes=boxes if count else None, + counts={"person": count} if count else {}, + value=float(count), + meta={"infer_ms": round((time.perf_counter() - started) * 1000, 1)}) + + def close(self) -> None: + self._net = None # type: ignore[assignment] + self._output_tensors = {} + self._input_tensor = None # type: ignore[assignment] + self._bytes = b"" + + # ------------------------------------------------------------------ 内部 + + def _preprocess(self, bmimg): + """BGRA/NV12 → RGB_PLANAR → letterbox → 归一化 到网络输入。""" + rgb = sail.BMImage(self._handle, bmimg.height(), bmimg.width(), + sail.Format.FORMAT_RGB_PLANAR, sail.DATA_TYPE_EXT_1N_BYTE) + self._bmcv.convert_format(bmimg, rgb) + img_w, img_h = rgb.width(), rgb.height() + r = min(self._net_w / img_w, self._net_h / img_h) + tw, th = int(round(r * img_w)), int(round(r * img_h)) + tx, ty = (self._net_w - tw) / 2, (self._net_h - th) / 2 + attr = sail.PaddingAtrr() + attr.set_stx(int(round(tx - 0.1))) + attr.set_sty(int(round(ty - 0.1))) + attr.set_w(tw) + attr.set_h(th) + attr.set_r(114) + attr.set_g(114) + attr.set_b(114) + resized = self._bmcv.crop_and_resize_padding( + rgb, 0, 0, img_w, img_h, self._net_w, self._net_h, attr, + sail.bmcv_resize_algorithm.BMCV_INTER_LINEAR, + ) + pre = sail.BMImage(self._handle, self._net_h, self._net_w, + sail.Format.FORMAT_RGB_PLANAR, self._img_dtype) + self._bmcv.convert_to( + resized, pre, + ((self._ab[0], self._ab[1]), (self._ab[2], self._ab[3]), (self._ab[4], self._ab[5])), + ) + return pre, r, tx, ty + + def _postprocess(self, batch, org_w: int, org_h: int, ratio: float, + tx: float, ty: float) -> List[list]: + """(N,6) 端到端输出 → 人员框(原图坐标;conf / 最小人高过滤)。""" + rows = batch[batch[:, 4] > self._conf] if batch.size else batch + out: List[list] = [] + for row in rows: + if int(row[5]) != PERSON_CLASS: + continue + x1 = float((row[0] - tx) / ratio) + y1 = float((row[1] - ty) / ratio) + x2 = float((row[2] - tx) / ratio) + y2 = float((row[3] - ty) / ratio) + x1, x2 = max(0.0, min(x1, org_w - 1)), max(0.0, min(x2, org_w - 1)) + y1, y2 = max(0.0, min(y1, org_h - 1)), max(0.0, min(y2, org_h - 1)) + if self._min_height and (y2 - y1) < self._min_height: + continue + out.append([x1, y1, x2, y2, float(row[4]), PERSON_CLASS]) + return out diff --git a/examples/person-count/assets/labels.txt b/examples/person-count/assets/labels.txt new file mode 100644 index 0000000..941cb4e --- /dev/null +++ b/examples/person-count/assets/labels.txt @@ -0,0 +1,80 @@ +person +bicycle +car +motorcycle +airplane +bus +train +truck +boat +traffic light +fire hydrant +stop sign +parking meter +bench +bird +cat +dog +horse +sheep +cow +elephant +bear +zebra +giraffe +backpack +umbrella +handbag +tie +suitcase +frisbee +skis +snowboard +sports ball +kite +baseball bat +baseball glove +skateboard +surfboard +tennis racket +bottle +wine glass +cup +fork +knife +spoon +bowl +banana +apple +sandwich +orange +broccoli +carrot +hot dog +pizza +donut +cake +chair +couch +potted plant +bed +dining table +toilet +tv +laptop +mouse +remote +keyboard +cell phone +microwave +oven +toaster +sink +refrigerator +book +clock +vase +scissors +teddy bear +hair drier +toothbrush diff --git a/examples/person-count/manifest.json b/examples/person-count/manifest.json new file mode 100644 index 0000000..267a904 --- /dev/null +++ b/examples/person-count/manifest.json @@ -0,0 +1,41 @@ +{ + "manifest_format": 1, + "id": "demo.person-count", + "name": "人员计数(示例算法包)", + "version": "1.0.0", + "api_version": 1, + "kind": "counter", + "entry": "Algorithm", + "code_package": "alg_demo_person_count", + "vendor": "chongming", + "description": "画面人员计数示例包(M8.2/M8.4):自带推理实现(不依赖 worker 内部模块),只用到 video_algpkg_api 的 ctx.model_bytes/param 与 AlgoResult;输出人数(counts/value)与人员框,用于验证\"一个任务挂多个算法\"。", + "models": [ + { + "name": "yolo26s_fp16_1b", + "file": "models/yolo26s_fp16_1b.bmodel", + "precision": "fp16", + "resolution": [640, 640], + "batch": 1, + "default": true + } + ], + "input": {"width": 640, "height": 640, "color": "rgb", "layout": "planar", + "mean": [0, 0, 0], "std": [255, 255, 255]}, + "output": {"type": "counter", "format": "N6", "classes_file": "assets/labels.txt"}, + "params_schema": [ + {"key": "conf", "type": "float", "default": 0.3, "min": 0.01, "max": 0.99, "step": 0.01, + "label": "人员置信度阈值", "desc": "低于该分数的人员不计入"}, + {"key": "min_height", "type": "int", "default": 0, "min": 0, "max": 2000, "step": 10, + "label": "最小人高(像素)", "desc": "过滤远处的误检(0 = 不过滤)"}, + {"key": "interval", "type": "float", "default": 0.5, "min": 0.02, "max": 60, "step": 0.02, + "label": "抽帧间隔(秒)", "desc": "计数类算法可放慢,省算力"}, + {"key": "draw", "type": "bool", "default": false, + "label": "绘制叠加", "desc": "把人员框画到输出帧上"} + ], + "defaults": {"conf": 0.3, "min_height": 0, "interval": 0.5, "draw": false}, + "alarm": {"type_template": "person_count", "cooldown_key": "algo", + "need_snapshot": true, "need_record": false}, + "requires": {"license_features": [], "devices": ["BM1688", "BM1684X", "CV186X"], "frameworks": ["sail"]}, + "ui": {"order": 20, "tags": ["示例", "计数"], "icon": "mdi-account-multiple"}, + "legacy": {} +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..58d7e30 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +# 算法包开发 SDK —— 独立项目(可单独 clone / 分发 / 出包) +# +# 定位:算法作者只需要这一个项目 + 一台设备(或没有设备也能做静态校验), +# 即可完成「写算法 → 本地跑通 → 打包 → 自检 → 交付 .mbank」的全流程, +# 不需要 worker 仓库、也不需要了解 worker 内部结构。 +[project] +name = "algpkg-sdk" +version = "0.1.0" +description = "算法包(.mbank)开发、测试与打包工具链" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + # .mbank 读写(svfs 虚拟文件系统 + 模块银行打包器) + "module-bank>=0.4.2", + "orjson>=3.10", + "numpy>=1.26", + # 注:设备侧推理(sophon-arm / sail)**不是必需依赖** —— 没有 NPU 的机器照样能做 + # manifest 校验、打包、挂载试加载(不实跑模型);只有 `algpkg run` 需要设备。 +] + +[project.optional-dependencies] +# 设备上跑推理/本地测试台时需要(BM1684X / BM1688 / CV186X 等 Sophon 设备) +device = ["sophon-arm>=3.11.1"] +# 开发本项目自身(单测 + 类型) +dev = ["pytest>=8", "sophon-arm>=3.11.1"] + +[project.scripts] +algpkg = "algpkg_sdk.cli:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +# 两个顶层包同属本发行:algpkg_sdk(工具链)+ video_algpkg_api(接口契约名的真实模块, +# 算法包代码只导入它;重导出 algpkg_sdk.api,见 src/video_algpkg_api/__init__.py) +packages = ["src/algpkg_sdk", "src/video_algpkg_api"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +markers = [ + "device: 需要 Sophon 设备(NPU)才能跑;无设备时自动跳过", +] + +# 索引:内部源(含 sophon-arm 等设备包)优先,公共源兜底 —— 与 workers/video 同口径 +[[tool.uv.index]] +url = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" + +[[tool.uv.index]] +url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" diff --git a/src/algpkg_sdk/__init__.py b/src/algpkg_sdk/__init__.py new file mode 100644 index 0000000..bad29ee --- /dev/null +++ b/src/algpkg_sdk/__init__.py @@ -0,0 +1,39 @@ +"""算法包 SDK(独立项目):算法包(`.mbank`)的开发、测试与打包工具链。 + +给算法作者的三件东西: + +| 用途 | 入口 | +| ---- | ---- | +| **写算法** —— 实现接口、读包内资源、返回结果 | `from algpkg_sdk import Algorithm, AlgoContext, AlgoResult`(也可沿用稳定别名 `video_algpkg_api`) | +| **本地跑通** —— 不用 worker,直接把包跑在图片/视频上 | `algpkg run <包.mbank> --image <图片>` 或 `from algpkg_sdk import testkit` | +| **打包与自检** —— 单文件 `.mbank`,可加密/混淆/签名 | `algpkg build / inspect / verify` | + +worker 侧把本项目的 `api` 模块以 `video_algpkg_api` 注入进程(见 :func:`algpkg_sdk.api.install_sdk_alias`), +因此**同一份算法代码在本地测试与设备运行时看到的是同一套接口**。 +""" +from __future__ import annotations + +from .api import (ALG_API_VERSION, AlgoContext, AlgoError, AlgoResult, Algorithm, + install_sdk_alias) +from .builder import AlgoBuilder, BuildError, inspect_package, verify_package +from .loader import (AlgoPackage, AlgoPackageError, namespace_loaded, purge_namespace, + sha256_file) +from .manifest import (MANIFEST_NAME, Manifest, ManifestError, ModelSpec, + build_manifest, code_package_for) + +__version__ = "0.1.0" + +#: 算法包代码里 `import` 的稳定模块名(契约名):本 SDK 以**同名真实模块**分发 +#: (`src/video_algpkg_api/`,重导出本模块的 api —— 见 video_algpkg_api/__init__.py), +#: worker 运行时再注册同名别名(setdefault,同一批对象,谁先解析都一样) +SDK_MODULE_NAME = "video_algpkg_api" + +install_sdk_alias() # 导入本包即注册别名:包内代码 import video_algpkg_api 立即可用 + +__all__ = [ + "ALG_API_VERSION", "AlgoContext", "AlgoError", "AlgoResult", "Algorithm", + "install_sdk_alias", "AlgoBuilder", "BuildError", "inspect_package", "verify_package", + "AlgoPackage", "AlgoPackageError", "purge_namespace", "namespace_loaded", "sha256_file", + "MANIFEST_NAME", "Manifest", "ManifestError", "ModelSpec", "build_manifest", + "code_package_for", "SDK_MODULE_NAME", "__version__", +] diff --git a/src/algpkg_sdk/api.py b/src/algpkg_sdk/api.py new file mode 100644 index 0000000..70ca20b --- /dev/null +++ b/src/algpkg_sdk/api.py @@ -0,0 +1,247 @@ +"""算法包 SDK(M8.1):算法实现方(内置 / 第三方)与 worker 之间的稳定接口。 + +**这是算法包的"接口契约"层**,与 worker 内部实现解耦: + +- 算法包里的代码 `from video_algpkg_api import Algorithm, AlgoContext, AlgoResult` + 即可(本模块在 worker 导入时以 `video_algpkg_api` 注册进 ``sys.modules``); +- 版本兼容由 :data:`ALG_API_VERSION` 把关(manifest.api_version,worker 装载时校验); +- 结果结构 :class:`AlgoResult` 是"检测框 / 计数"的统一载体,绘制、告警、统计都只认它。 + +**生命周期**(worker 侧调用顺序):: + + Algorithm(ctx) # 构造:只保存 ctx,不做重活 + algo.load() # 加载模型(可失败:抛异常 → 任务 failed,见 controller) + algo.infer(frame) # 单帧推理(抽帧由运行时统一控制) + algo.draw(frame, result) # 可选:自定义叠加;缺省用 worker 的统一 Drawer + algo.close() # 释放(模型实例回收时调用) + +线程模型:同一实例**不会**被并发调用(worker 侧 ModelRegistry 用互斥锁串行化), +实现方不必自己加锁;但 `infer` 不得长时间阻塞(抽帧循环是协作式的)。 +""" +from __future__ import annotations + +import logging +import sys +from typing import Any, Callable, Optional + +import numpy as np + +#: 接口版本:worker 装载算法包时校验 `manifest.api_version <= ALG_API_VERSION`。 +#: 破坏性变更才 +1(不兼容的旧包会被拒绝装载并给出可读原因)。 +ALG_API_VERSION = 1 + +#: 算法包代码里 `import` 的稳定模块名(本模块在 worker 侧以此名注册)。 +SDK_MODULE_NAME = "video_algpkg_api" + +#: 算法包类型(manifest.kind)——决定 UI 呈现与结果渲染方式。 +KINDS = ("detector", "classifier", "counter", "tracker") + +#: 结果类型(manifest.output.type)。 +OUTPUT_TYPES = ("boxes", "count", "counter") + + +class AlgoError(RuntimeError): + """算法实现方主动抛出的可读错误(worker 会把它作为任务 last_error 展示)。""" + + +class AlgoResult: + """一次推理的结果(统一载体)。 + + - ``boxes``:``(N, 6)`` float32/float64 数组(x1,y1,x2,y2,score,cls,原图坐标系) + —— 绘制、告警、统计都基于它;无框的算法(纯计数)留 None; + - ``counts``:计数类算法的分项计数(如 ``{"person": 3}``;无分项可用 ``{"": n}``); + - ``value``:单值结果(如"当前画面人数"); + - ``labels``:类别名(下标与 boxes 的 cls 对应;缺省用包内 labels 或 COCO 表)。 + """ + + __slots__ = ("boxes", "counts", "value", "labels", "meta") + + def __init__(self, boxes: Any = None, counts: dict | None = None, + value: float | None = None, labels: Optional[list[str]] = None, + meta: dict | None = None): + if boxes is not None: + boxes = np.asarray(boxes) + if boxes.ndim != 2 or (boxes.size and boxes.shape[1] != 6): + raise AlgoError(f"boxes 形状必须是 (N,6)(x1,y1,x2,y2,score,cls),收到 {boxes.shape}") + self.boxes = boxes + self.counts = counts + self.value = float(value) if value is not None else None + self.labels = labels + self.meta = meta or {} + + # ------------------------------------------------------------------ 便捷 + + def detections(self) -> int: + """检出目标数(告警/统计口径:boxes 行数,其次 counts 合计,其次 value)。""" + if self.boxes is not None: + return int(self.boxes.shape[0]) + if self.counts: + return int(sum(self.counts.values())) + return int(self.value or 0) + + def top(self, index: int = 4): + """置信度最高的那行(告警取"最可信目标"用);无框返回 None。""" + if self.boxes is None or self.boxes.shape[0] == 0: + return None + return max(self.boxes, key=lambda row: float(row[index])) + + def summary(self) -> dict: + """统计/接口展示用的紧凑摘要。""" + return { + "detections": self.detections(), + "counts": self.counts, + "value": self.value, + } + + +class AlgoContext: + """算法实现方可见的上下文(模型字节、参数、设备、资源读取、日志)。 + + 由 worker 构造注入;算法实现只读使用。**不要缓存 `model_bytes()` 的返回值之外的重对象**, + 实例回收(卸载/空闲 TTL)时 worker 会调用 ``close()``。 + + **多模型包**(如"检测 + 二次判定/误报过滤"):一个包可声明多份模型(manifest.models[]), + 任务选中的那份是"入口模型"(:meth:`model_bytes` 不给名时读它),其余按名字取用:: + + self._det = ctx.model_bytes() # 选中模型(manifest 里 default 的那份) + self._cls = ctx.model_bytes("fp_filter") # 包内第二份(按 manifest.models[].name) + """ + + def __init__(self, *, algo_id: str, version: str, model_name: str, params: dict, + dev_id: int = 0, meta: dict | None = None, kind: str = "detector", + output: dict | None = None, logger: logging.Logger | None = None, + reader: Callable[[str], bytes] | None = None, + models: list[dict] | None = None): + self.algo_id = algo_id + self.version = version + self.model_name = model_name + self.params = dict(params or {}) + self.dev_id = int(dev_id) + #: 选中模型在 manifest.models[] 里的完整条目(precision/resolution/batch…) + self.meta = dict(meta or {}) + #: 包内**全部**已声明模型条目(含 meta 本身;多模型包按名取用,见 model_bytes) + self.models: tuple[dict, ...] = tuple( + dict(item) for item in (models if models else ([self.meta] if self.meta else [])) + ) + self.kind = kind + self.output = dict(output or {}) + self.logger = logger or logging.getLogger(f"algo.{algo_id}") + self._reader = reader + + # ------------------------------------------------------------------ 资源 + + def model_bytes(self, name: str | None = None) -> bytes: + """读模型字节(从算法包 VFS 读;每次调用都会重新读,需要缓存请自行持有)。 + + `name` 空 = **任务选中的模型**(向后兼容的旧写法);给名字 = 按 + `manifest.models[]` 的 `name`(或 `file` 路径)取包内任意已声明模型—— + 多模型包(如检测 + 二次判定)用它读第二份模型,不必硬编码包内路径。 + """ + if self._reader is None: + raise AlgoError("ctx 未绑定包内读取器(reader)——算法实现不应自行构造 AlgoContext") + if not str(name or "").strip(): + return self._reader(self.meta.get("file") or self.model_name) + spec = self.model_spec(name) + return self._reader(str(spec.get("file") or spec.get("name"))) + + def model_spec(self, name: str | None = None) -> dict: + """查已声明模型的完整条目(precision/resolution/batch/default)。 + + `name` 空 = 选中模型;不存在时抛 :class:`AlgoError`(消息里带可选清单)。 + """ + wanted = str(name or "").strip() + if not wanted: + return dict(self.meta) + for spec in self.models: + if str(spec.get("name") or "") == wanted or str(spec.get("file") or "") == wanted: + return dict(spec) + available = ", ".join(str(spec.get("name")) for spec in self.models) or "(无)" + raise AlgoError(f"算法包 {self.algo_id} 内没有模型 {wanted!r}(可选:{available})") + + def read_asset(self, rel_path: str) -> bytes: + """读取包内任意文件(assets/labels.txt 等)。""" + if self._reader is None: + raise AlgoError("ctx 未绑定包内读取器(reader)") + return self._reader(rel_path) + + def param(self, key: str, default: Any = None) -> Any: + """取任务参数(manifest.defaults / params_schema 的默认值已合并进来)。 + + 返回 `Any`:值的类型由 manifest.params_schema 决定,取用处自行转换 + (如 `conf = float(ctx.param("conf", 0.3))`)。 + """ + return self.params.get(key, default) + + +class Algorithm: + """算法实现基类(算法包入口,见 manifest.entry)。 + + 最小实现(包内 `__init__.py`):: + + from video_algpkg_api import Algorithm, AlgoContext, AlgoResult + + class Algorithm(Algorithm): # 类名固定为 Algorithm(manifest.entry 可改) + def load(self): + self._engine = MyEngine(self.ctx.model_bytes(), self.ctx.dev_id) + + def infer(self, frame): + return AlgoResult(boxes=self._engine(frame)) + + `infer` 返回 :class:`AlgoResult`、``(N,6)`` 数组或 dict(自动包装)均可。 + """ + + #: 实现方声明的接口版本(与包 manifest.api_version 一致即可) + api_version = ALG_API_VERSION + #: 结果类型提示(缺省由 manifest 决定) + kind = "detector" + + def __init__(self, ctx: AlgoContext): + self.ctx = ctx + self._loaded = False + + # ------------------------------------------------------------------ 生命周期 + + def load(self) -> None: + """加载模型/初始化(可能耗时;失败即抛异常,任务会转 failed 并带可读错误)。""" + + def infer(self, frame) -> AlgoResult: # pragma: no cover - 抽象 + raise NotImplementedError("算法包必须实现 infer(frame)") + + def draw(self, frame, result: AlgoResult): + """可选:自定义叠加绘制(返回新帧)。缺省返回 None → worker 用统一 Drawer。""" + return None + + def close(self) -> None: + """释放资源(引擎句柄等);worker 在实例回收时调用,可重复调用。""" + + # ------------------------------------------------------------------ worker 适配 + + def ensure_loaded(self) -> "Algorithm": + if not self._loaded: + self.load() + self._loaded = True + return self + + def __call__(self, images: list): + """批量入口(与历史 `ModelEntry.infer(images)` 签名一致):返回每张图的 boxes。""" + self.ensure_loaded() + boxes = [] + for image in images: + result = self.infer(image) + if isinstance(result, AlgoResult): + boxes.append(result.boxes if result.boxes is not None + else np.zeros((0, 6), dtype=np.float32)) + elif isinstance(result, dict): + boxes.append(np.asarray(result.get("boxes") or np.zeros((0, 6), dtype=np.float32))) + else: + boxes.append(np.asarray(result)) + return boxes + + +def install_sdk_alias(name: str = SDK_MODULE_NAME) -> str: + """把本模块注册成稳定的导入名(算法包代码 `import video_algpkg_api` 依赖它)。 + + 用 ``setdefault``:包代码若已自带同名模块(自测场景)则不覆盖。 + """ + sys.modules.setdefault(name, sys.modules[__name__]) + return name diff --git a/src/algpkg_sdk/builder.py b/src/algpkg_sdk/builder.py new file mode 100644 index 0000000..8734152 --- /dev/null +++ b/src/algpkg_sdk/builder.py @@ -0,0 +1,299 @@ +"""算法包打包器(M8.4 工具链底座):源码目录 → 单文件 `.mbank`。 + +打包产物 = 一个标准 svfs 包(`.mbank`),内部结构:: + + manifest.json # 清单(Manifest.parse 校验) + modules//** # 包内代码(.pyc 锚点 + .py 源码 + .meta.json) + models/<...>.bmodel # 随包模型(多份精度/核数) + assets/labels.txt # 可选资源(类别名、阈值表…) + +实现复用现有底座(不新造轮子): +- 代码部分 = `module_bank.PythonToSQLite.pack_directory()`(同一套 .pyc/元数据/可选 PyArmor); +- manifest / 模型 / 资源 = `packer.vfs.add_bytes()`(**任意文件**都能进 svfs,见 §3 取证); +- 版本 = svfs 快照:打包完把当前快照命名为包版本(`rename_snapshot`), + 之后可用 `--snapshot <版本>` 钉住加载(回滚语义); +- 签名(可选)= `packer.sign_version()`(svfs 的 `signatures` 表,HMAC-SHA256 —— 注意是 + **对称密钥**,不是非对称签名:验签方必须持有同一密钥,见《算法包设计》§4.7 的偏差说明)。 + +打包后自检(`_selfcheck`):重新以只读方式打开,核对 manifest 可解析、模型文件在包内 +且非空、代码模块齐全 —— 坏包不许出门。 +""" +from __future__ import annotations + +import logging + +import orjson +from pathlib import Path + +from .manifest import MANIFEST_NAME, Manifest, ManifestError +from .loader import sha256_file + +logger = logging.getLogger(__name__) + +#: 资源目录(随包分发的非代码文件;`assets/**` 原样进包) +ASSETS_DIR = "assets" + + +class BuildError(RuntimeError): + """打包失败(消息面向"源目录怎么改")。""" + + +class AlgoBuilder: + """源码目录 → `.mbank` 打包器。""" + + def __init__(self, src_dir: str | Path, *, key: str | None = None, compress: bool = True, + pyarmor: bool = False, model_files: dict[str, str] | None = None, + verbose: bool = True): + """ + 参数: + key: Fernet 密钥(加密包内**代码**;模型/资源不加密——svfs 不加密任意文件, + 见设计文档 §4.7 的记录) + pyarmor: 是否用 PyArmor 混淆代码(需要 PyArmor 授权;失败会抛 BuildError) + model_files: `{模型名: 磁盘路径}` —— manifest 里声明的模型不在源目录 models/ 下时 + 的显式映射(内置包复用 `workers/video/models/.bmodel` 就靠它) + """ + self.src = Path(src_dir) + if not self.src.is_dir(): + raise BuildError(f"源码目录不存在: {self.src}") + self.key = key + self.compress = bool(compress) + self.pyarmor = bool(pyarmor) + self.model_files = dict(model_files or {}) + self.verbose = bool(verbose) + + # ------------------------------------------------------------------ 读取源码 + + def _read_manifest(self) -> tuple[dict, Manifest]: + path = self.src / MANIFEST_NAME + if not path.is_file(): + raise BuildError(f"源码目录缺少 {MANIFEST_NAME}: {path}") + try: + raw = orjson.loads(path.read_bytes()) + except Exception as exc: + raise BuildError(f"{MANIFEST_NAME} 不是合法 JSON: {exc}") from exc + try: + manifest = Manifest.parse(raw, source=str(path)) + except ManifestError as exc: + raise BuildError(f"{MANIFEST_NAME} 校验失败: {exc}") from exc + return raw, manifest + + def _model_sources(self, manifest: Manifest) -> list[tuple[str, Path]]: + """解析每个模型的磁盘来源:显式映射 > 源目录内同路径 > 源目录 models/<文件名>。""" + resolved: list[tuple[str, Path]] = [] + for spec in manifest.models: + candidates: list[Path] = [] + if spec.name in self.model_files: + candidates.append(Path(self.model_files[spec.name])) + elif spec.file in self.model_files: + candidates.append(Path(self.model_files[spec.file])) + candidates.append(self.src / spec.file) + candidates.append(self.src / "models" / Path(spec.file).name) + found = next((c for c in candidates if c.is_file()), None) + if found is None: + tried = ", ".join(str(c) for c in candidates) + raise BuildError( + f"模型 {spec.name!r}({spec.file})找不到磁盘文件;找过: {tried}\n" + f"提示: 用 --model {spec.name}=<路径> 显式指定" + ) + resolved.append((spec.file, found)) + return resolved + + def _asset_sources(self) -> list[tuple[str, Path]]: + """`assets/**` 原样进包(排除 __pycache__ 等)。""" + root = self.src / ASSETS_DIR + if not root.is_dir(): + return [] + items = [] + for path in sorted(root.rglob("*")): + if not path.is_file() or "__pycache__" in path.parts: + continue + items.append((str(path.relative_to(self.src)).replace("\\", "/"), path)) + return items + + # ------------------------------------------------------------------ 打包 + + def build(self, out_path: str | Path, *, sign_key: str | None = None, + sign_key_id: str | None = None, replace: bool = True, + code_include: list[str] | None = None) -> dict: + """执行打包,返回产物摘要(manifest/大小/sha256)。""" + try: + from module_bank import PythonToSQLite + except ImportError as exc: # pragma: no cover - 依赖缺失 + raise BuildError(f"缺少 module_bank 依赖: {exc}") from exc + + raw_manifest, manifest = self._read_manifest() + out = Path(out_path) + if out.exists(): + if not replace: + raise BuildError(f"产物已存在(未指定 replace): {out}") + out.unlink() + out.parent.mkdir(parents=True, exist_ok=True) + + code_dir = self.src / manifest.code_package + if not code_dir.is_dir(): + raise BuildError( + f"源码目录缺少包代码目录 {manifest.code_package}/(manifest.code_package 指向它): {code_dir}" + ) + models = self._model_sources(manifest) + assets = self._asset_sources() + + # ① 建包(svfs)+ ② 打代码(module_bank 的既有能力) + try: + packer = PythonToSQLite(str(out), key=self.key, pyarmor_obfuscate=self.pyarmor, + compress=self.compress) + except Exception as exc: + raise BuildError(f"创建算法包失败: {exc}") from exc + + try: + self._log(f"打包代码: {manifest.code_package}/ → {out.name}") + packer.pack_directory( + str(code_dir), manifest.code_package, + include_patterns=list(code_include or ["*.py"]), + exclude_patterns=["__pycache__", "*.pyc"], + ) + # ③ 清单 / 模型 / 资源(任意文件直接进 svfs) + packer.vfs.add_bytes(MANIFEST_NAME, orjson.dumps(raw_manifest, option=orjson.OPT_INDENT_2), + replace=True) + for rel, source in models: + self._log(f"打包模型: {rel} ← {source}({source.stat().st_size / 1048576:.1f} MB)") + packer.vfs.add_file(str(source), rel, replace=True) + for rel, source in assets: + self._log(f"打包资源: {rel}") + packer.vfs.add_file(str(source), rel, replace=True) + + if self.key or self.pyarmor: + # 加密/混淆后**删掉明文源码**:否则包内同时留着 .py,谁都读得到(也绕过了"没密钥装不上" + # 的保护)。参考 chongming-cli 的 build.py::_stage_pack_modules 同样的收尾动作。 + removed = packer.delete_source_code(None) + self._log(f"已删除包内明文源码(加密/混淆包): {removed}") + packer.verify_package_structure() + self._tag_version(packer, manifest.version) + if sign_key: + packer.sign_version(sign_key, key_id=sign_key_id, snapshot=manifest.version) + self._log(f"已签名(key_id={sign_key_id or '-'})") + except BuildError: + raise + except Exception as exc: + raise BuildError(f"打包失败: {type(exc).__name__}: {exc}") from exc + finally: + try: + packer.close() + except Exception: # pragma: no cover - 关闭失败不影响产物 + pass + + summary = self._selfcheck(out) + self._log(f"完成: {out}({summary['size_bytes'] / 1048576:.1f} MB,sha256 {summary['sha256'][:12]}…)") + return summary + + @staticmethod + def _tag_version(packer, version: str) -> None: + """把当前快照命名为包版本(svfs 快照 = 版本;回滚/钉版本靠它)。""" + try: + vfs = packer.vfs + snapshot_id = getattr(vfs, "snapshot_id", None) + snapshots = vfs.list_snapshots() + if snapshot_id is None and snapshots: + snapshot_id = snapshots[-1]["id"] + if snapshot_id is not None: + vfs.rename_snapshot(snapshot_id, str(version)) + except Exception as exc: # pragma: no cover - 命名失败不致命 + logger.warning("[algpkg] 快照命名失败(不影响装载): %s", exc) + + def _selfcheck(self, out: Path) -> dict: + """重新只读打开产物,核对清单/模型/模块(坏包不许出门)。""" + from .loader import AlgoPackage, AlgoPackageError + + try: + pkg = AlgoPackage(out, key=self.key) + pkg.open() + except AlgoPackageError as exc: + raise BuildError(f"产物自检失败: {exc}") from exc + try: + manifest = pkg.require_manifest() + modules = sorted(pkg.list_files(), key=lambda item: item["path"]) + return { + "path": str(out), + "algo_id": manifest.id, + "name": manifest.name, + "version": manifest.version, + "api_version": manifest.api_version, + "code_package": manifest.code_package, + "models": [m.to_dict() for m in manifest.models], + "files": modules, + "size_bytes": out.stat().st_size, + "sha256": sha256_file(out), + "encrypted": bool(self.key), + } + finally: + pkg.close() + + def _log(self, message: str) -> None: + if self.verbose: + print(f"[algpkg build] {message}") + + +def inspect_package(path: str | Path, *, key: str | None = None) -> dict: + """只读检查一个 `.mbank`(`algpkg inspect`):清单 + 文件清单 + 快照/签名。""" + from .loader import AlgoPackage + + pkg = AlgoPackage(path, key=key) + pkg.open() + try: + manifest = pkg.require_manifest() + snapshots: list[dict] = [] + signatures: list[dict] = [] + try: + from sqlite_vfs.core import SQLiteVFS + + vfs = SQLiteVFS(str(path), readonly=True, check_same_thread=False) + snapshots = [dict(row) for row in vfs.list_snapshots()] + signatures = [dict(row) for row in vfs.list_signatures()] + vfs.close() + except Exception as exc: # pragma: no cover - 元信息读取失败不致命 + logger.debug("[algpkg] 快照/签名读取失败: %s", exc) + return { + **manifest.info(), + "filename": Path(path).name, + "size_bytes": Path(path).stat().st_size, + "sha256": sha256_file(path), + "files": pkg.list_files(), + "snapshots": snapshots, + "signatures": signatures, + } + finally: + pkg.close() + + +def verify_package(path: str | Path, *, key: str | None = None, + signature_key: str | None = None, snapshot=None) -> dict: + """校验一个 `.mbank`(`algpkg verify`):结构 + 模型存在 + 可选验签。""" + from .loader import AlgoPackage + + pkg = AlgoPackage(path, key=key, snapshot=snapshot) + pkg.open() + try: + manifest = pkg.require_manifest() + report: dict = { + "algo_id": manifest.id, "version": manifest.version, + "api_version": manifest.api_version, "ok": True, + "models": [], "signature": None, + } + for spec in manifest.models: + data = pkg.read_model(spec.name)[0] + report["models"].append({"name": spec.name, "file": spec.file, + "size_bytes": len(data)}) + if signature_key: + from sqlite_vfs.core import SQLiteVFS + + vfs = SQLiteVFS(str(path), readonly=True, check_same_thread=False) + try: + report["signature"] = dict(vfs.verify_signature(signature_key, snapshot_id=snapshot)) + report["ok"] = bool(report["signature"].get("ok")) + finally: + vfs.close() + return report + finally: + pkg.close() + + +__all__ = ["AlgoBuilder", "BuildError", "inspect_package", "verify_package"] diff --git a/src/algpkg_sdk/cli.py b/src/algpkg_sdk/cli.py new file mode 100644 index 0000000..88e10c1 --- /dev/null +++ b/src/algpkg_sdk/cli.py @@ -0,0 +1,448 @@ +"""`algpkg` 命令行:打包 / 检查 / 校验 / 脚手架 / **本地测试台**。 + +安装本项目后即可直接用(不需要 worker 仓库):: + + algpkg init --id my.algo --name 我的算法 # 脚手架 + algpkg build --out my.algo-1.0.0.mbank [--model 名=路径] [--encrypt KEY] [--sign KEY] + algpkg inspect [--json] # 看清单/模型/快照/签名 + algpkg verify [--signature-key KEY] # 结构+模型+(可选)验签 + algpkg list + +本地测试(不需要 worker;`run`/`compare` 需要设备):: + + algpkg check [--deep] # 静态:清单/结构/试加载 + algpkg run --source clip.mp4 [--params '{"conf":0.3}'] [--save-annotated out/] + algpkg compare --source clip.mp4 # 两包对拍 + +等价入口:`python -m algpkg_sdk.cli ...`。 +""" +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +from .builder import AlgoBuilder, BuildError, inspect_package, verify_package +from .manifest import Manifest, code_package_for + + +# ====================================================================== 子命令 + +def cmd_build(args: argparse.Namespace) -> int: + models: dict[str, str] = {} + for item in args.model or []: + if "=" not in item: + print(f"参数格式错误(应为 名=路径): {item}", file=sys.stderr) + return 2 + name, path = item.split("=", 1) + models[name.strip()] = path.strip() + + builder = AlgoBuilder(args.src, key=args.encrypt, compress=not args.no_compress, + pyarmor=args.pyarmor, model_files=models, verbose=not args.quiet) + try: + summary = builder.build(args.out, sign_key=args.sign, sign_key_id=args.key_id, + replace=not args.no_replace) + except BuildError as exc: + print(f"✗ 打包失败: {exc}", file=sys.stderr) + return 1 + if args.json: + print(json.dumps(summary, ensure_ascii=False, indent=2)) + else: + print(f"✓ {summary['algo_id']}@{summary['version']} → {summary['path']}" + f"({summary['size_bytes'] / 1048576:.1f} MB)") + print(f" 模型: {', '.join(m['name'] for m in summary['models'])}" + f" | 代码包: {summary['code_package']}" + f" | {'已加密' if summary['encrypted'] else '未加密'}") + return 0 + + +def cmd_inspect(args: argparse.Namespace) -> int: + try: + info = inspect_package(args.mbank, key=args.encrypt) + except Exception as exc: + print(f"✗ 检查失败: {exc}", file=sys.stderr) + return 1 + if args.json: + print(json.dumps(info, ensure_ascii=False, indent=2, default=str)) + return 0 + print(f"算法包: {info['filename']}({info['size_bytes'] / 1048576:.1f} MB)") + print(f" id/name : {info['algo_id']} / {info['name']}") + print(f" 版本/api : {info['version']} / api_version={info['api_version']}") + print(f" 类型/入口 : {info['kind']} / {info['entry']}(代码包 {info['code_package']})") + print(f" sha256 : {info['sha256']}") + print(" 模型:") + for model in info["models"]: + mark = "(默认)" if model.get("default") else "" + print(f" - {model['name']} [{model.get('precision') or '-'}] {model['file']}{mark}") + print(" 文件:") + for item in info["files"]: + print(f" - {item['path']}({item['size']} B)") + print(f" 快照: {', '.join(str(s.get('name')) for s in info['snapshots']) or '(无)'}") + print(f" 签名: {len(info['signatures'])} 条" + + (f"(key_id={info['signatures'][-1].get('key_id')})" if info["signatures"] else "")) + return 0 + + +def cmd_verify(args: argparse.Namespace) -> int: + try: + report = verify_package(args.mbank, key=args.encrypt, + signature_key=args.signature_key, snapshot=args.snapshot) + except Exception as exc: + print(f"✗ 校验失败: {exc}", file=sys.stderr) + return 1 + print(json.dumps(report, ensure_ascii=False, indent=2, default=str)) + if report.get("models") and report.get("ok"): + print("✓ 结构与模型校验通过" + ("(签名有效)" if args.signature_key else "")) + return 0 + print("✗ 校验未通过", file=sys.stderr) + return 1 + + +def cmd_list(args: argparse.Namespace) -> int: + root = Path(args.dir) + files = sorted(list(root.glob("*.mbank")) + list((root / "builtin").glob("*.mbank"))) + if not files: + print(f"({root} 下没有 .mbank)") + return 0 + print(f"{'算法':<28} {'版本':<10} {'大小':>9} 文件") + for path in files: + try: + info = inspect_package(path, key=args.encrypt) + print(f"{info['algo_id']:<28} {info['version']:<10} {info['size_bytes'] / 1048576:>7.1f}MB {path.name}") + except Exception as exc: + print(f"{'?':<28} {'-':<10} {path.stat().st_size / 1048576:>7.1f}MB {path.name} ⚠ {exc}") + return 0 + + +def cmd_init(args: argparse.Namespace) -> int: + root = Path(args.dir) + manifest_path = root / "manifest.json" + if manifest_path.exists() and not args.force: + print(f"✗ {manifest_path} 已存在(--force 覆盖)", file=sys.stderr) + return 1 + code_package = args.code_package or code_package_for(args.id) + root.mkdir(parents=True, exist_ok=True) + (root / code_package).mkdir(parents=True, exist_ok=True) + (root / "assets").mkdir(parents=True, exist_ok=True) + + manifest_path.write_text(json.dumps(_scaffold_manifest(args, code_package), + ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + (root / code_package / "__init__.py").write_text(_scaffold_code(args), encoding="utf-8") + labels = root / "assets" / "labels.txt" + if not labels.exists(): + labels.write_text("# 每行一个类别名(第 0 行 = 类别 0);按需替换\nobject\n", encoding="utf-8") + readme = root / "README.md" + if not readme.exists(): + readme.write_text(_scaffold_readme(args, code_package), encoding="utf-8") + + print(f"✓ 已生成脚手架: {root}") + print(f" 1) 把模型放到 {root}/models/(或打包时用 --model 名=<路径> 指定)") + print(f" 2) 实现 {code_package}/__init__.py 的 load/infer") + print(f" 3) 打包: workers/video/algpkg build {root} --out {args.id}-{args.version}.mbank") + return 0 + + +# ====================================================================== 脚手架内容 + +def _scaffold_manifest(args: argparse.Namespace, code_package: str) -> dict: + return { + "manifest_format": 1, + "id": args.id, + "name": args.name, + "version": args.version, + "api_version": 1, + "kind": args.kind, + "entry": "Algorithm", + "code_package": code_package, + "vendor": args.vendor or "", + "description": f"{args.name}(由 algpkg init 生成)", + "models": [{ + "name": "default", + "file": f"models/{args.id}.bmodel", + "precision": "", + "resolution": [640, 640], + "batch": 1, + "default": True, + }], + "input": {"width": 640, "height": 640, "color": "rgb", "layout": "planar", + "mean": [0, 0, 0], "std": [255, 255, 255]}, + "output": {"type": "boxes", "format": "N6", "classes_file": "assets/labels.txt"}, + "params_schema": [ + {"key": "conf", "type": "float", "default": 0.25, "min": 0.01, "max": 0.99, + "step": 0.01, "label": "置信度阈值", "desc": "低于该分数的目标被丢弃"}, + {"key": "interval", "type": "float", "default": 0.2, "min": 0.02, "max": 60, + "step": 0.02, "label": "抽帧间隔(秒)", "desc": "两次推理之间的最小间隔"}, + ], + "defaults": {"conf": 0.25, "interval": 0.2}, + "alarm": {"type_template": "{class}", "cooldown_key": "algo", + "need_snapshot": True, "need_record": True}, + "requires": {"license_features": [], "devices": [], "frameworks": ["sail"]}, + "ui": {"order": 100, "tags": [], "icon": "mdi-puzzle"}, + "legacy": {}, + } + + +def _scaffold_code(args: argparse.Namespace) -> str: + return f'''"""算法包实现:{args.name}({args.id})。 + +只依赖 `video_algpkg_api`(worker 提供的稳定接口)与设备框架(sophon.sail / numpy)。 +本文件由 `algpkg init` 生成,改完即可打包: + workers/video/algpkg build <本目录> --out {args.id}-{args.version}.mbank +""" +from __future__ import annotations + +import numpy as np +import sophon.sail as sail + +from video_algpkg_api import Algorithm as AlgorithmBase, AlgoResult + + +class Algorithm(AlgorithmBase): + """{args.name}。""" + + kind = "{args.kind}" + + #: 引擎句柄:load() 赋值、close() 释放。类级声明为非 Optional,infer() 里可直接用 + #: (不必判空);close() 置 None 与声明冲突,那处显式 `type: ignore`——运行时语义就是"丢引用"。 + _net: sail.Engine + + def load(self) -> None: + # 模型字节来自算法包自身(.mbank 内的 models/**),不落盘: + data = self.ctx.model_bytes() + size = len(data) + self.ctx.logger.info("加载模型 %s(%d 字节, conf=%s)", + self.ctx.model_name, size, self.ctx.param("conf")) + self._net = sail.Engine(data, size, self.ctx.dev_id, sail.IOMode.SYSO) + self._graph = self._net.get_graph_names()[0] + # TODO: 按模型实际输入做前处理(可参考 worker 的 YOLO26._preprocess) + self._handle = sail.Handle(self.ctx.dev_id) + self._bmcv = sail.Bmcv(self._handle) + + def infer(self, frame) -> AlgoResult: + """单帧推理。返回 AlgoResult(boxes 为 (N,6):x1,y1,x2,y2,score,cls)。 + + TODO: 前处理 → self._net.process(...) → 后处理(NMS/反变换), + 再 return AlgoResult(boxes=boxes, labels=[...])。 + 下面这行只做"链路可跑通"的占位(空结果),替换为真实实现。 + """ + _ = frame + boxes = np.zeros((0, 6), dtype=np.float32) + return AlgoResult(boxes=boxes) + + def close(self) -> None: + self._net = None # type: ignore[assignment] +''' + + +def _scaffold_readme(args: argparse.Namespace, code_package: str) -> str: + return f"""# {args.name}(算法包 {args.id}) + +由 `algpkg init` 生成。目录结构: + +``` +manifest.json 清单(算法元数据 / 模型 / 参数表单 / 告警策略) +{code_package}/ 包内代码(入口类 Algorithm,只依赖 video_algpkg_api) +models/ 随包模型(.bmodel;打包时进包,不单独分发) +assets/ 可选资源(labels.txt 类别名等) +``` + +## 开发步骤 + +1. 把模型放到 `models/`(或打包时 `--model 名=<磁盘路径>` 指定); +2. 实现 `{code_package}/__init__.py` 的 `load()`(加载模型字节)与 `infer(frame)`(单帧结果); +3. 打包:`workers/video/algpkg build . --out {args.id}-{args.version}.mbank`; +4. 检查:`workers/video/algpkg inspect {args.id}-{args.version}.mbank`; +5. 安装到设备:`POST /api/v1/video/algorithm/install`(算法管理页上传亦可), + 随后在任务表单里选择本算法(参数表单由 `manifest.params_schema` 生成)。 + +## 接口约定(video_algpkg_api) + +- `ctx.model_bytes(name=None)` / `ctx.read_asset(path)`:读包内文件字节(模型不落盘); + 多模型包(检测 + 二次判定)按名字取第二份:`ctx.model_bytes("fp_filter")`; +- `ctx.param("conf", 默认值)`:任务参数(已按 manifest 校验/补默认值); +- `infer(frame)` 返回 `AlgoResult(boxes=(N,6), counts=...)`;`draw()` 可选自定义叠加; +- `load()` 抛异常 → 任务转 `failed` 且错误可读(含包 id/版本); +- 线程模型:同一实例不会被并发调用。 +""" + + +# ====================================================================== 本地测试台 + +def cmd_check(args: argparse.Namespace) -> int: + from . import testkit + + try: + report = testkit.check(args.mbank, key=args.encrypt, deep=args.deep, + extra_path=args.extra_path) + except Exception as exc: + print(f"✗ 检查失败: {exc}", file=sys.stderr) + return 1 + if args.json: + print(json.dumps(report, ensure_ascii=False, indent=2, default=str)) + return 0 if report["ok"] else 1 + manifest = report.get("manifest") or {} + print(f"算法包: {Path(args.mbank).name}({manifest.get('algo_id')}@{manifest.get('version')})") + for item in report["checks"]: + print(f" {'✓' if item['ok'] else '✗'} {item['name']}" + + (f" — {item['detail']}" if item["detail"] else "")) + if not report.get("device"): + print(" (本机无 sophon.sail:跳过实跑;`algpkg run` 需在设备上执行)") + print("✓ 检查通过" if report["ok"] else "✗ 检查未通过") + return 0 if report["ok"] else 1 + + +def cmd_run(args: argparse.Namespace) -> int: + from . import testkit + + params = {} + if args.params: + try: + params = json.loads(args.params) + except json.JSONDecodeError as exc: + print(f"✗ --params 不是合法 JSON: {exc}", file=sys.stderr) + return 2 + try: + report = testkit.run(args.mbank, source=args.source, params=params, model=args.model, + frames=args.frames, warmup=args.warmup, dev_id=args.dev_id, + key=args.encrypt, save_annotated=args.save_annotated, + extra_path=args.extra_path) + except Exception as exc: + print(f"✗ 运行失败: {exc}", file=sys.stderr) + return 1 + summary = report.summary() + if args.json: + print(json.dumps(summary, ensure_ascii=False, indent=2, default=str)) + else: + times = summary["infer_ms"] + print(f"算法: {summary['algo_id']}@{summary['version']}(模型 {summary['model']})") + print(f"源: {summary['source']} · 帧数 {summary['frames']} · 单帧耗时 " + f"avg {times['avg']}ms / min {times['min']} / max {times['max']}") + print(f"逐帧检出: {summary['detections']}") + if summary["annotated"]: + print(f"已存叠加图 {len(summary['annotated'])} 张 → {Path(summary['annotated'][0]).parent}") + return 0 + + +def cmd_compare(args: argparse.Namespace) -> int: + from . import testkit + + try: + a = testkit.run(args.a, source=args.source, frames=args.frames, dev_id=args.dev_id, + extra_path=args.extra_path) + b = testkit.run(args.b, source=args.source, frames=args.frames, dev_id=args.dev_id, + extra_path=args.extra_path) + result = testkit.compare(a, b, tolerance=args.tolerance) + except Exception as exc: + print(f"✗ 对比失败: {exc}", file=sys.stderr) + return 1 + if args.json: + print(json.dumps(result, ensure_ascii=False, indent=2, default=str)) + else: + print(f"A: {result['a']} B: {result['b']}") + for frame in result["frames"]: + mark = "✓" if frame["identical"] else "✗" + print(f" {mark} 帧{frame['frame']}: A 检出 {frame['a']} / B 检出 {frame['b']}") + print("逐位一致 ✓" if result["identical"] else "存在差异 ✗(--tolerance 可放宽浮点比较)") + return 0 if result["identical"] else 1 + + +# ====================================================================== 入口 + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="algpkg", description="算法包工具链(M8.4)") + sub = parser.add_subparsers(dest="command", required=True) + + build = sub.add_parser("build", help="打包源码目录 → .mbank") + build.add_argument("src", help="源码目录(含 manifest.json 与代码目录)") + build.add_argument("--out", required=True, help="产物路径(*.mbank)") + build.add_argument("--model", action="append", metavar="名=路径", + help="模型磁盘来源(可多次;缺省用源码目录内的 models/<文件>)") + build.add_argument("--encrypt", metavar="KEY", help="Fernet 密钥:加密包内代码") + build.add_argument("--pyarmor", action="store_true", help="用 PyArmor 混淆代码") + build.add_argument("--sign", metavar="KEY", help="签名密钥(HMAC-SHA256,对称)") + build.add_argument("--key-id", metavar="ID", help="签名 key_id(如 ci-2026)") + build.add_argument("--no-compress", action="store_true", help="不做内容压缩") + build.add_argument("--no-replace", action="store_true", help="产物已存在时报错") + build.add_argument("--json", action="store_true", help="输出 JSON 摘要") + build.add_argument("--quiet", action="store_true") + build.set_defaults(func=cmd_build) + + inspect = sub.add_parser("inspect", help="查看包内容(清单/模型/快照/签名)") + inspect.add_argument("mbank") + inspect.add_argument("--encrypt", metavar="KEY") + inspect.add_argument("--json", action="store_true") + inspect.set_defaults(func=cmd_inspect) + + verify = sub.add_parser("verify", help="校验包(结构/模型存在性/可选验签)") + verify.add_argument("mbank") + verify.add_argument("--encrypt", metavar="KEY") + verify.add_argument("--signature-key", metavar="KEY", help="签名密钥(验签;HMAC 对称)") + verify.add_argument("--snapshot", help="指定版本(快照 id 或名字)") + verify.add_argument("--json", action="store_true", help="输出 JSON(默认已是结构化输出)") + verify.set_defaults(func=cmd_verify) + + listing = sub.add_parser("list", help="列出目录内的算法包") + listing.add_argument("dir") + listing.add_argument("--encrypt", metavar="KEY") + listing.set_defaults(func=cmd_list) + + check = sub.add_parser("check", help="静态检查一个包(清单/结构/试加载;不需 worker)") + check.add_argument("mbank") + check.add_argument("--deep", action="store_true", help="再实例化入口类并加载默认模型(需设备)") + check.add_argument("--encrypt", metavar="KEY") + check.add_argument("--json", action="store_true") + check.add_argument("--extra-path", action="append", metavar="DIR", + help="额外包搜索路径(特权包才需要:依赖 worker 内部模块;可多次)") + check.set_defaults(func=cmd_check) + + run_cmd = sub.add_parser("run", help="在设备上跑一个包(图片/视频)") + run_cmd.add_argument("mbank") + run_cmd.add_argument("--source", required=True, help="图片或视频路径") + run_cmd.add_argument("--params", metavar="JSON", help='任务参数,如 \'{"conf":0.3}\'') + run_cmd.add_argument("--model", help="指定包内模型名(缺省用 default)") + run_cmd.add_argument("--frames", type=int, default=5, help="取样帧数(默认 5)") + run_cmd.add_argument("--warmup", type=int, default=2, help="预热帧数(不计入耗时)") + run_cmd.add_argument("--dev-id", type=int, default=0) + run_cmd.add_argument("--save-annotated", metavar="DIR", help="把每帧叠加结果存图到目录") + run_cmd.add_argument("--encrypt", metavar="KEY") + run_cmd.add_argument("--json", action="store_true") + run_cmd.add_argument("--extra-path", action="append", metavar="DIR", + help="额外包搜索路径(特权包才需要;可多次)") + run_cmd.set_defaults(func=cmd_run) + + cmp_cmd = sub.add_parser("compare", help="两个包/版本对拍(逐帧检出 + 逐位一致)") + cmp_cmd.add_argument("a") + cmp_cmd.add_argument("b") + cmp_cmd.add_argument("--source", required=True) + cmp_cmd.add_argument("--frames", type=int, default=5) + cmp_cmd.add_argument("--dev-id", type=int, default=0) + cmp_cmd.add_argument("--tolerance", type=float, default=0.0, help="浮点容差(默认逐位)") + cmp_cmd.add_argument("--json", action="store_true") + cmp_cmd.add_argument("--extra-path", action="append", metavar="DIR", + help="额外包搜索路径(特权包才需要;可多次)") + cmp_cmd.set_defaults(func=cmd_compare) + + init = sub.add_parser("init", help="生成算法包脚手架") + init.add_argument("dir", help="生成目录") + init.add_argument("--id", required=True, help="算法 id(小写字母/数字/._-)") + init.add_argument("--name", required=True, help="显示名(如:抽烟检测)") + init.add_argument("--version", default="1.0.0") + init.add_argument("--kind", default="detector", choices=["detector", "classifier", "counter", "tracker"]) + init.add_argument("--vendor", default="") + init.add_argument("--code-package", default="", help="包内代码模块名(缺省由 id 推导)") + init.add_argument("--force", action="store_true") + init.set_defaults(func=cmd_init) + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + try: + return int(args.func(args) or 0) + except KeyboardInterrupt: # pragma: no cover + return 130 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/algpkg_sdk/loader.py b/src/algpkg_sdk/loader.py new file mode 100644 index 0000000..b42be06 --- /dev/null +++ b/src/algpkg_sdk/loader.py @@ -0,0 +1,400 @@ +"""算法包装载器(M8.1):打开 `.mbank`(svfs 包)→ 读 manifest → 挂载包内代码 → 取模型字节。 + +`.mbank` 的双重身份(《算法包与多算法任务设计》§3 取证): +- 对 `sqlite_vfs` 来说它就是一个**只读虚拟文件系统**(`manifest.json` / `models/*.bmodel` + 都是普通文件,用 `SQLiteVFS.read_file()` 读字节,**不落盘**); +- 对 `module_bank` 来说它是**模块银行**(`modules//**`,`install_importer()` + 挂进 ``sys.meta_path``,包内代码即可 `import`),装载前可验签。 + +两条容易踩的实现约束(本模块已处理): +1. **svfs 句柄自带线程约束**:`SQLiteVFS` 的连接默认 `check_same_thread=True`, + 而 `module_bank` 的查找器/加载器正是用它读字节码 —— 于是"启动时挂载、任务线程里首次 + import"会抛 `ProgrammingError`。本模块的对策:**挂载时把所有模块一次性 import 完** + (`_eager_import`,同时也是"装载前试加载"验收项),此后模块都在 ``sys.modules`` 里, + 任务线程不再触发查找器读盘; +2. **挂载/卸载必须在同一线程**(查找器的连接在创建线程建立):由注册表在**专用单线程 + 执行器**上串行执行 mount/unmount(见 registry.py 的 `_lifecycle`)。 + +包内自有的读取句柄(读 manifest / 模型字节)用 `check_same_thread=False` + 独占锁, +可以从任意任务线程调用。 +""" +from __future__ import annotations + +import hashlib +import importlib +import logging +import sys +import threading +from pathlib import Path + +from .api import AlgoContext, Algorithm, AlgoResult, install_sdk_alias +from .manifest import MANIFEST_NAME, Manifest, ManifestError + +logger = logging.getLogger(__name__) + +#: 包内预留的文件名(不与模型/资源混淆) +_RESERVED = {MANIFEST_NAME, "algorithm", "code"} + + +def purge_namespace(code_package: str) -> int: + """清理 `sys.modules` 里某个包代码命名空间的全部模块(返回清理数)。""" + prefix = str(code_package or "").strip() + if not prefix: + return 0 + removed = 0 + for name in [n for n in list(sys.modules) if n == prefix or n.startswith(prefix + ".")]: + sys.modules.pop(name, None) + removed += 1 + return removed + + +def namespace_loaded(code_package: str) -> bool: + """某个包代码命名空间当前是否有模块在 `sys.modules`(判断能否安全清理)。""" + prefix = str(code_package or "").strip() + return bool(prefix) and any(n == prefix or n.startswith(prefix + ".") + for n in sys.modules) + + +class AlgoPackageError(RuntimeError): + """算法包不可用(格式/清单/代码/签名任一环节失败)——消息面向"怎么修"。""" + + +def sha256_file(path: str | Path, chunk: int = 1 << 20) -> str: + digest = hashlib.sha256() + with open(path, "rb") as handle: + while True: + block = handle.read(chunk) + if not block: + break + digest.update(block) + return digest.hexdigest() + + +class AlgoPackage: + """一个 `.mbank` 算法包的装载句柄(读元数据/模型/资源 + 挂载代码 + 构造实例)。 + + 典型用法(注册表内):: + + pkg = AlgoPackage("/opt/algpkgs/smoke-detect-1.0.0.mbank") + pkg.open() # 读 manifest(只读 VFS 句柄,本对象持有) + pkg.mount() # 挂代码(必须在注册表的单线程执行器上调用) + algo = pkg.new_instance("smoke_fp16", params, dev_id=0) # 任意线程 + pkg.unmount() # 卸载(同一线程) + pkg.close() + """ + + def __init__(self, path: str | Path, *, key: str | None = None, snapshot=None, + signature_key: str | None = None, signature_key_id: str | None = None, + allow_unsigned: bool = True, source: str = "disk", + sha256: str = "", enabled: bool = True): + self.path = Path(path) + self.filename = self.path.name + self.source = source # disk | upload | builtin + self.sha256 = sha256 # 安装时算好;scan 时可延迟计算 + self._key = key + self._snapshot = snapshot + self._signature_key = signature_key + self._signature_key_id = signature_key_id + self._allow_unsigned = bool(allow_unsigned) + + self.manifest: Manifest | None = None + self.signature: dict = {} # 验签报告(装载时填充) + self.last_error: str | None = None + self.mounted = False + + self._vfs = None # 自有只读句柄(读文件字节) + self._finder = None # module_bank 的查找器(挂载后非 None) + self._lock = threading.RLock() # 保护 _vfs 读写 + self._import_lock = threading.RLock() # 保护 importlib 调用(装载/卸载交错) + + # ================================================================== 元数据 + + @property + def algo_id(self) -> str: + return self.manifest.id if self.manifest else "" + + @property + def version(self) -> str: + return self.manifest.version if self.manifest else "" + + @property + def size_bytes(self) -> int: + try: + return self.path.stat().st_size + except OSError: + return 0 + + def open(self) -> "AlgoPackage": + """打开只读 VFS 并解析 manifest(可重复调用;失败抛 AlgoPackageError)。""" + if self.manifest is not None: + return self + if not self.path.is_file(): + raise AlgoPackageError(f"算法包不存在: {self.path}") + try: + from sqlite_vfs.core import SQLiteVFS + except ImportError as exc: # pragma: no cover - 依赖缺失 + raise AlgoPackageError(f"缺少 sqlite_vfs 依赖(module-bank): {exc}") from exc + + try: + # check_same_thread=False + 自有锁:任务线程要读模型字节(见模块文档) + self._vfs = SQLiteVFS(str(self.path), readonly=True, check_same_thread=False) + except Exception as exc: + raise AlgoPackageError(f"不是有效的 svfs 包({self.path.name}): {exc}") from exc + + try: + raw = self._read_raw(MANIFEST_NAME) + except FileNotFoundError as exc: + self._close_vfs() + raise AlgoPackageError( + f"包内缺少 {MANIFEST_NAME}({self.path.name})——不是算法包,或打包时漏了清单" + ) from exc + try: + import orjson + + self.manifest = Manifest.parse(orjson.loads(raw), source=self.filename) + except ManifestError as exc: + self._close_vfs() + raise AlgoPackageError(f"manifest 校验失败({self.path.name}): {exc}") from exc + except Exception as exc: + self._close_vfs() + raise AlgoPackageError(f"manifest 解析失败({self.path.name}): {exc}") from exc + + # 模型文件存在性/非空检查(装载前的静态校验) + for spec in self.manifest.models: + try: + size = len(self._read_raw(spec.file)) + except FileNotFoundError as exc: + self._close_vfs() + raise AlgoPackageError( + f"manifest 声明的模型文件不在包内: {spec.file}({self.path.name})" + ) from exc + if size <= 0: + self._close_vfs() + raise AlgoPackageError(f"包内模型文件为空: {spec.file}") + return self + + # ================================================================== 文件读取 + + def _read_raw(self, rel_path: str) -> bytes: + """按包内相对路径读字节(manifest/模型/资源统一入口)。""" + if self._vfs is None: + raise AlgoPackageError("算法包尚未打开(先 open())") + clean = str(rel_path or "").lstrip("/").replace("\\", "/") + if ".." in clean.split("/"): + raise AlgoPackageError(f"非法包内路径: {rel_path!r}") + with self._lock: + return self._vfs.read_file(clean) + + def read_file(self, rel_path: str) -> bytes: + return self._read_raw(rel_path) + + def read_model(self, model_name: str | None = None) -> tuple[bytes, dict]: + """读选中模型的字节(返回 (bytes, 模型元信息))。""" + spec = self.require_manifest().model(model_name) + data = self._read_raw(spec.file) + if not data: + raise AlgoPackageError(f"模型文件为空: {spec.file}") + return data, spec.to_dict() + + def read_asset(self, rel_path: str) -> bytes: + return self._read_raw(rel_path) + + def list_files(self) -> list[dict]: + """包内文件清单(管理页展示用;不含 modules/ 下的字节码)。""" + if self._vfs is None: + return [] + with self._lock: + entries = [e for e in self._vfs.all_entries() if not e.get("is_directory")] + items = [] + for entry in entries: + path = entry.get("path", "") + if str(path).startswith("modules/"): + continue + items.append({"path": path, "size": int(entry.get("file_size") or 0)}) + return sorted(items, key=lambda i: i["path"]) + + def require_manifest(self) -> Manifest: + if self.manifest is None: + self.open() + assert self.manifest is not None + return self.manifest + + # ================================================================== 挂载 / 卸载 + # + # 注意:mount()/unmount() 必须在**同一个线程**调用(module_bank 查找器的 sqlite 连接 + # 在创建线程建立)。注册表用专用单线程执行器保证这一点,见 registry.py。 + + def mount(self) -> "AlgoPackage": + """挂载包内代码到 sys.meta_path,并**试加载**全部模块(本线程内完成)。""" + manifest = self.require_manifest() + if self.mounted: + return self + try: + from module_bank import PythonToSQLite + from module_bank.layout import SignatureVerificationError + except ImportError as exc: # pragma: no cover + raise AlgoPackageError(f"缺少 module_bank 依赖: {exc}") from exc + + with self._import_lock: + try: + packer = PythonToSQLite(str(self.path), key=self._key, readonly=True, + snapshot=self._snapshot) + except Exception as exc: + raise AlgoPackageError(f"打开算法包失败({self.path.name}): {exc}") from exc + + try: + finder = packer.install_importer( + snapshot=self._snapshot, + signature_key=self._signature_key, + signature_key_id=self._signature_key_id, + allow_unsigned=self._allow_unsigned, + ) + except SignatureVerificationError as exc: + raise AlgoPackageError(f"签名校验未通过,拒绝装载: {exc}") from exc + except Exception as exc: + raise AlgoPackageError(f"装载导入器失败({self.path.name}): {exc}") from exc + + self._finder = finder + self.mounted = True + try: + self.signature = self._verify_signature_report() + self._eager_import(manifest) + except BaseException: + self.unmount() + raise + logger.info("[algpkg] 已挂载 %s@%s(代码 %s,模块 %s 个)", + manifest.id, manifest.version, manifest.code_package, + len(getattr(self._finder, "module_cache", {}) or {})) + return self + + def _verify_signature_report(self) -> dict: + """记录验签报告(管理页展示"是否签过/签名者";未配置签名密钥时为空)。""" + key = self._signature_key # 签名密钥独立于(可选的)加密密钥 + if not key or self._finder is None: + return {} + try: + return dict(self._finder.vfs.verify_signature(key, key_id=self._signature_key_id)) + except Exception as exc: # pragma: no cover - 报告失败不影响装载 + logger.debug("[algpkg] 验签报告读取失败: %s", exc) + return {} + + def _eager_import(self, manifest: Manifest) -> None: + """把所有模块一次性 import 完(线程约束 + 试加载验收,见模块文档)。""" + module_cache = dict(getattr(self._finder, "module_cache", {}) or {}) + if not module_cache: + raise AlgoPackageError( + f"算法包 {manifest.id} 内没有可导入的模块(modules/ 为空)" + f"——打包时是否漏了代码目录?" + ) + install_sdk_alias() + # 先父后子:包(__init__)优先,其余按名字长度排序(父模块先于子模块) + for name in sorted(module_cache, key=lambda n: (n.count("."), n)): + try: + importlib.import_module(name) + except Exception as exc: + raise AlgoPackageError( + f"算法包代码导入失败: {name}: {type(exc).__name__}: {exc}" + ) from exc + + module = importlib.import_module(manifest.entry_module) + entry = getattr(module, manifest.entry_class, None) + if entry is None: + raise AlgoPackageError( + f"入口类不存在: {manifest.entry_module}.{manifest.entry_class}" + f"(manifest.entry={manifest.entry!r})" + ) + if not callable(entry): + raise AlgoPackageError(f"入口不可调用: {manifest.entry!r}") + + def unmount(self, *, purge_modules: bool = True) -> None: + """从 sys.meta_path 摘除(必须在挂载线程调用)。 + + `purge_modules`:是否同时清理 `sys.modules` 里本包代码命名空间——**同一个 `code_package` + 可能同时被"同 id 的另一个版本"的包装载**(如 probe 试用 v1.1.0 时 v1.0.0 还在用), + 此时清理会让另一份包的模块消失,任务线程再 import 就会触发查找器的跨线程读 + (module_bank 的 svfs 连接有线程约束 → ProgrammingError)。故清理由 + :class:`~.registry.AlgoRegistry` 按"命名空间是否仍被其它已挂载包占用"决定。 + """ + with self._import_lock: + finder, self._finder = self._finder, None + self.mounted = False + if finder is None: + return + manifest = self.manifest + try: + if finder in sys.meta_path: + sys.meta_path.remove(finder) + except ValueError: # pragma: no cover - 已被移除 + pass + if purge_modules and manifest is not None: + purge_namespace(manifest.code_package) + finder.close() + logger.info("[algpkg] 已卸载 %s", manifest.id if manifest else self.filename) + + # ================================================================== 实例构造 + + def new_instance(self, model_name: str | None = None, params: dict | None = None, + dev_id: int = 0, logger_: logging.Logger | None = None) -> Algorithm: + """按 manifest 入口构造并加载一个算法实例(任意线程可调用,实例不再并发调用)。""" + manifest = self.require_manifest() + spec = manifest.model(model_name) + checked = manifest.check_params(params) + ctx = AlgoContext( + algo_id=manifest.id, version=manifest.version, model_name=spec.name, + params=checked, dev_id=dev_id, meta=spec.to_dict(), kind=manifest.kind, + output=manifest.output, logger=logger_ or logger, reader=self.read_file, + models=[item.to_dict() for item in manifest.models], # 多模型包按名取用 + ) + module = importlib.import_module(manifest.entry_module) + entry = getattr(module, manifest.entry_class) + instance = entry(ctx) + if isinstance(instance, Algorithm): + return instance.ensure_loaded() + # 鸭子类型兜底:外部包可能自带基类/依赖旧版 SDK;只要有 infer() 就放行 + if not callable(getattr(instance, "infer", None)): + raise AlgoPackageError( + f"入口 {manifest.entry} 的实例没有 infer()(应为 video_algpkg_api.Algorithm 子类)" + ) + if callable(getattr(instance, "load", None)): + instance.load() + return instance + + # ================================================================== 展示与关闭 + + def info(self, *, enabled: bool = True, active: bool = True, instances: int = 0, + builtin: bool = False) -> dict: + manifest = self.require_manifest() + return { + **manifest.info(), + "filename": self.filename, + "path": str(self.path), + "size_bytes": self.size_bytes, + "sha256": self.sha256, + "source": self.source, + "builtin": builtin, + "enabled": enabled, + "active": active, + "mounted": self.mounted, + "instances": instances, + "signed": bool(self.signature.get("signature")), + "signature": {k: self.signature.get(k) for k in ("key_id", "algorithm", "ok") + if k in self.signature}, + "error": self.last_error, + } + + def close(self) -> None: + """卸载(若已挂载)+ 关闭自有 VFS 句柄。""" + self.unmount() + self._close_vfs() + + def _close_vfs(self) -> None: + vfs, self._vfs = self._vfs, None + if vfs is not None: + try: + with self._lock: + vfs.close() + except Exception as exc: # pragma: no cover + logger.debug("[algpkg] 关闭 VFS 异常 %s: %s", self.filename, exc) + + def __repr__(self) -> str: # pragma: no cover - 调试用 + return f"" diff --git a/src/algpkg_sdk/manifest.py b/src/algpkg_sdk/manifest.py new file mode 100644 index 0000000..504d322 --- /dev/null +++ b/src/algpkg_sdk/manifest.py @@ -0,0 +1,350 @@ +"""算法包清单(manifest.json)解析与校验(M8.1,字段见《算法包与多算法任务设计》§4.1)。 + +职责边界: +- **打包端**(algpkg build)按本模块的 :func:`build_manifest` 生成清单; +- **装载端**(worker)按 :meth:`Manifest.parse` 校验(字段缺失/类型不符 → 拒绝装载, + 错误信息要能直接告诉打包方哪一项不对); +- **任务参数**(params)由 :meth:`Manifest.check_params` 校验并按 schema 补默认值。 + +字段表(`*` 为必需): + +| 字段 | 说明 | +| ---- | ---- | +| `id`* / `name`* / `version`* | 唯一标识(`builtin.yolov26-detect`)、显示名、包版本 | +| `api_version` | 接口版本(缺省 1;> worker 的 ALG_API_VERSION 拒绝装载) | +| `kind` | `detector` / `classifier` / `counter` / `tracker`(决定 UI 与结果渲染) | +| `entry` | 入口类:`Algorithm` 或 `submodule:Algorithm`(相对包代码根,缺省 `Algorithm`) | +| `code_package` | 包代码的模块名(`modules//…`;缺省由 id 推导 `alg_`) | +| `models`* | `[{name, file, precision, resolution, batch, default}]`,含唯一 default | +| `input` | `{width,height,color,layout,mean,std}`(前处理契约,展示用) | +| `output` | `{type: boxes|count|counter, format, classes_file}` | +| `params_schema` | `[{key,type,default,min,max,step,label,desc,options}]`(任务参数表单来源) | +| `defaults` | 默认参数(conf/nms/interval/draw 等,任务未显式给值时用) | +| `alarm` | `{type_template,cooldown_key,need_snapshot,need_record}`(告警生成策略) | +| `requires` | `{license_features[],devices[],frameworks[]}`(授权/设备能力校验) | +| `ui` | `{order,tags,icon}`(算法管理页展示) | +| `vendor` / `description` / `built_at` | 溯源信息 | +| `legacy` | 兼容映射:`{algorithm: "yolo", default_model: "x.bmodel"}`(存量任务迁移用) | +""" +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import Any + +from .api import ALG_API_VERSION, KINDS, OUTPUT_TYPES + +MANIFEST_NAME = "manifest.json" +MANIFEST_FORMAT = 1 +DEFAULT_ENTRY = "Algorithm" + +#: 算法包 id:小写字母/数字开头,允许 . _ - +ID_RE = re.compile(r"^[a-z0-9][a-z0-9._-]*$") +#: 包内相对路径(禁绝对路径与 .. 越界) +_REL_OK = re.compile(r"^[^/\\][^\\]*$") + +#: 参数类型 → 取值转换/校验 +_PARAM_TYPES = ("int", "float", "bool", "str", "enum") + + +class ManifestError(ValueError): + """清单不合法(打包/装载两侧都用它,消息面向"打包方怎么改")。""" + + +def code_package_for(algo_id: str) -> str: + """由 algo_id 推导包内代码模块名(`builtin.yolov26-detect` → `alg_builtin_yolov26_detect`)。""" + return "alg_" + re.sub(r"\W+", "_", str(algo_id or "")).strip("_").lower() + + +def _require(condition: bool, message: str) -> None: + if not condition: + raise ManifestError(message) + + +def _check_rel_path(value: str, field_name: str) -> str: + text = str(value or "").strip().replace("\\", "/") + _require(bool(text), f"{field_name} 不能为空") + _require(not text.startswith("/") and ".." not in text.split("/"), + f"{field_name} 必须是包内相对路径(不得以 / 开头或含 ..): {text!r}") + return text + + +@dataclass +class ModelSpec: + """包内模型条目(一份包可带多份精度/核数不同的模型,任务选其一)。""" + + name: str + file: str + precision: str = "" + resolution: list[int] | None = None + batch: int = 1 + default: bool = False + + @classmethod + def parse(cls, raw: Any, index: int) -> "ModelSpec": + _require(isinstance(raw, dict), f"models[{index}] 必须是对象") + name = str(raw.get("name") or "").strip() + _require(bool(name), f"models[{index}].name 不能为空") + file = _check_rel_path(raw.get("file") or "", f"models[{index}].file") + resolution = raw.get("resolution") + if resolution is not None: + _require(isinstance(resolution, (list, tuple)) and len(resolution) == 2, + f"models[{index}].resolution 必须是 [width, height]") + resolution = [int(resolution[0]), int(resolution[1])] + return cls( + name=name, file=file, + precision=str(raw.get("precision") or "").strip(), + resolution=resolution, + batch=int(raw.get("batch") or 1), + default=bool(raw.get("default", False)), + ) + + def to_dict(self) -> dict: + return {"name": self.name, "file": self.file, "precision": self.precision, + "resolution": self.resolution, "batch": self.batch, "default": self.default} + + +@dataclass +class Manifest: + """已校验的算法包清单(不可变视图 + 查询辅助)。""" + + id: str + name: str + version: str + api_version: int = ALG_API_VERSION + kind: str = "detector" + entry: str = DEFAULT_ENTRY + code_package: str = "" + vendor: str = "" + description: str = "" + built_at: str = "" + models: list[ModelSpec] = field(default_factory=list) + input: dict = field(default_factory=dict) + output: dict = field(default_factory=dict) + params_schema: list[dict] = field(default_factory=list) + defaults: dict = field(default_factory=dict) + alarm: dict = field(default_factory=dict) + requires: dict = field(default_factory=dict) + ui: dict = field(default_factory=dict) + legacy: dict = field(default_factory=dict) + raw: dict = field(default_factory=dict) + + # ------------------------------------------------------------------ 解析 + + @classmethod + def parse(cls, data: Any, *, source: str = "") -> "Manifest": + where = f"({source})" if source else "" + _require(isinstance(data, dict), f"manifest 必须是 JSON 对象{where}") + algo_id = str(data.get("id") or "").strip() + _require(bool(algo_id), f"manifest.id 缺失{where}") + _require(bool(ID_RE.match(algo_id)), + f"manifest.id 只能用小写字母/数字/._-(收到 {algo_id!r}){where}") + name = str(data.get("name") or "").strip() or algo_id + version = str(data.get("version") or "").strip() + _require(bool(version), f"manifest.version 缺失{where}(建议语义化版本,如 1.0.0)") + + api_version = int(data.get("api_version") or ALG_API_VERSION) + _require(api_version <= ALG_API_VERSION, + f"算法包 api_version={api_version} 高于本 worker 支持的 {ALG_API_VERSION}" + f"{where}——请升级 worker 或改用低版本 api 重新打包") + _require(api_version >= 1, f"manifest.api_version 非法: {api_version}{where}") + + kind = str(data.get("kind") or "detector").strip().lower() + _require(kind in KINDS, f"manifest.kind 必须是 {'/'.join(KINDS)} 之一(收到 {kind!r}){where}") + + models_raw = data.get("models") + _require(isinstance(models_raw, list) and len(models_raw) > 0, + f"manifest.models 必须是非空数组{where}") + models = [ModelSpec.parse(item, i) for i, item in enumerate(models_raw)] + names = [m.name for m in models] + _require(len(set(names)) == len(names), f"manifest.models[].name 重复{where}: {names}") + if not any(m.default for m in models): + models[0].default = True + else: + first_default = next(i for i, m in enumerate(models) if m.default) + for i, m in enumerate(models): # 多份 default 只认第一个 + m.default = i == first_default + + output = data.get("output") or {} + _require(isinstance(output, dict), f"manifest.output 必须是对象{where}") + out_type = str(output.get("type") or ({"counter": "counter"}.get(kind, "boxes"))) + _require(out_type in OUTPUT_TYPES, + f"manifest.output.type 必须是 {'/'.join(OUTPUT_TYPES)} 之一(收到 {out_type!r}){where}") + output = {**output, "type": out_type} + + params_schema = data.get("params_schema") or [] + _require(isinstance(params_schema, list), f"manifest.params_schema 必须是数组{where}") + for i, item in enumerate(params_schema): + _require(isinstance(item, dict), f"params_schema[{i}] 必须是对象{where}") + key = str(item.get("key") or "").strip() + _require(bool(key), f"params_schema[{i}].key 不能为空{where}") + ptype = str(item.get("type") or "float").strip().lower() + _require(ptype in _PARAM_TYPES, + f"params_schema[{i}].type 必须是 {'/'.join(_PARAM_TYPES)} 之一(收到 {ptype!r}){where}") + if ptype == "enum": + options = item.get("options") + _require(isinstance(options, list) and len(options) > 0, + f"params_schema[{i}]: type=enum 必须给 options{where}") + schema_keys = [str(i.get("key")) for i in params_schema] + _require(len(set(schema_keys)) == len(schema_keys), + f"params_schema[].key 重复{where}: {schema_keys}") + + defaults = data.get("defaults") or {} + _require(isinstance(defaults, dict), f"manifest.defaults 必须是对象{where}") + for section in ("input", "alarm", "requires", "ui", "legacy"): + value = data.get(section) or {} + _require(isinstance(value, dict), f"manifest.{section} 必须是对象{where}") + + entry = str(data.get("entry") or DEFAULT_ENTRY).strip() or DEFAULT_ENTRY + code_package = str(data.get("code_package") or "").strip() or code_package_for(algo_id) + + return cls( + id=algo_id, name=name, version=version, api_version=api_version, kind=kind, + entry=entry, code_package=code_package, + vendor=str(data.get("vendor") or "").strip(), + description=str(data.get("description") or "").strip(), + built_at=str(data.get("built_at") or "").strip(), + models=models, input=dict(data.get("input") or {}), output=output, + params_schema=[dict(i) for i in params_schema], defaults=dict(defaults), + alarm=dict(data.get("alarm") or {}), requires=dict(data.get("requires") or {}), + ui=dict(data.get("ui") or {}), legacy=dict(data.get("legacy") or {}), + raw=dict(data), + ) + + # ------------------------------------------------------------------ 查询 + + def model(self, name: str | None = None) -> ModelSpec: + """按名取模型(None/空 = 默认模型);不存在抛 ManifestError。""" + wanted = str(name or "").strip() + if not wanted: + return self.default_model() + for spec in self.models: + if spec.name == wanted or spec.file == wanted: + return spec + raise ManifestError( + f"算法包 {self.id} 内没有模型 {wanted!r}(可选:" + f"{', '.join(m.name for m in self.models)})" + ) + + def default_model(self) -> ModelSpec: + return next((m for m in self.models if m.default), self.models[0]) + + @property + def entry_module(self) -> str: + """入口类所在模块(`submodule:Class` 形式时取 submodule,否则包代码根)。""" + module = self.entry.split(":", 1)[0] if ":" in self.entry else "" + module = module.strip() + return f"{self.code_package}.{module}" if module else self.code_package + + @property + def entry_class(self) -> str: + """入口类名。""" + return self.entry.split(":", 1)[1].strip() if ":" in self.entry else self.entry + + def schema_defaults(self) -> dict: + """params_schema 里的默认值(不含 manifest.defaults)。""" + out: dict = {} + for item in self.params_schema: + if "default" in item: + out[str(item["key"])] = item["default"] + return out + + # ------------------------------------------------------------------ 任务参数 + + def check_params(self, params: dict | None) -> dict: + """校验任务参数并按 schema/defaults 补默认值(返回新 dict)。 + + 规则:schema 声明了类型/范围的键做严格校验(越界/类型不符直接拒绝—— + 静默截断会让"界面显示 A、实际跑 B");schema 未声明的键原样保留(向前兼容)。 + """ + given = dict(params or {}) + _require(isinstance(given, dict), "算法参数必须是对象") + merged = {**self.schema_defaults(), **self.defaults, **given} + for item in self.params_schema: + key = str(item["key"]) + if key not in merged: + continue + merged[key] = self._coerce(item, merged[key]) + return merged + + @staticmethod + def _coerce(item: dict, value: Any) -> Any: + key, ptype = str(item["key"]), str(item.get("type") or "float").lower() + label = str(item.get("label") or key) + try: + if ptype == "int": + result: Any = int(value) + elif ptype == "float": + result = float(value) + elif ptype == "bool": + result = bool(value) + elif ptype == "str": + result = str(value) + else: # enum + result = value + options = [o.get("value") if isinstance(o, dict) else o for o in item["options"]] + _require(value in options, f"{label} 必须是 {options} 之一(收到 {value!r})") + return result + except (TypeError, ValueError): + raise ManifestError(f"算法参数 {label} 类型应为 {ptype}(收到 {value!r})") from None + if ptype in ("int", "float"): + if item.get("min") is not None and result < item["min"]: + raise ManifestError(f"算法参数 {label} 不能小于 {item['min']}(收到 {result})") + if item.get("max") is not None and result > item["max"]: + raise ManifestError(f"算法参数 {label} 不能大于 {item['max']}(收到 {result})") + return result + + # ------------------------------------------------------------------ 展示 + + def info(self) -> dict: + """接口展示用(algorithm.list/get/install 的统一结构,不含运行时字段)。""" + return { + "algo_id": self.id, + "name": self.name, + "version": self.version, + "api_version": self.api_version, + "kind": self.kind, + "vendor": self.vendor, + "description": self.description, + "built_at": self.built_at, + "entry": self.entry, + "code_package": self.code_package, + "models": [m.to_dict() for m in self.models], + "default_model": self.default_model().name, + "input": dict(self.input), + "output": dict(self.output), + "params_schema": [dict(i) for i in self.params_schema], + "defaults": dict(self.defaults), + "alarm": dict(self.alarm), + "requires": dict(self.requires), + "ui": dict(self.ui), + "legacy": dict(self.legacy), + } + + +def build_manifest(algo_id: str, name: str, version: str, *, models: list[dict], + code_package: str = "", **fields) -> dict: + """打包端构造清单(algpkg build 用);字段校验交给 :meth:`Manifest.parse`。""" + payload = { + "manifest_format": MANIFEST_FORMAT, + "id": algo_id, + "name": name or algo_id, + "version": version, + "api_version": int(fields.pop("api_version", ALG_API_VERSION)), + "kind": fields.pop("kind", "detector"), + "entry": fields.pop("entry", DEFAULT_ENTRY), + "code_package": code_package or code_package_for(algo_id), + "models": models, + "built_at": fields.pop("built_at", datetime.now(timezone.utc).astimezone() + .isoformat(timespec="seconds")), + } + for key in ("vendor", "description", "input", "output", "params_schema", "defaults", + "alarm", "requires", "ui", "legacy"): + value = fields.pop(key, None) + if value: + payload[key] = value + if fields: + raise ManifestError(f"build_manifest 收到未知字段: {', '.join(sorted(fields))}") + Manifest.parse(payload, source="build") # 打包前先自检:坏清单不许进包 + return payload diff --git a/src/algpkg_sdk/py.typed b/src/algpkg_sdk/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/algpkg_sdk/testkit.py b/src/algpkg_sdk/testkit.py new file mode 100644 index 0000000..f10cfe0 --- /dev/null +++ b/src/algpkg_sdk/testkit.py @@ -0,0 +1,306 @@ +"""本地测试台:**不需要 worker**,直接把算法包跑在图片/视频上验证。 + +覆盖三档(按机器能力自动降级,绝不假装跑过): + +| 档位 | 依赖 | 能验证什么 | +| ---- | ---- | ---- | +| 静态 | 无(任意机器) | manifest 合法性、包结构、模型文件存在、代码可导入(`mount` 试加载) | +| 设备 | Sophon NPU(`sophon-arm`) | 真正 `load()` + `infer()`:跑图片/视频、输出检出、可选画框存图、两包结果对比 | +| 性能 | 设备 | 单帧耗时统计(预热后取均值/分位),供"能不能上现场"先做粗判 | + +典型用法(也封装成了 `algpkg run`): + + from algpkg_sdk import testkit + + report = testkit.check("/tmp/my.algo-1.0.0.mbank") # 静态检查 + runs = testkit.run("/tmp/my.algo-1.0.0.mbank", source="a.jpg", # 设备上真跑 + params={"conf": 0.3}, save_annotated="out/") + testkit.compare(a_runs, b_runs) # 包/版本对拍 +""" +from __future__ import annotations + +import logging +import os +import sys +import time +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +from .api import Algorithm, AlgoResult +from .loader import AlgoPackage, AlgoPackageError +from .manifest import Manifest + +logger = logging.getLogger(__name__) + + +#: 额外的包搜索路径(环境变量,冒号分隔)。**只给"特权包"用**: +#: 例如内置包 `builtin.yolov26-detect` 复用 worker 自带的推理实现(`workers.video.algorithm.yolo`), +#: 脱离 worker 环境跑测试时需要把 worker 工程加进来: +#: ALGPKG_EXTRA_PATH=/data/sophon-edge/workers/video algpkg run builtin.mbank --source a.jpg +#: 第三方包应**自带实现**(只依赖 algpkg_sdk / video_algpkg_api),不需要这个开关。 +ENV_EXTRA_PATH = "ALGPKG_EXTRA_PATH" + + +def apply_extra_path(extra: list[str] | None = None) -> list[str]: + """把额外搜索路径插到 sys.path 最前(幂等);返回实际生效的路径列表。""" + items: list[str] = [] + for chunk in (extra or []) + (os.environ.get(ENV_EXTRA_PATH) or "").split(":"): + text = str(chunk or "").strip() + if text and Path(text).is_dir() and text not in items: + items.append(text) + for item in reversed(items): + if item not in sys.path: + sys.path.insert(0, item) + return items + + +def has_device() -> bool: + """本机是否具备设备推理能力(能不能 import sophon.sail)。""" + try: + import sophon.sail # noqa: F401 + return True + except Exception: + return False + + +# ---------------------------------------------------------------------- 静态检查 + +def check(path: str | Path, *, key: str | None = None, deep: bool = False, + dev_id: int = 0, extra_path: list[str] | None = None) -> dict: + """静态检查一个包(可在任意机器运行)。 + + `deep=True` 时额外**实例化入口类**(会 load 模型 → 需要设备)。 + 返回结构化报告:`{ok, manifest, files, checks:[{name, ok, detail}]}`。 + """ + applied = apply_extra_path(extra_path) + checks: list[dict] = [] + + def add(name: str, ok: bool, detail: str = "") -> None: + checks.append({"name": name, "ok": bool(ok), "detail": detail}) + + pkg = AlgoPackage(path, key=key) + manifest = None + try: + pkg.open() + manifest = pkg.require_manifest() + add("manifest 合法", True, f"{manifest.id}@{manifest.version}(api v{manifest.api_version})") + except AlgoPackageError as exc: + add("manifest 合法", False, str(exc)) + return {"ok": False, "manifest": None, "files": [], "checks": checks} + + add("模型文件在包内", True, + ", ".join(f"{m.name}({m.file})" for m in manifest.models)) + + files = pkg.list_files() + add("包内文件", True, ", ".join(f["path"] for f in files)) + + if not has_device(): + add("代码挂载(试加载)", False, "本机没有 sophon.sail,跳过(可在设备上重跑)") + return {"ok": False, "manifest": manifest.info(), "files": files, "checks": checks, + "device": False, "extra_path": applied} + + try: + pkg.mount() # 挂载 = 全量 import(试加载) + add("代码挂载(试加载)", True, f"代码包 {manifest.code_package}") + except Exception as exc: + add("代码挂载(试加载)", False, f"{type(exc).__name__}: {exc}") + finally: + try: + pkg.unmount() # 清命名空间:检查完不留状态 + except Exception: # pragma: no cover + pass + + if deep: + try: + spec = manifest.default_model() + instance = pkg.new_instance(spec.name, {}, dev_id=dev_id) + add("默认模型可加载", True, f"{spec.name}({type(instance).__name__})") + try: + instance.close() + except Exception: + pass + except Exception as exc: + add("默认模型可加载", False, f"{type(exc).__name__}: {exc}") + + ok = all(c["ok"] for c in checks) + pkg.close() + return {"ok": ok, "manifest": manifest.info(), "files": files, "checks": checks, + "device": True, "extra_path": applied} + + +# ---------------------------------------------------------------------- 设备上真跑 + +@dataclass +class FrameResult: + index: int + detections: int + boxes: Any = None + counts: dict | None = None + value: float | None = None + infer_ms: float = 0.0 + + +@dataclass +class RunReport: + package: str + algo_id: str = "" + version: str = "" + model: str = "" + source: str = "" + frames: list[FrameResult] = field(default_factory=list) + total_ms: float = 0.0 + annotated: list[str] = field(default_factory=list) + error: str | None = None + + @property + def detections(self) -> list[int]: + return [f.detections for f in self.frames] + + def summary(self) -> dict: + times = [f.infer_ms for f in self.frames if f.infer_ms] + return { + "algo_id": self.algo_id, "version": self.version, "model": self.model, + "source": self.source, "frames": len(self.frames), + "detections": self.detections, + "infer_ms": {"avg": round(sum(times) / len(times), 1) if times else 0, + "min": round(min(times), 1) if times else 0, + "max": round(max(times), 1) if times else 0}, + "annotated": self.annotated, "error": self.error, + } + + +def run(path: str | Path, *, source: str, params: dict | None = None, model: str | None = None, + frames: int = 5, warmup: int = 2, dev_id: int = 0, key: str | None = None, + save_annotated: str | Path | None = None, + extra_path: list[str] | None = None) -> RunReport: + """在设备上真跑:解码 `source`(图片或视频)→ 逐帧 `infer` → 汇总。 + + - `warmup` 帧不计入耗时(首次推理含设备初始化,测性能要排除); + - `save_annotated` 给了目录就把每帧结果画框存图(用包内 labels + worker 同款调色板)。 + """ + import sophon.sail as sail + + if not has_device(): + raise RuntimeError("本机没有 sophon.sail:`run` 需要设备(图片/视频推理);" + "可用 `check()` 做静态检查") + apply_extra_path(extra_path) + + pkg = AlgoPackage(path, key=key) + pkg.open() + manifest = pkg.require_manifest() + report = RunReport(package=str(path), algo_id=manifest.id, version=manifest.version) + decoder = None + # 先挂载代码:`new_instance` 要 import 包内模块(等价于 worker 装载时的 install_importer + 试加载)。 + # 之前漏了这步,"单进程里先跑过 check"会因模块残留在 sys.modules 而假通过 —— 独立 CLI 一跑就露。 + pkg.mount() + try: + model_spec = manifest.model(model or manifest.default_model().name) + merged = manifest.check_params(params or {}) + report.model = model_spec.name + report.source = str(source) + instance = pkg.new_instance(model_spec.name, merged, dev_id=dev_id) + + decoder = sail.Decoder(str(source), True, dev_id) + if not decoder.is_opened(): + raise RuntimeError(f"解码失败(源不可用): {source}") + handle = sail.Handle(dev_id) + + total = warmup + max(1, frames) + for index in range(total): + frame = decoder.read(handle) + if frame is None or frame.width() == 0: + break + started = time.perf_counter() + result = instance.infer(frame) + elapsed = (time.perf_counter() - started) * 1000 + if index < warmup: + continue + boxed = result if isinstance(result, AlgoResult) else AlgoResult(boxes=result) + report.frames.append(FrameResult( + index=index - warmup, detections=boxed.detections(), boxes=boxed.boxes, + counts=boxed.counts, value=boxed.value, infer_ms=elapsed)) + if save_annotated is not None: + path_out = _annotate(frame, boxed, manifest, Path(save_annotated), + index - warmup, dev_id) + report.annotated.append(str(path_out)) + try: + instance.close() + except Exception: # pragma: no cover + pass + report.total_ms = sum(f.infer_ms for f in report.frames) + except Exception as exc: + report.error = f"{type(exc).__name__}: {exc}" + raise + finally: + if decoder is not None: + try: + decoder.release() + except Exception: + pass + pkg.close() # close = 卸载(清 sys.modules 命名空间)+ 关闭 VFS + return report + + +def compare(a: RunReport, b: RunReport, *, tolerance: float = 0.0) -> dict: + """两个包/版本的推理结果对比(对拍用):逐帧检出数、逐位一致性。""" + import numpy as np + + pairs = min(len(a.frames), len(b.frames)) + frames = [] + identical = True + for i in range(pairs): + fa, fb = a.frames[i], b.frames[i] + same = False + if fa.boxes is None and fb.boxes is None: + same = True + elif fa.boxes is not None and fb.boxes is not None: + same = (fa.boxes.shape == fb.boxes.shape and + bool(np.allclose(fa.boxes, fb.boxes, atol=tolerance))) + identical = identical and same + frames.append({"frame": i, "a": fa.detections, "b": fb.detections, "identical": same}) + return { + "a": f"{a.algo_id}@{a.version}:{a.model}", "b": f"{b.algo_id}@{b.version}:{b.model}", + "frames": frames, "identical": identical and pairs > 0, + "note": "identical=True 表示两边的框逐位一致(tolerance 可放宽)", + } + + +def _annotate(frame, result: AlgoResult, manifest: Manifest, out_dir: Path, index: int, + dev_id: int) -> Path: + """把结果画到帧上并保存(复用 sail/bmcv;失败时退回保存原始帧)。""" + import sophon.sail as sail + + out_dir.mkdir(parents=True, exist_ok=True) + path = out_dir / f"{manifest.id}-{index:04d}.jpg" + try: + handle = sail.Handle(dev_id) + bmcv = sail.Bmcv(handle) + canvas = sail.BMImage(handle, frame.height(), frame.width(), + sail.Format.FORMAT_YUV444P, sail.DATA_TYPE_EXT_1N_BYTE) + bmcv.convert_format(frame, canvas) + if result.boxes is not None: + for det in result.boxes: + x1, y1, x2, y2 = (int(v) for v in det[:4]) + w, h = x2 - x1, y2 - y1 + if w > 4 and h > 4: + bmcv.rectangle(canvas, x1, y1, w, h, _palette(int(det[5])), 2) + bmcv.imwrite(str(path), canvas) + except Exception as exc: # 画框失败不致命:至少留下原帧 + logger.warning("[testkit] 画框失败(%s),保存原始帧", exc) + try: + sail.Bmcv(sail.Handle(dev_id)).imwrite(str(path), frame) + except Exception: + return Path("") + return path + + +def _palette(cls: int) -> tuple[int, int, int]: + """类索引 → YUV444P 颜色(与 worker 的统一绘制同色系,够看即可)。""" + colors = [(0, 128, 128), (255, 128, 128), (128, 64, 128), (128, 192, 128), + (128, 128, 64), (64, 128, 128)] + return colors[cls % len(colors)] + + +__all__ = ["has_device", "check", "run", "compare", "apply_extra_path", "ENV_EXTRA_PATH", + "RunReport", "FrameResult"] diff --git a/src/video_algpkg_api/__init__.py b/src/video_algpkg_api/__init__.py new file mode 100644 index 0000000..062d15f --- /dev/null +++ b/src/video_algpkg_api/__init__.py @@ -0,0 +1,35 @@ +"""`video_algpkg_api` —— 算法包接口契约名的**真实模块**(随 algpkg-sdk 分发)。 + +算法包内的代码应当且只应写这一个导入名:: + + from video_algpkg_api import Algorithm, AlgoContext, AlgoResult + +本模块把它变成任何工具都能直接解析的名字(IDE 补全 / mypy / pyright / 本地直跑), +不必先依赖 worker 在运行时把 ``video_algpkg_api`` 注册进 ``sys.modules``。 + +三条保证(改这里之前先想清楚): + +- **对象同一**:本模块只是 `algpkg_sdk.api` 的**重导出**(同一批类/函数对象,不是副本), + 因此 ``isinstance(algo, Algorithm)`` 校验(worker 侧 ``AlgoPackage.new_instance``) + 在"本地用本模块"与"设备用运行时注册"两种解析路径下完全一致; +- **设备侧不受影响**:worker 启动与每次挂载前仍会调 + :func:`algpkg_sdk.api.install_sdk_alias`(``sys.modules.setdefault``)——在 worker 进程里 + 该名字先被注册为 ``algpkg_sdk.api``,本模块此时被惰性遮蔽,两者指向同一批对象,谁先解析都一样; +- **不要另造同名模块**:自行 vendor/copy 一份会造成"本地用副本、设备用权威版"的分叉, + 两份实现不一致时表现为"本地过、设备挂";包内代码禁止携带本模块(打包只收 + ``manifest.code_package/`` 目录与 models/assets,正常不会带上)。 + +本地开发只看这一条:装好 algpkg-sdk(``uv pip install -e ./algpkg-sdk``)即可—— +导入本模块 → 自动完成 SDK 侧的全部初始化,与设备运行看到的是同一套接口。 +""" +from __future__ import annotations + +from algpkg_sdk.api import (ALG_API_VERSION, KINDS, OUTPUT_TYPES, SDK_MODULE_NAME, + AlgoContext, AlgoError, AlgoResult, Algorithm, + install_sdk_alias) + +#: 与 algpkg_sdk.api 的公共面保持一致(显式列出而非 `import *`:类型检查器据此识别重导出) +__all__ = [ + "ALG_API_VERSION", "KINDS", "OUTPUT_TYPES", "SDK_MODULE_NAME", + "AlgoContext", "AlgoError", "AlgoResult", "Algorithm", "install_sdk_alias", +] diff --git a/src/video_algpkg_api/py.typed b/src/video_algpkg_api/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_sdk.py b/tests/test_sdk.py new file mode 100644 index 0000000..5fb56e2 --- /dev/null +++ b/tests/test_sdk.py @@ -0,0 +1,388 @@ +"""SDK 自测:清单校验 / 打包往返 / 装载策略 / 本地测试台(无需 worker)。 + +有设备的用例标 `@pytest.mark.device`(无 sophon.sail 时自动跳过): + + uv run pytest # 静态用例 + uv run pytest -m device # 设备上跑推理 +""" +from __future__ import annotations + +import json +import subprocess +import sys +import tempfile +from pathlib import Path + +import pytest + +from algpkg_sdk import (AlgoBuilder, AlgoError, AlgoPackage, AlgoPackageError, Manifest, + ManifestError, install_sdk_alias, sha256_file) +from algpkg_sdk import testkit +from algpkg_sdk.cli import main as cli_main + +EXAMPLES = Path(__file__).resolve().parents[1] / "examples" + + +def _manifest(**overrides) -> dict: + payload = { + "manifest_format": 1, "id": "t.demo", "name": "测试包", "version": "1.0.0", + "api_version": 1, "kind": "detector", "entry": "Algorithm", + "code_package": "alg_t_demo", + "models": [{"name": "default", "file": "models/t.bmodel", "precision": "fp16", + "resolution": [640, 640], "batch": 1, "default": True}], + "params_schema": [{"key": "conf", "type": "float", "default": 0.25, + "min": 0.01, "max": 0.99, "label": "置信度"}], + "defaults": {"conf": 0.25}, + } + payload.update(overrides) + return payload + + +# ---------------------------------------------------------------------- 清单 + +def test_manifest_required_fields() -> None: + Manifest.parse(_manifest()) + for missing in ("id", "version", "models"): + bad = _manifest() + bad.pop(missing) + with pytest.raises(ManifestError): + Manifest.parse(bad) + + +def test_manifest_rejects_bad_values() -> None: + with pytest.raises(ManifestError): # id 形态 + Manifest.parse(_manifest(id="Bad Id!")) + with pytest.raises(ManifestError): # 模型路径越界 + Manifest.parse(_manifest(models=[{"name": "d", "file": "../x.bmodel"}])) + with pytest.raises(ManifestError): # api_version 过高 + Manifest.parse(_manifest(api_version=99)) + with pytest.raises(ManifestError): # params_schema 重复键 + Manifest.parse(_manifest(params_schema=[ + {"key": "conf", "type": "float"}, {"key": "conf", "type": "float"}])) + + +def test_manifest_default_model_pick() -> None: + manifest = Manifest.parse(_manifest(models=[ + {"name": "fp16", "file": "models/a.bmodel"}, + {"name": "int8", "file": "models/b.bmodel", "default": True}])) + assert manifest.default_model().name == "int8" + assert manifest.model("fp16").file == "models/a.bmodel" + with pytest.raises(ManifestError): + manifest.model("nope") + + +def test_manifest_check_params_semantics() -> None: + manifest = Manifest.parse(_manifest()) + merged = manifest.check_params({"conf": 0.5}) + assert merged["conf"] == 0.5 + assert manifest.check_params({})["conf"] == 0.25 # 补默认 + with pytest.raises(ManifestError): # 越界拒绝(不静默截断) + manifest.check_params({"conf": 2.0}) + with pytest.raises(ManifestError): # 类型不符 + manifest.check_params({"conf": "abc"}) + + +def test_manifest_legacy_and_entry_helpers() -> None: + manifest = Manifest.parse(_manifest(entry="sub:Alg", legacy={"algorithm": "yolo"})) + assert manifest.entry_module == "alg_t_demo.sub" + assert manifest.entry_class == "Alg" + assert manifest.legacy["algorithm"] == "yolo" + + +# ---------------------------------------------------------------------- 打包往返 + +def _make_source(root: Path, *, model_bytes: bytes = b"FAKE-BMODEL" * 512) -> Path: + src = root / "t.demo" + (src / "alg_t_demo").mkdir(parents=True) + (src / "alg_t_demo" / "__init__.py").write_text( + "from video_algpkg_api import Algorithm as Base, AlgoResult\n" + "class Algorithm(Base):\n" + " def load(self):\n" + " self._data = self.ctx.model_bytes()\n" + " self.ctx.logger.info('loaded %s bytes', len(self._data))\n" + " def infer(self, frame):\n" + " return AlgoResult(boxes=None, value=len(self._data))\n", + encoding="utf-8", + ) + (src / "models").mkdir() + (src / "models" / "t.bmodel").write_bytes(model_bytes) + (src / "assets").mkdir() + (src / "assets" / "labels.txt").write_text("person\ncar\n", encoding="utf-8") + (src / "manifest.json").write_text(json.dumps(_manifest()), encoding="utf-8") + return src + + +def test_build_roundtrip_and_bytes_fidelity() -> None: + install_sdk_alias() + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + src = _make_source(root) + out = root / "t.demo-1.0.0.mbank" + summary = AlgoBuilder(src, verbose=False).build(out) + + assert out.is_file() and summary["algo_id"] == "t.demo" + assert summary["sha256"] == sha256_file(out) + + pkg = AlgoPackage(out) + pkg.open() + manifest = pkg.require_manifest() + assert manifest.version == "1.0.0" + data, meta = pkg.read_model("default") + assert data == (src / "models" / "t.bmodel").read_bytes() # 包内模型与源文件逐字节相同 + assert meta["precision"] == "fp16" + assert pkg.read_asset("assets/labels.txt").decode().splitlines() == ["person", "car"] + files = {item["path"] for item in pkg.list_files()} + assert {"manifest.json", "assets/labels.txt", "models/t.bmodel"} <= files + pkg.close() + + +def test_build_creates_version_snapshot_and_signature() -> None: + from sqlite_vfs.core import SQLiteVFS + + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + src = _make_source(root) + out = root / "signed.mbank" + AlgoBuilder(src, verbose=False).build(out, sign_key="KEY-1", sign_key_id="ci") + vfs = SQLiteVFS(str(out), readonly=True, check_same_thread=False) + try: + assert [s["name"] for s in vfs.list_snapshots()] == ["1.0.0"] + report = vfs.verify_signature("KEY-1") + assert report["ok"] and report["key_id"] == "ci" + assert not vfs.verify_signature("KEY-WRONG")["ok"] + finally: + vfs.close() + + +def test_build_reports_missing_model_and_code() -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + src = _make_source(root) + (src / "models" / "t.bmodel").unlink() + with pytest.raises(Exception) as exc: + AlgoBuilder(src, verbose=False).build(root / "x.mbank") + assert "模型" in str(exc.value) + + src2 = _make_source(root / "other") + import shutil + shutil.rmtree(src2 / "alg_t_demo") + with pytest.raises(Exception) as exc2: + AlgoBuilder(src2, verbose=False).build(root / "y.mbank") + assert "代码目录" in str(exc2.value) + + +def test_encrypt_removes_plain_source() -> None: + from sqlite_vfs.core import SQLiteVFS + + key = "uA9mYq0T2wGZ0dX1vJ8kL3pR5sN7bC4eF6hK2mP0qT8=" + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + src = _make_source(root) + out = root / "enc.mbank" + AlgoBuilder(src, key=key, verbose=False).build(out) + vfs = SQLiteVFS(str(out), readonly=True, check_same_thread=False) + try: + files = [e["path"] for e in vfs.all_entries() if not e["is_directory"]] + finally: + vfs.close() + assert not any(f.endswith(".py") for f in files) # 明文源码不得留在包里 + # 不带 key 打开结构没问题;装载(import)会因缺密钥失败 + pkg = AlgoPackage(out) + pkg.open() + with pytest.raises(AlgoPackageError) as exc: + pkg.mount() + assert "密钥" in str(exc.value) + pkg.close() + + +def test_bad_file_rejected() -> None: + with tempfile.TemporaryDirectory() as tmp: + bad = Path(tmp) / "bad.mbank" + bad.write_bytes(b"not a database at all") + with pytest.raises(AlgoPackageError): + AlgoPackage(bad).open() + + +# ---------------------------------------------------------------------- 多模型包 + +def _make_multi_model_source(root: Path) -> Path: + """一个包带两份模型(检测 + 二次判定):代码按名字取第二份(多模型包的标准写法)。""" + src = root / "t.multi" + (src / "alg_t_multi").mkdir(parents=True) + (src / "alg_t_multi" / "__init__.py").write_text( + "from video_algpkg_api import Algorithm as Base, AlgoResult\n" + "class Algorithm(Base):\n" + " def load(self):\n" + " self._det = self.ctx.model_bytes() # 选中模型(默认检测器)\n" + " self._cls = self.ctx.model_bytes('fp_filter') # 按名字取第二份\n" + " def infer(self, frame):\n" + " return AlgoResult(boxes=None, value=len(self._det) + len(self._cls))\n", + encoding="utf-8", + ) + (src / "models").mkdir() + (src / "models" / "det.bmodel").write_bytes(b"DET" * 256) + (src / "models" / "fp.bmodel").write_bytes(b"FPFILTER" * 128) + (src / "manifest.json").write_text(json.dumps(_manifest( + id="t.multi", name="多模型包", code_package="alg_t_multi", + models=[{"name": "detector", "file": "models/det.bmodel", "default": True}, + {"name": "fp_filter", "file": "models/fp.bmodel", "precision": "int8"}])), + encoding="utf-8") + return src + + +def test_multi_model_package_context_access() -> None: + """`ctx.model_bytes(name)` 按 manifest.models[] 取包内任意模型;不给名 = 选中模型(旧写法兼容)。""" + install_sdk_alias() + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + src = _make_multi_model_source(root) + out = root / "multi.mbank" + AlgoBuilder(src, verbose=False).build(out) + + pkg = AlgoPackage(out) + pkg.open() + pkg.mount() + try: + instance = pkg.new_instance("detector", {}) + ctx = instance.ctx + det = (src / "models" / "det.bmodel").read_bytes() + fp = (src / "models" / "fp.bmodel").read_bytes() + assert ctx.model_name == "detector" + assert ctx.model_bytes() == det # 选中模型(向后兼容) + assert ctx.model_bytes("fp_filter") == fp # 按 name 取第二份 + assert ctx.model_bytes("models/fp.bmodel") == fp # 按 file 路径也认 + assert [m["name"] for m in ctx.models] == ["detector", "fp_filter"] + assert ctx.model_spec("fp_filter")["precision"] == "int8" + assert ctx.model_spec()["name"] == "detector" + with pytest.raises(AlgoError) as exc: + ctx.model_bytes("nope") + assert "fp_filter" in str(exc.value) # 报错带可选清单 + assert instance.infer(None).value == len(det) + len(fp) # 包内代码真读到两份不同字节 + finally: + pkg.close() + + +# ---------------------------------------------------------------------- 契约名(video_algpkg_api) + +def test_contract_module_importable_without_prior_registration() -> None: + """`video_algpkg_api` 是随 SDK 分发的**真实模块**,不只是运行时注册的别名。 + + 新解释器里**先** import 它也要成功(本地开发/IDE/类型检查的常见姿势), + 且与 `algpkg_sdk.api` 是同一批对象(重导出,非副本)——否则 worker 侧 + `isinstance(instance, Algorithm)` 会出现两个基类("本地过、设备挂")。 + """ + script = ( + "import video_algpkg_api as contract\n" + "assert contract.__file__.endswith('video_algpkg_api/__init__.py'), contract.__file__\n" + "import algpkg_sdk.api as api\n" + "for name in contract.__all__:\n" + " assert getattr(contract, name) is getattr(api, name), name\n" + "print('contract-ok')\n" + ) + with tempfile.TemporaryDirectory() as tmp: + proc = subprocess.run([sys.executable, "-c", script], capture_output=True, + text=True, timeout=30, cwd=tmp) + assert proc.returncode == 0, proc.stderr + assert "contract-ok" in proc.stdout + + +def test_contract_alias_still_registered_to_api_module() -> None: + """worker 侧注册语义不变:导入 `algpkg_sdk` 后,`video_algpkg_api` 解析到 api 模块本身。 + + (`install_sdk_alias` 用 setdefault:设备上 worker 先注册,真实模块被惰性遮蔽, + 两条路径拿到的是同一批对象。) + """ + import video_algpkg_api as resolved + + from algpkg_sdk import api + + assert resolved is api + + +# ---------------------------------------------------------------------- CLI + +def test_cli_init_build_inspect_check(tmp_path: Path) -> None: + src = tmp_path / "cli.demo" + assert cli_main(["init", str(src), "--id", "cli.demo", "--name", "CLI 示例"]) == 0 + assert (src / "manifest.json").is_file() + code_pkg = next((src.glob("alg_cli_demo")), None) + assert code_pkg is not None and (code_pkg / "__init__.py").is_file() + + model = tmp_path / "fake.bmodel" + model.write_bytes(b"FAKE" * 1024) + out = tmp_path / "cli.demo-1.0.0.mbank" + assert cli_main(["build", str(src), "--out", str(out), "--model", f"default={model}", + "--quiet"]) == 0 + assert cli_main(["inspect", str(out), "--json"]) == 0 + assert cli_main(["verify", str(out), "--json"]) == 0 + assert cli_main(["list", str(tmp_path)]) == 0 + + +def test_cli_check_reports_static_results(tmp_path: Path) -> None: + """check 在无设备时也要给出"清单/结构 OK、试加载跳过"的诚实结论。""" + src = tmp_path / "chk.demo" + cli_main(["init", str(src), "--id", "chk.demo", "--name", "检查示例"]) + model = tmp_path / "fake.bmodel" + model.write_bytes(b"FAKE" * 1024) + out = tmp_path / "chk.mbank" + cli_main(["build", str(src), "--out", str(out), "--model", f"default={model}", "--quiet"]) + report = testkit.check(out) + names = {item["name"]: item for item in report["checks"]} + assert names["manifest 合法"]["ok"] + assert names["模型文件在包内"]["ok"] + if not testkit.has_device(): + assert not names["代码挂载(试加载)"]["ok"] and "跳过" in names["代码挂载(试加载)"]["detail"] + + +def test_module_entry_point_help() -> None: + proc = subprocess.run([sys.executable, "-m", "algpkg_sdk.cli", "--help"], + capture_output=True, text=True, timeout=30) + assert proc.returncode == 0 + assert "check" in proc.stdout and "run" in proc.stdout and "compare" in proc.stdout + + +# ---------------------------------------------------------------------- 示例包(真实结构) + +def test_example_person_count_manifest() -> None: + example = EXAMPLES / "person-count" + manifest = Manifest.parse(json.loads((example / "manifest.json").read_text())) + assert manifest.id == "demo.person-count" and manifest.kind == "counter" + assert manifest.output["type"] == "counter" + assert (example / manifest.code_package).is_dir() + # 模型文件由打包时 --model 指定(示例目录不带 bmodel),这里只校验声明形态 + assert manifest.default_model().file.startswith("models/") + + +# ---------------------------------------------------------------------- 设备用例 + +@pytest.mark.device +def test_check_deep_on_device() -> None: + """设备:入口类可实例化(模型真加载)。 + + 默认用**自包含**示例包(examples/person-count 打出来的包);内置包(依赖 worker 内部模块) + 属"特权包",需额外传 `extra_path=[worker 工程目录]`。 + """ + import os + + pkg_path = os.environ.get("ALGPKG_DEVICE_PACKAGE") + if not pkg_path or not Path(pkg_path).is_file(): + pytest.skip("未提供 ALGPKG_DEVICE_PACKAGE(指向含真实模型的 .mbank)") + extra = [p for p in (os.environ.get("ALGPKG_EXTRA_PATH") or "").split(":") if p] + report = testkit.check(pkg_path, deep=True, extra_path=extra) + assert report["ok"], report["checks"] + + +@pytest.mark.device +def test_run_on_device() -> None: + """设备:跑一段视频,拿到逐帧检出与单帧耗时。""" + import os + + pkg_path = os.environ.get("ALGPKG_DEVICE_PACKAGE") + clip = os.environ.get("ALGPKG_DEVICE_CLIP") + if not pkg_path or not clip: + pytest.skip("未提供 ALGPKG_DEVICE_PACKAGE / ALGPKG_DEVICE_CLIP") + extra = [p for p in (os.environ.get("ALGPKG_EXTRA_PATH") or "").split(":") if p] + report = testkit.run(pkg_path, source=clip, frames=3, warmup=1, extra_path=extra) + summary = report.summary() + assert summary["frames"] >= 1 + assert summary["infer_ms"]["avg"] > 0 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..942be7f --- /dev/null +++ b/uv.lock @@ -0,0 +1,789 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "algpkg-sdk" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "module-bank" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "orjson" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "sophon-arm" }, +] +device = [ + { name = "sophon-arm" }, +] + +[package.metadata] +requires-dist = [ + { name = "module-bank", specifier = ">=0.4.2" }, + { name = "numpy", specifier = ">=1.26" }, + { name = "orjson", specifier = ">=3.10" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8" }, + { name = "sophon-arm", marker = "extra == 'dev'", specifier = ">=3.11.1" }, + { name = "sophon-arm", marker = "extra == 'device'", specifier = ">=3.11.1" }, +] +provides-extras = ["device", "dev"] + +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/d2/2cde336b375f55c76ca670f0be3978cc048e31e24f3b4d7ce8473150a388/cffi-2.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be", size = 183779, upload-time = "2026-08-03T21:19:15.602Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/1a/4b2f7c92293ba05cbd4a9a1b28faaf0326272d9488e6354657571c48a7aa/cffi-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b", size = 184178, upload-time = "2026-08-03T21:19:16.67Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/0b/ba385d8ccedf926c3cd06e8e2f327027da5afe5f0eb30f1f7bc43ac55125/cffi-2.1.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004", size = 211037, upload-time = "2026-08-03T21:19:17.705Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/b9/0f2e58b2cefa33255bff36935d42b13180fe559bba82596540eb404bde7d/cffi-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9", size = 218652, upload-time = "2026-08-03T21:19:18.735Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/15/180e0dab27b9312c7479003d14c9e547634b7dcb934e2cc4650e1b131a7a/cffi-2.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98", size = 205422, upload-time = "2026-08-03T21:19:19.96Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/d4/03026f0c850cbbaa9030750490225b4a7f4d524ea4df72c3cc740a90f4ef/cffi-2.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9", size = 205444, upload-time = "2026-08-03T21:19:21.246Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/77/60bebf6f818bec84210ac5b6979ce4eeadce6fbbaabc9c7ab23e506d1ce5/cffi-2.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6", size = 218742, upload-time = "2026-08-03T21:19:22.523Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/ae/679bf47e73fd77b352171727f07de559a003f14de5d02b904a6ec1fa73ca/cffi-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf", size = 221054, upload-time = "2026-08-03T21:19:23.694Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/b8/eefc0e06913b70aa153bf74c946094a18f58fd4aff11b7f372bfdfdca050/cffi-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659", size = 213489, upload-time = "2026-08-03T21:19:24.922Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/13/4e56852824a03cdf68523a35686f1c28eacd4bd30a7b0a78e682e6e6e1d3/cffi-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9", size = 220241, upload-time = "2026-08-03T21:19:26.214Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/7f/040f9e163e4acac3ee3d85b02d00b2576e7ca980d8785f0a3a5f1a9bf7f5/cffi-2.1.1-cp310-cp310-win32.whl", hash = "sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41", size = 174578, upload-time = "2026-08-03T21:19:27.338Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/0b/644a2ec1a4eaba49c2939410bb1eb1d25b09d6d0582f5d2f95c537043725/cffi-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1", size = 185082, upload-time = "2026-08-03T21:19:28.409Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470, upload-time = "2026-08-03T21:19:41.246Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096, upload-time = "2026-08-03T21:19:42.615Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941, upload-time = "2026-08-03T21:19:43.747Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/af/182eb91b0df3fe75c4d9f26fe70684569566745f6ba7e5c9c73a862c5252/cryptography-50.0.2.tar.gz", hash = "sha256:7b46165bb56eb4704e2eaaf86f3c940d19154535d9b0ca7d6d590b04060e00d5", size = 880623, upload-time = "2026-09-30T15:30:04.884Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/56/d194340cc4a57535e82e1bee9e89667ac4b7c13b5d3f59686deae3094dd5/cryptography-50.0.2-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:fa8f5efb344d6908a1ce62f4a24e2e5780f825d6f53f5f50ec5ffacac72936cb", size = 3914904, upload-time = "2026-09-30T14:43:44.339Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/69/c9bd862c3bf43d6399c433caf002df16e2dffd4be49bdf515cda38038711/cryptography-50.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:79def8d059362e7831389ed3be0ecdf58a89386e1271e35dd9f5af84e81bffd0", size = 4731146, upload-time = "2026-09-30T14:43:47.113Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/69/64cef1f702bf6657e0cc186ed1a2891d50d29fb41586b254e1c07adea261/cryptography-50.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630ebfea3bf689d075f82316324ff7433dc447fe6bc1bfc76524b74b4a9567d2", size = 4719841, upload-time = "2026-09-30T14:43:49.01Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/6b/61a3f8d8c5e1e49a6cddccafc4015cc1c0021360ab0acb4080e7a423644a/cryptography-50.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f9f6143a8c75945eb960d9eb98905a441394abfa24afaae239d514ffb2586480", size = 4738340, upload-time = "2026-09-30T14:43:50.932Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/2e/7212ca32fd43dc91f2f41db20160b268098874b4c9a0e7be94d6835f5b2e/cryptography-50.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a582ab2ae1d34f67112cadc86702774c9ea4374df6bca6afe672817203c99134", size = 5367029, upload-time = "2026-09-30T14:43:52.911Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/f1/b474e930c4d910328780e3940da76f5aa5cbc48ce1fc14e44d239d9ea9db/cryptography-50.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856", size = 4753050, upload-time = "2026-09-30T14:43:55.272Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/52/9af10e80ac16b0fcc2123f9cbd5e7afbd0fd5075bb7a607c592258a39cda/cryptography-50.0.2-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ac9ed99d81760c62fe89d5f0815cdfa1ba9a35141cf30f1c2d044f04b4803d2e", size = 4376724, upload-time = "2026-09-30T14:43:57.24Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/37/6202e488cc1eb625ea110c292c6bda92823176e023f427d8d5660ce8d632/cryptography-50.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:87e9ce85beb6b328ba370cc6e6aea483c92617b4c95b1d33a49297eb662bfb04", size = 4737859, upload-time = "2026-09-30T14:43:59.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/30/e86d7d518489b0ae2497091a35287abcb1a2ce4037837a34afbe9b1d6964/cryptography-50.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f265528741e048bce55c3463ed721fb0aa45a5888d8add8cfeccb3035451bbdc", size = 5324103, upload-time = "2026-09-30T14:44:01.901Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/69/2c833a049475e0a3444e94c7d0aca0aa51d166374a449b09e92ac98138de/cryptography-50.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dab55f57c74c3cad24c323bacbbd04be4705ba6eb0d92e920b1fc4837ed5079", size = 4752576, upload-time = "2026-09-30T14:44:04.545Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/5d/906970b83bbfc1f5bbfb677a143c181f2801f23b6a7204a3b47c42c97e65/cryptography-50.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25784ce8b9621c90c643efb9e1e2162ab3b0224cae446ad5e70e7fcb1ce18b51", size = 4870819, upload-time = "2026-09-30T14:44:06.884Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/e3/f2298d3bb55e0c4a91841ec4d01b3f020ba8c5fbf15ccdcc6dcf03f97025/cryptography-50.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:85d0d9a31b9098e98534226d5686b47264b95e62ce459dc2e62fdfc809f9fe93", size = 5030152, upload-time = "2026-09-30T14:44:09.443Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/4f/adfc442765721292fff86d314ce385d3249d22db42295c0dd057727b60f3/cryptography-50.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:7afa5a6602a9f29af1f3a2965f831bae7c9d5d597b7cbb716d41ab3b7d89879c", size = 3824692, upload-time = "2026-09-30T14:44:11.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/cb/52eb3770c0d0be2702a98c6e96065ddc0a2877cf0845aa9c23397c142cd4/cryptography-50.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f785f6161f202ab04d8ca194158968798e480ca058943907972da5f12e2881e8", size = 3892731, upload-time = "2026-09-30T14:44:13.485Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/8e/aa1fc533d4546b127b45de8aa024eb5933d23eff9debfe25931e56861095/cryptography-50.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0ecbc5652bdb6fc9eaf89a7d196e20941adfe812f43bc4ca05d9150496821047", size = 4710431, upload-time = "2026-09-30T14:44:15.427Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/64/72bc3f75176e7e406b748a3e3830432b8c51297b38368713df04dc04898a/cryptography-50.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab50ee449bf968271e820086f10a33d101dd060370abc10bcd22279be2656539", size = 4694824, upload-time = "2026-09-30T14:44:17.69Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/c6/62c77550edfa5ca3f14bf44a1e6739b9fa09d6e998a11d97ed8213bccc98/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a9f7355e6fab51f6c369b86fb7571cffa05edee2c2121e0380a37fb9ac1cd5c1", size = 4716967, upload-time = "2026-09-30T14:44:19.661Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/37/cce70f150c432914460157a6ecc161752e053aa5ec0ef3b3f7dc6e31039a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:94e5e9f108ee10471288214d3d233fbfbb492840a8457eb85178d643ddeb32c7", size = 5328676, upload-time = "2026-09-30T14:44:21.744Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/9a/6f2f0304d634ceafdeaf23e84537336664ac419b5d07611675c2ad3f6b7a/cryptography-50.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:241449bf940a5d27309bd317e6f9a2af6932113818bb2b8f5c59ddc7ef16da18", size = 4727698, upload-time = "2026-09-30T14:44:24.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/de/66bcf9244d118663b2e1aaded8990f4640e3d7b7411870a5765f252074d2/cryptography-50.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d8947001be83df1394050758ce0e745dd74fb134eef0a4b5124208dfc3a68c37", size = 4354821, upload-time = "2026-09-30T14:44:26.263Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/e6/db28a28c7b6c676addce89136de3d8db49ea825a8c863472e36e42ead4ad/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4a20ce1e5cb4284a86692fdcba7cb8754185c6b2e5c56fcef3751cf451d3cdc2", size = 4716748, upload-time = "2026-09-30T14:44:28.447Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/96/01546c7f69ea0e2ab790a2e4f0934a4052fb9b388147fbf83c2fd72f1e57/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:84f964e537f916e2cc85199e5a88742e964939b575ac8598b3f9d6cc416cdaf1", size = 5285085, upload-time = "2026-09-30T14:44:30.704Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/01/03263395f74d50b071e9e66daace3f8bef80493e5d410726f2ba8554736b/cryptography-50.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:828d49b0ff5a0e3975865571c5d91dbbdd0d38d8289b249a163e9425413a5e05", size = 4727268, upload-time = "2026-09-30T14:44:32.92Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/94/2bfe8f29ec0cc9c0d99359c4161adf32858e4934b72c6d100d2ac0bbe962/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:deb9fde5c60e437ee4821bc9bc39ff31b42135c27e1dc61ef0a629389c1de62e", size = 4849503, upload-time = "2026-09-30T14:44:34.969Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/44/e80651ecbf0e42b62e2bb5f5768916e07eea72e1297338956a61df361f88/cryptography-50.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8c71ba2cd31fc93748c38e1b613200ff1c2665cbfd5341fe3a61cfde35a1430e", size = 5004057, upload-time = "2026-09-30T14:44:37.064Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/cc/1d33befb3cd7ea7e77d2d73f43f2066471da1b21f24a6156efcaabf6d2e8/cryptography-50.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:78198641e5be9521beea5aa782bb551a58068d10e6eb04c9c680c1b69f2e7d45", size = 3795868, upload-time = "2026-09-30T14:44:39.71Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/49/93f6a6e7a87c9aa68d44d3e1cdb5fe8f60c90d5d2f46acae9a56892816b8/cryptography-50.0.2-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:edc3342adf8f697fc5f59c887a304356f147b397809440ed64e2fa6af2f50f37", size = 4133708, upload-time = "2026-09-30T14:44:41.807Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/75/32ac2a56243d778805c16ca6a32b8f74fb757df7e28d7ecb560afafb59cf/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d370b8d1dfcdf7130178137f6fbee6140774a1acc6cacefc4b42643ec11d0a3a", size = 4956267, upload-time = "2026-09-30T14:44:43.693Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/a4/2c8d734e43d97f0842ee9f1b7b4bfb3d0cf5e19edebf43c2afe6675c2320/cryptography-50.0.2-cp315-abi3.abi3t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f9bd7f90c64fe89253f0a2c05e3c4856072660429ce8831b4235bf29403a67", size = 4966465, upload-time = "2026-09-30T14:44:45.769Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/58/ee288c829a6f41f6235ae9dd33d82fd19b45442b65b4c8a3da36963d9f7a/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_aarch64.whl", hash = "sha256:e275096ea1e60cc595cda2836fd4a6c725d1125108b868be17f53684d164e2cc", size = 4959356, upload-time = "2026-09-30T14:44:48.211Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/20/9ded6d51ddd9897f6b6e81fb9ebea7951d7cc5d6c890b0ed8abf77a51a80/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_ppc64le.whl", hash = "sha256:b13478603dcd0a2479ff8e87e2c19a7d525734686fe3c49542472293a204212d", size = 5548822, upload-time = "2026-09-30T14:44:50.86Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/a8/8df951850d6b31d2a00218f19e2b3f999523437ed7a819df7fa427942fca/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_28_x86_64.whl", hash = "sha256:58a0c478eeca76fe5e07993c5a0703def34a6dc6a0cda4f5564639b33112ffe7", size = 5001199, upload-time = "2026-09-30T14:44:53.379Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/f9/36b3022218ce75b7cdf068fb95f809f9bd0d820e4955ef43b90c255cc7ac/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_31_armv7l.whl", hash = "sha256:d38cdff612d06fa6a32840d5e1b1f7a27cee4a349aa9085d94a67789d6bfd408", size = 4629333, upload-time = "2026-09-30T14:44:55.635Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/72/20f99a219f6af47cdd1cbd978c243b92d71496e168a746138af44ded4f29/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_aarch64.whl", hash = "sha256:fdd28f912fccfec1846a94e2e1e8f9b0012f557f0c46fe4f3eb0d7a87afcf90b", size = 4958822, upload-time = "2026-09-30T14:44:59.639Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/20/196f112617fb08eb4d608a2a6c422373d46f9cc2857f38fc0667033c0899/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_ppc64le.whl", hash = "sha256:cbc8738fd8526d80f35cb3a40d41f41a2e7030bb3b18b09a6778ef63d291c2fd", size = 5506351, upload-time = "2026-09-30T14:45:02.267Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/95/83378121ef3eaaaf71d4b781577ff794acb39b9e1b87a3f156898c8497ed/cryptography-50.0.2-cp315-abi3.abi3t-manylinux_2_34_x86_64.whl", hash = "sha256:e105ab60406787da31fccc883fc0f733af1efd78f0136a4599692c4083a73d0c", size = 5000859, upload-time = "2026-09-30T14:45:05.009Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/f7/70fd7ae4d1dbfa7ba29b02e1b9068771519a86027756510b700ce81086a8/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:6f8700550aa1474a91e5dc07049c46f98b423b5b1ddd0483e0b51362eeeaf5be", size = 5092151, upload-time = "2026-09-30T15:29:15.932Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/be/688367b74de86984bd58d8efacfc7c9e68b89a6a22ced0fb4f38db50254a/cryptography-50.0.2-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:c71be1cbfa5cd9a41ee452acf1eccd82b2c05950358b106ec8ceb83411d1a020", size = 5286120, upload-time = "2026-09-30T15:29:18.309Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/d1/55f8a3f2ef5d1529e16835ef10cf0fe3d559ce237b46dddc440c0bba3649/cryptography-50.0.2-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:c423ab384a46c4dff7217b2ea5ba2e11cffdeab6441acd04cf65a369caf0366c", size = 4111557, upload-time = "2026-09-30T15:29:20.155Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/ad/ac987755d00e1e64273760228d2635ae38dae2be83e3c6e0d3289d91dec3/cryptography-50.0.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0ec5f09541743261e66e291b4a0cbf0fb2997aeaab6d9e9c740b9dba1b58d1c2", size = 3943588, upload-time = "2026-09-30T15:29:22.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/8d/6d585339bedf85d45044c85d8412dac53f2bb6f918e8b7777efba1787844/cryptography-50.0.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c5e67125c7dca78d199ec4e116aa93dbb83494808ecbb8211a2cb09b1bf41dbd", size = 4756166, upload-time = "2026-09-30T15:29:24.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/f1/1c1f6874e8550cfddd4b688ceb38cefb6ed15ceed224d56f133f3d88c214/cryptography-50.0.2-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee247f5c245c9a2fe7c8e2214e295918838e44e00a45a6718451e4004219e767", size = 4749145, upload-time = "2026-09-30T15:29:26.807Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/63/61b15dc1a8de03fe0adbe3fd7608b3ad5c73bf50993bbcb1faaa930afe33/cryptography-50.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dfe9763530994147d9af1def057a5b9658b00e8f8fe8743d144d1e0911c2e454", size = 4763638, upload-time = "2026-09-30T15:29:28.588Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/35/b345bdfa40c9126df1a9d33236aa98418367931b8725f84fc3ae2b98dc59/cryptography-50.0.2-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:58ddb5a8e3179d12f19e4ea34d2d32e9d63a4baa142c875c1eb59f41b7243acd", size = 5382217, upload-time = "2026-09-30T15:29:30.589Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/87/ef344a9e616871f2519c22d6afcda79ddd5d35e9592d95eb6e677608d055/cryptography-50.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f21e8a22c8605750c7af886bab299a363721264061b4ac0a30efb73cfd58efc5", size = 4781387, upload-time = "2026-09-30T15:29:32.605Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/5b/f2fdb13cd0b96f6f932c8627bb292a45f11c64d21620a8e120aee9a3b848/cryptography-50.0.2-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:9c8402a82ea0dc4ceeab793db05f0fafa8ca139ca34fcde5df0f596103c74107", size = 4403790, upload-time = "2026-09-30T15:29:34.374Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/ce/7e4f662b1e3c393513569e402cfc85ac7da0bd3d5435e122a3140219eb2d/cryptography-50.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:0ddc924c04591c2811ca024d62ecad4f7f6f08af8939c211438f48a16bd23602", size = 4764319, upload-time = "2026-09-30T15:29:36.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/3f/86ff33ce34cc0de6847fb96e035a1a760d81652e38643f617c02ad32ef7a/cryptography-50.0.2-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a6557e5f38e065ca9fbdaf7cfc7435ecb1d113aa81a022d1b51921ee7432e227", size = 5338560, upload-time = "2026-09-30T15:29:39.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/cf/6b5c8e2fd9202d98988ab7cb5cc5c991704c4ad55f492ff408e4969f83f1/cryptography-50.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1981f1db4630889b9ef7803fadef12b056f428cb6b85c27ba57b774793b6093c", size = 4780973, upload-time = "2026-09-30T15:29:41.251Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/bf/8d6ebc7dded797bd0f0160d52188021211f011a2b164ef0ae1dac4587465/cryptography-50.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a8701d6b584d76e909e3d305b7d126b41439876a5aaf76cddc67fc230eafa2e", size = 4897738, upload-time = "2026-09-30T15:29:43.106Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/aa/f3f6e0de7e6253b8baa8b2d8fb9d50924fa75cee3d4624bd4bc1208ee923/cryptography-50.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce47f66801c20ec6c6632453bb5960fe38939e9306970b48b3a5a26de7745d94", size = 5058280, upload-time = "2026-09-30T15:29:44.827Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/7a/f08d34ce09d60f89ebd391e2ebc6ba2b995e6dd7552f41820f8085f94e53/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:92e665960f25fcdc73725b9cec7a3824f279ba97a98653afe9ffac2e43668f67", size = 4716035, upload-time = "2026-09-30T15:29:48.681Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/67/e18fb65592451a2acb76e9f2fbe14e0f47a8318b4c5430f1633851d03daa/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eef4c2f3423810b3070ab391f85436d2f8bbfcb286ac15cbc73190b3563b1f1a", size = 4726917, upload-time = "2026-09-30T15:29:50.608Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/28/38fdce17e60f6b825e69fc3b7f75e70a6612759980704697e1de4cbfaf6e/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7c6d0330c472d96f6a6afe24d80dfdf15176c33096f0a4397ae4c60f3dd3be48", size = 4715341, upload-time = "2026-09-30T15:29:52.522Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/b1/d9121a717e0f893c64bd6ca7702614778d7df2a5c309128a002421788516/cryptography-50.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:1ba34f04897fcdaa73f74145c25f3ec146fbd56593853e88adc2e811303c5f42", size = 4726322, upload-time = "2026-09-30T15:29:54.263Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/8b/e6d153808bf353e152abd2fd4d8f09670d956ac78379ac46e60d7efbf04c/cryptography-50.0.2-pp311-pypy311_pp80-macosx_11_0_arm64.whl", hash = "sha256:3dc4fd8058cea1644971207d530e1a03a184a805ffc8ebdddf0599d78a331b81", size = 3873097, upload-time = "2026-09-30T15:29:56.097Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/1d/1271f287ff7170ddafc2aad36260c4eec20ccd2fea70f38455e9d56d427b/cryptography-50.0.2-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:7b75de3c8b3be1cdb1052747c929440c3eea46c1bc2cb8a6e3a48388e9b7b452", size = 3805376, upload-time = "2026-09-30T15:29:58.729Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, +] + +[[package]] +name = "module-bank" +version = "0.4.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyarmor" }, + { name = "sqlite-vfs" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/a6/436bc5ffd88b5bf00afff5632a3f02a1aa679487fff4e0d4c581af973c6b/module_bank-0.4.2-py3-none-any.whl", hash = "sha256:a89fdd7a5c1b8b2b6a073a4208d42e17cbee7cbf60971436b19d236b97b11b88", size = 44987, upload-time = "2026-09-20T09:32:06.539Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/b0/c7453d0b6e2073c3264468b106ee1563750cecc910965e67357e3698c83e/numpy-2.5.4.tar.gz", hash = "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", size = 20866315, upload-time = "2026-10-10T20:05:31.422Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/97/ba2074e92b7befea137e77ea8471e768bbd87c339b7e8c9f5a931949f977/numpy-2.5.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356", size = 17001609, upload-time = "2026-10-10T20:02:40.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/a9/bac826765e971d8e16e2064e9ac7525fd69b40ac17c905033a7f5442023f/numpy-2.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17", size = 12015718, upload-time = "2026-10-10T20:02:43.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/2f/5ea3570fcb8ccd0882bea99436a513b2c85dad8f774a2057849130a8fb99/numpy-2.5.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8", size = 5451717, upload-time = "2026-10-10T20:02:46.169Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/f2/b4fc1bafca03868220b5eaf729d2f21ebd7d7b151c0f9e144fe212bbca35/numpy-2.5.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a", size = 6789926, upload-time = "2026-10-10T20:02:48.139Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/96/8319e2457ae4333c62c815c7006b869a4f60985c1e01024c2f8c6c040fe5/numpy-2.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2", size = 15695312, upload-time = "2026-10-10T20:02:50.115Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/a3/c799c62e19c337e6d3770b08e475887fb30ce8477d3c09efca6b2f0228a6/numpy-2.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a", size = 16727283, upload-time = "2026-10-10T20:02:53.186Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/6b/3604e53fb00314d0dc1b94ec9125a1484f649c0a17480b1f0f0c7a9d6250/numpy-2.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf", size = 17047890, upload-time = "2026-10-10T20:02:56.038Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/7a/e8b58a5289a0d464c52885de47c35a935cdd70c03a4c3ab94a5126416dd0/numpy-2.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645", size = 18485839, upload-time = "2026-10-10T20:02:59.018Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/c9/47094f597015009f310b8c900def59065ef1ff5a6fe7b51fc65ec58ec2c6/numpy-2.5.4-cp312-cp312-win32.whl", hash = "sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c", size = 6138936, upload-time = "2026-10-10T20:03:01.626Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/33/fefe62073dc8acfd0f2b9ed7c003af2f50aa61555e113e6db02b8f79f145/numpy-2.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a", size = 12573091, upload-time = "2026-10-10T20:03:04.349Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/07/161270b0c2eec56e4c905f6d6d22e1b836887b2cb189d3f5820aa588e9dd/numpy-2.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3", size = 10521630, upload-time = "2026-10-10T20:03:06.767Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/14/1c3ee0118a8fce08565a5d8482631608426a33af10a01077fada5dc7c119/numpy-2.5.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53", size = 16997729, upload-time = "2026-10-10T20:03:09.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/8c/b0ea9477fb1f0d4484bbc5cba21678cc9969704d8d7f3f158d1db35f8e14/numpy-2.5.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d", size = 12009826, upload-time = "2026-10-10T20:03:11.946Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/84/6a3d75b3ba3dfe84ac0053450753d1e6d250a8bf80f66474cc46d1fb643f/numpy-2.5.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2", size = 5445803, upload-time = "2026-10-10T20:03:14.329Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/18/bb993f267ca20b376e07092a16793a5b31ed3138751e9ba480011a14d742/numpy-2.5.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959", size = 6786220, upload-time = "2026-10-10T20:03:16.602Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/b6/135bb0953b61dc21c6cafa14b424ae666944e4899cf140e00c2b322a1a45/numpy-2.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988", size = 15689178, upload-time = "2026-10-10T20:03:18.721Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/24/3bd070f3269dc609d8f26b2643f62ef91bb415841c0b294805aaf7fe06da/numpy-2.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0", size = 16718044, upload-time = "2026-10-10T20:03:21.386Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/8e/9d15bd356b0a019c965312b1a3c6a727cac4cae5bc40045fbc12ce4cff9c/numpy-2.5.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34", size = 17048364, upload-time = "2026-10-10T20:03:24.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/fe/9d5b560db964f15871885f2250795d15945f8699e17ef90c0c2ff4c875b2/numpy-2.5.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b", size = 18474904, upload-time = "2026-10-10T20:03:27.895Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/98/d27552990f1bd611ef3e7466adadc78312ea2df63b83aad47fdc3d3ca8df/numpy-2.5.4-cp313-cp313-win32.whl", hash = "sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c", size = 6134537, upload-time = "2026-10-10T20:03:30.511Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/8c/140a40398a66b4471211be1affdb6ed24c486d581bd28d07b7f2fcb69540/numpy-2.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129", size = 12566113, upload-time = "2026-10-10T20:03:32.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/52/01d205e5e8ccb27b2b0b141e801f22b830198c979111b0fa44771438d9a9/numpy-2.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf", size = 10519523, upload-time = "2026-10-10T20:03:35.163Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/ba/005cb5edd580d2f84d7ca3206b92dc17d4388e56e6f87ffe8f2762f83139/numpy-2.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18", size = 17005499, upload-time = "2026-10-10T20:03:37.961Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/49/fee7587c33ee35f7977f9051d7f2023d4e7246d62710c80f20c2361ea232/numpy-2.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076", size = 12019666, upload-time = "2026-10-10T20:03:40.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/b2/c6ce165acffceb15a82c07b9cc77d391f86b3f379ba62911908ae5d34b91/numpy-2.5.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53", size = 5455617, upload-time = "2026-10-10T20:03:43.138Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/7f/dd85ce260a669a89be06842cf355d7353a33e6cfbc590fb8ebb947d88dc9/numpy-2.5.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255", size = 6791932, upload-time = "2026-10-10T20:03:44.874Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/d6/34b0a2b0741386a63025a65a2c09caaaaaad6d0ca95b66cd65c30dd7fcb5/numpy-2.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617", size = 15710899, upload-time = "2026-10-10T20:03:46.839Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/d5/928078d2b28f26829b138b4a6c3980045022fb409f570657a224ae60ef4e/numpy-2.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3", size = 16721710, upload-time = "2026-10-10T20:03:49.489Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/cf/673fd1b8f4cd78eb6320e87ec4c90ac19c095644259e3749853a405c70f4/numpy-2.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00", size = 17066182, upload-time = "2026-10-10T20:03:52.25Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/92/a77b5061b1b3e2643928c37976d79ee173e1b171ed158b7a3c61056b41bc/numpy-2.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37", size = 18480315, upload-time = "2026-10-10T20:03:55.39Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/1d/1486ef3d3fb2279fd93c4c43c1bbbf1ca389a19816696684409f71babaab/numpy-2.5.4-cp314-cp314-win32.whl", hash = "sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23", size = 6185739, upload-time = "2026-10-10T20:03:58.186Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/9a/e1e512ebc948d5b9dd33b08736760f0ebbed2848fd4eda1f553088a6dcee/numpy-2.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3", size = 12703552, upload-time = "2026-10-10T20:04:00.28Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/05/de709a982d7bbcd688a3fad71f002e9ff80c2db39e03ee726609b610f1d1/numpy-2.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e", size = 10803901, upload-time = "2026-10-10T20:04:02.659Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/34/083570ada3bb2a30fbe5d77c8c6fef9141144a15d33e6f793a67e9749ab8/numpy-2.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162", size = 12138695, upload-time = "2026-10-10T20:04:05.012Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/06/1f9c24db48eef0c2d1207e3b11fffb0478e39dfd8c1e1be7476936885eed/numpy-2.5.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380", size = 5574615, upload-time = "2026-10-10T20:04:07.316Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/0f/593fba2e1560e949123bc7d2fc48b5893d56e58cd4bd5a273d2fbf60b220/numpy-2.5.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454", size = 6889383, upload-time = "2026-10-10T20:04:09.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/9f/b799dfdce4e05e80ed4bc815c71ff343a11533b2c0ffc221cae8538cda63/numpy-2.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551", size = 15753763, upload-time = "2026-10-10T20:04:12.278Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/88/16c5f12f86f5ad2817c4d103205131fc6c8acb3d1878af05a1a4f23ec859/numpy-2.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73", size = 16757212, upload-time = "2026-10-10T20:04:14.799Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/4f/a1fe40e18a898e6a5089f4f0d891f0a493eb0574d5b34458f0fbe5aa3e5c/numpy-2.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5", size = 17116471, upload-time = "2026-10-10T20:04:17.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/46/e923a11c78e65c1722e7aaad817c06bd591324174b9d28ce5d31eee4d432/numpy-2.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365", size = 18524063, upload-time = "2026-10-10T20:04:20.365Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/fa/84ab064514440c1f64a1b21088f2c82756defdd05e07c75ab233899565b2/numpy-2.5.4-cp314-cp314t-win32.whl", hash = "sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647", size = 6340926, upload-time = "2026-10-10T20:04:22.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/7e/6cd886876f435b10685db9b9f7eeb70356f99e052116f4e5f11c5792c714/numpy-2.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb", size = 12901584, upload-time = "2026-10-10T20:04:24.99Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/1b/3c1684f6a06f7307f2335fca6e486cb162847fb97e91d65f8eb5cabad213/numpy-2.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394", size = 10891152, upload-time = "2026-10-10T20:04:27.52Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/f4/3224deff3af2bef6bc0b175369698d8cb348f3d91d9bb0286cd5c9eae9e0/numpy-2.5.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179", size = 17003231, upload-time = "2026-10-10T20:04:30.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/75/fee0b8c6d94b44b2fdfae74f6a4ad5a138739589a8aebaec28ce4e713ed5/numpy-2.5.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad", size = 12018300, upload-time = "2026-10-10T20:04:32.519Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/c0/d0b335a499a04b65f532c3f034346ef390f81299060f928492dabc1e0272/numpy-2.5.4-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5", size = 5454250, upload-time = "2026-10-10T20:04:34.943Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/0e/461b3783c03d668052e6a21b01b673db6ffcb7831fd32d9aa5368c1cd426/numpy-2.5.4-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1", size = 6789644, upload-time = "2026-10-10T20:04:37.258Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/02/5dad269b02166965a7b4ca14adaddd75dbee0de42435bfecf561b84ba5a6/numpy-2.5.4-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266", size = 15704353, upload-time = "2026-10-10T20:04:39.616Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/3a/01360c8036822ed9f7aa32189a77d1476567ec1e8e1383522389e4faac45/numpy-2.5.4-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d", size = 16718648, upload-time = "2026-10-10T20:04:42.383Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/5c/b863a2c093c4d6f21a597fcaf24ead0835c09ab16a8312d5a5a8868af683/numpy-2.5.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3", size = 17059053, upload-time = "2026-10-10T20:04:44.976Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/60/ced4f57f9a1258a0af74f17cb0b0c2700b5c67cd6678823c803b263e4df3/numpy-2.5.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877", size = 18477406, upload-time = "2026-10-10T20:04:47.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/bd/0ef22dafaafcc7d4bb3ca26b8d2afbd55dedad8eaba99a8c864e1997456f/numpy-2.5.4-cp315-cp315-win32.whl", hash = "sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508", size = 6185133, upload-time = "2026-10-10T20:04:50.467Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/bc/d2651b155ecc608a77e6f4d15495c11f14f19bb98f8bf0c5b0d38f86dda1/numpy-2.5.4-cp315-cp315-win_amd64.whl", hash = "sha256:332f3378fe077dd850e677ec01bdcc4f22368fb5d50ef10b2c79230b1bf5a592", size = 12703085, upload-time = "2026-10-10T20:04:52.63Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/d2/45e404f8abb26fb9eda12b94012936873e827b1be76f2ee7890be128312e/numpy-2.5.4-cp315-cp315-win_arm64.whl", hash = "sha256:d4cccbbc78717966f764cd3af4fb70276fa01fc7a2688af11c78901fa5c04f05", size = 10801451, upload-time = "2026-10-10T20:04:55.677Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/c3/2ae14e09cfdb67dc187a342e15308a21c15bf4d2071f8079e6aee5fe56dc/numpy-2.5.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d", size = 17097121, upload-time = "2026-10-10T20:04:58.403Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/cf/305ae624ef8a039414317224abe9ec9c2fe7ea3c2e1cf204d43ff6b2ffb9/numpy-2.5.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f", size = 12135439, upload-time = "2026-10-10T20:05:01.65Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/a8/f75c63813aef95827bb2c0d13b12803016853056e8792c280058cdbfe783/numpy-2.5.4-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71", size = 5571451, upload-time = "2026-10-10T20:05:04.135Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/0f/f17763f983868b5c49b4101ebd7e00760bd1769478a6bb6a8de6e085bbac/numpy-2.5.4-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f", size = 6883356, upload-time = "2026-10-10T20:05:06.249Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/a7/8af04c5a79e047996cfa38854dcfbececdd0343a7c933a46fdd03ef6f5da/numpy-2.5.4-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd", size = 15750991, upload-time = "2026-10-10T20:05:08.376Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/7a/648254290d0c504faa8f2d07aa206660c728802c781a6f3fc68ab7cb5d71/numpy-2.5.4-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d", size = 16757675, upload-time = "2026-10-10T20:05:11.393Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/fe/4a8c3cdb0c70400cfe4c5bec42d3099a5673802a95064614b33e07b82aa1/numpy-2.5.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac", size = 17113846, upload-time = "2026-10-10T20:05:14.49Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/7e/619692bb67778702c0e9eb2d468568a7573f4e269386ea61aed01ee4e557/numpy-2.5.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab", size = 18522915, upload-time = "2026-10-10T20:05:17.33Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/b5/4da41c328788f575838f97a098fe8ca691ebc6f6fd73ad4a262ee40b184d/numpy-2.5.4-cp315-cp315t-win32.whl", hash = "sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788", size = 6335804, upload-time = "2026-10-10T20:05:19.921Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/94/6482ddfa3d312490cb9358f375bf2ad56427dbea8769187158e94d653753/numpy-2.5.4-cp315-cp315t-win_amd64.whl", hash = "sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee", size = 12890095, upload-time = "2026-10-10T20:05:21.875Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/7f/c2d1b436b6e7cfebac140c2579a298344b85f2991a2ce5c3615cefb29400/numpy-2.5.4-cp315-cp315t-win_arm64.whl", hash = "sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f", size = 10883718, upload-time = "2026-10-10T20:05:28.547Z" }, +] + +[[package]] +name = "orjson" +version = "3.13.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/72/380b97dc45bd162d23afe5194721ef678d9eac7cfaa549fe2873f7f0a518/orjson-3.13.0.tar.gz", hash = "sha256:d1de5eb04485110c5da4c657e49168995d55e076b1ce60f1a042e254f4186c4f", size = 2732604, upload-time = "2026-10-07T14:09:25.719Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/8c/25b6e2bd4f6b8e67a6b5acbc11a8cff4970e35c79837a24ec7db8732238d/orjson-3.13.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4f66eac85b072092e9941c3111882afd7527bf926cbc717038fa3654b582002b", size = 223510, upload-time = "2026-10-07T14:07:54.539Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/4d/5772e32ebc19d0b76b957a48e69a09546400db35cebe76c21b2c341d1a30/orjson-3.13.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:efa160215c4630836d3b1250af4c7a305acd8239e0d75aff986b8088c2fcacb6", size = 113481, upload-time = "2026-10-07T14:07:56.229Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/6a/5ce6adad2c0cb734cb9d19b7b9d9c7bbdb16c136af453dd37adace806547/orjson-3.13.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e5c8175e1574dcbe446ee654275d353c1d78bbd9a0dc9f209bf35c9df72d171", size = 130791, upload-time = "2026-10-07T14:07:57.751Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/49/d954f02229efb06850a5f9aaf06e77e03046a009d49eb78f499fbd798ded/orjson-3.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78a12d4f8d740cc9ae197f5223682e5e960ba61b4fb2ce5a6a3bb54e83fde28e", size = 129465, upload-time = "2026-10-07T14:07:59.143Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/a2/abcb0647268f334cb85768170b164e4c97f7a2ed5fddd146f79297494d9e/orjson-3.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93c70a5e22bbbbdeafc7b273441e8452a196041d67fd4d9a9c450c66370a8486", size = 130727, upload-time = "2026-10-07T14:08:00.659Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/b0/5672f0505e6cde410cc7916cc2fbf88d90216d667b37907df041a659db06/orjson-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b3bc6b81835ce65f4729ae401607583d41139c6de95bc7453f450f1391d3e7b", size = 135280, upload-time = "2026-10-07T14:08:02.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/58/c223e3ac16193d00c1c3cbc786cb6db47158bff0558c52133e6dd0be7a12/orjson-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6d0684895b119ad167fb4ec05113639dc7f728022deec4756a710e838ed92e7a", size = 126844, upload-time = "2026-10-07T14:08:03.549Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/a2/f6fd98acef1e36b8c8ae0275f0268a0f22bb6a1b436ee4536e1cdaf31b03/orjson-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:7991921c5da527a963b6d4cffd0e4ea89c7e71d4be0c8be1bfe6edb223ce7d96", size = 121455, upload-time = "2026-10-07T14:08:05.024Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/a3/0be3b115907fea61ed340639fb0e1562cd18969bad5b3f486f808197aaff/orjson-3.13.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:948bad47f2e2e43527f14248364a0e5dee26dd3184691010ec4a1ebeb0fd6771", size = 223146, upload-time = "2026-10-07T14:08:06.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/f7/665935edb16163f8b764182e29a30cf056947a66893ed032191e5f01eb3d/orjson-3.13.0-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:1807c2fa49d393c7ee95fd1ef1b39cbb24aa3ccd81f30b84503ba59407666960", size = 123546, upload-time = "2026-10-07T14:08:08.324Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/ec/e7cde480c0e212594d17ba2b2bd210c002052e9147fc1a1aeafaabe722fb/orjson-3.13.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:637dbca1fccffe83780e806fbc0f17427c0c59bf822528eb0acc8f0aa9f19acb", size = 113290, upload-time = "2026-10-07T14:08:09.816Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/36/59/4455fb11a297af73611dfc437f0f89456220227ed1cb1544a5a0ee9d6c03/orjson-3.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:554948becd1110123ef9f6a6e1310fd92b2d07d2cbac6dbf65df3de75702e736", size = 130342, upload-time = "2026-10-07T14:08:11.253Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/80/0eec5fbde2e52407646b4cb3118f63175bdcee1e2390c2759dc96e0bc62a/orjson-3.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9d9a101bd8dbfad112170f009cd155e52bb8c936468821a0d03cbb96c0e426", size = 129138, upload-time = "2026-10-07T14:08:12.814Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/cc/c0874f13819ae346d69ca00d074d464710b494abd4442bdebf75ac404a98/orjson-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89bcf2d4bc6c9a7e1763c8cf534f38712e66b76a0fefda7fb7785462f0d635e4", size = 130518, upload-time = "2026-10-07T14:08:14.392Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/ab/140dd9adff84bf64b862c4fcfe2d055af6014d5ba03a075f95c9addb2ec7/orjson-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a79cdc4934fe81f593072c94e13da3095e9d41c2deef8f6ff2901794ca1c5042", size = 134924, upload-time = "2026-10-07T14:08:16.09Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/0a/e8f6deb032b1d98a39043cf99b863d8b9e842e2ffc2d2067d2e2a88c18e4/orjson-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:50a5202ba388b3850ba24437951727d3aa6d79a21964a30ae8dc6a059a5fd34c", size = 126704, upload-time = "2026-10-07T14:08:17.439Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/cf/be64b99ff75f7983488390d4ef5df72115119770eed295691c0a715d492a/orjson-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0377d6962fa431c93ecd78fdea771bb62ec545b24ee0c5d4e32acf2260af259", size = 121287, upload-time = "2026-10-07T14:08:18.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/ab/1b8ca186baf3420f12db1f2819fcc5f2cae69e4cf051168501726a64c0fa/orjson-3.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:1d84820b2ec4ac975cba482214032de5b0dbdd17046170c98e642ef9c4a4ee4b", size = 126314, upload-time = "2026-10-07T14:08:20.452Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/17/ed65f84ed5ed6a1e06eb628611b4172e7480fc4ad92594856751a6363cac/orjson-3.13.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb8644dc6d705e1269ed2842bf4dbe2b4e50d670de503bf79d5cef3a5148a4c7", size = 223063, upload-time = "2026-10-07T14:08:21.979Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/4d/9332eb96d2e379384be0f211f543835eebc81f460c9403b84abe1294c431/orjson-3.13.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:6ff2a2c67f35202f7d823753d38ad371a9b7fc297567cdfff4420e763cb9f6f8", size = 123364, upload-time = "2026-10-07T14:08:24.026Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/06/558456b7da27e974a8c9ea09117b07119f6fa131cd62b8b9ecad9eea94e1/orjson-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:65c4e0e106ccc7265b488385659117a6805c37d042f737558ecd68aa0c67ad8f", size = 113199, upload-time = "2026-10-07T14:08:25.476Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/f2/1187a9c09965620348262ec0f406868f6d7c234b2e9b5ee51020bdde5748/orjson-3.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:fbbad6b9b1da43f25c1f5b20cd5a268e028a2fc95d5a8d1ade6059973bc71584", size = 130329, upload-time = "2026-10-07T14:08:26.877Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/07/5d1a151bc11600434fe799e73abfc6a4d463d02e149a20e47c59d3a985ae/orjson-3.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d895cf7bbfd50ef34bb63bb727b14514f259f3e3f8dd010783bd38e864c6e", size = 129072, upload-time = "2026-10-07T14:08:28.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/8c/bb07c368abbf4021c4cd01c12edb526e00090f7f750ff1b88da6e6b6c7a6/orjson-3.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceadfd314bd238f584fc229a4bbaf0e573597e7a026dec5429fbf29fd66c641", size = 130612, upload-time = "2026-10-07T14:08:30.041Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/8d/4b66d19619ed344ac000ffea7c006477d0061d580646e736ef0e203759e8/orjson-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b74c30e56346aad067937d766846ee74c231d1d18aad3f324e9b9261de3b2d5e", size = 134632, upload-time = "2026-10-07T14:08:31.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/88/f8221f6593e37eb26ec4706e185b9ac6f38ff0c8f7bad5459844031ffd2d/orjson-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4329c19b8a25693f60a77b867c9d2a3ab637b20e36f5b7bea7f5acb492b44b15", size = 126807, upload-time = "2026-10-07T14:08:32.914Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/9d/a1ca7321eeafd7d72e174cdc388cc96301f41516d863e7b1f64f0a1735be/orjson-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:b571236d8393edcd3236e07423f762bfcf571f852aad667a3bce9e7b755e0790", size = 121538, upload-time = "2026-10-07T14:08:34.325Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/a0/1f19b4779c910104370932fceb9ed436b47ac077f297db74008062525c04/orjson-3.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:8594956a75223f657e1e68c568c0eeb3dd145f02cd6b78a47fd9a8095dbc4eae", size = 126259, upload-time = "2026-10-07T14:08:35.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/56/f8ad2546150168858c16915c452b00eecb79597597524d1ad6ae14ad4eab/orjson-3.13.0-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:64e8f345048d988c8b68d3882e5d41028fca1219a9939b32e4a77be34c8ae8e3", size = 222892, upload-time = "2026-10-07T14:08:37.495Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/19/725d23160b2471a3f27026c55bb79af34687652d8be8f5f583cee5dcd42f/orjson-3.13.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ded33b972cffdaf4ca0ac917338ab61d2bb10d68987dbcae641c313fbfdbf499", size = 123319, upload-time = "2026-10-07T14:08:38.989Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/08/e5d81a00b22c73dfcb60d80da3bd92d5a7684346593536565f184dbae3c9/orjson-3.13.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:45e34deb3437509f4ec9888dd9ee5dc426cfe21be10f1eb4ea3a9e4d33034f9e", size = 113196, upload-time = "2026-10-07T14:08:40.383Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/78/fda6117c69a43e470b1e9dff38dd8c5f0bc6fd8a47e4d4561ab023039335/orjson-3.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9825b954155b345c4759f24e5f8d652b9aec2261bb5d4e1abe06bba0a1200535", size = 130245, upload-time = "2026-10-07T14:08:41.878Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/31/d0cfebd456defb234414795ae7599696bf124843dfe077d0c9ece0c93554/orjson-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b081f0e7b600ff24513dec4ca75507fa05e904607847e386e8310d5b7b96b6c7", size = 128981, upload-time = "2026-10-07T14:08:43.716Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/46/f8d83189ff5b7b2ff225a58c5908618cc4e86afe09e65d17a30ac68c9da4/orjson-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbed5f4c4b88d94bcc36115f4c3bb3aa25da1563a5c3328aa3acebce2b083040", size = 130370, upload-time = "2026-10-07T14:08:45.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/6a/d6344c305003ea826b3fa0482645a897a3cd6d477ed74e1fe15d3322cb23/orjson-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9b61676116f755126b90e740a9cff36b91562f47ec330056cc88cc3b9f02f4b", size = 134595, upload-time = "2026-10-07T14:08:46.63Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/52/d73fa44f88d53e02d10de1cf77c16ed13204ff5bca47e1692da6b406619c/orjson-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ef75ed7e81dae34a3649f82df52cd85f9ac839a7d6ec78ab355b33b3b27ef7f", size = 126513, upload-time = "2026-10-07T14:08:48.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/f8/bcfc50b4ab851c4f9c0ee62f52bf3b28f0bcd0d9fe08e0ad98d4585148db/orjson-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ee06e53b998c71ce3eb93b86222912fdd9dcced685ac64d4525d36fac338ea4", size = 121371, upload-time = "2026-10-07T14:08:49.549Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/7a/d6927845712ec2b1e89263cd12d7203531db185dbad67f914226f2fca156/orjson-3.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:89efecad02515df7f318d0613b5dfd6d2a1acd323a2b8294712789a715945525", size = 126134, upload-time = "2026-10-07T14:08:51.118Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/10/98b5a3cdc086abf78d8cd20bb0cba124485d4b6a745722197bd209d967a5/orjson-3.13.0-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a7bfc7db961c7d96cb75889dc6a1e4ae1e91d87ee61da564f582bd742b8dfeef", size = 222889, upload-time = "2026-10-07T14:08:52.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/7c/7728c5280ab5202f4891ff4b0b96e2e1dbd5520dfee53edf083c54409a64/orjson-3.13.0-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:91d933e668ff0ffe164d7c2daec36beba6d1ce7fadb71538fbe142a71f8a1e6e", size = 123312, upload-time = "2026-10-07T14:08:54.25Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/a5/d9a44321e6f66c0f64b45be587395f87ad94cb447bce7d92286f6b97d46a/orjson-3.13.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:6c8bfe728b81b0fd58a3c7f3f9c5a113f87f2992c9948e0f28707aafd737c0bc", size = 113146, upload-time = "2026-10-07T14:08:55.803Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/da/d95c80d413f288feb471e16d82e5c1512d2439728e3bac917d058c31f098/orjson-3.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e8e05549f3b30f9d8a8e28c5aba11cc2a4b90b90961ec685ca58444b0815fc09", size = 130348, upload-time = "2026-10-07T14:08:57.31Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/0f/36fdfb32ad1852997bac00e3ce52c7888d8a1094ba9dcdcbb22fcc6b953a/orjson-3.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c749ab3ac30b5ab1ffb7677f8b92eacfdfdc5260210baa398f845bc3714c05d8", size = 128971, upload-time = "2026-10-07T14:08:58.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/de/a82acf93bdcca0c79ccff25ef0c6868d24ccbc2e72f21fae39c8cabce4f1/orjson-3.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58a9619d88f8818d9ab6b39d70d203789457ba13c1ed5d274f33ce9ae7e81a36", size = 130359, upload-time = "2026-10-07T14:09:00.412Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/ca/2bc4f7697cb9f6897bf61aca11803df096a5d971bf69ef5538b243bb1fa8/orjson-3.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2715c4808d1571029ed18fd07a82140bf3ba7def0dc89f8d015c416e3649bf87", size = 134583, upload-time = "2026-10-07T14:09:02.047Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/b3/12b1af9b87ff9fa0aaf4e5724c87672b30bb5de76f275f7fac64e8219c1b/orjson-3.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08bf722f923d2100bc5e5a5dcf72c656db557049c1bea26582fdd5dd9d5395a1", size = 126500, upload-time = "2026-10-07T14:09:03.863Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/ea/cf257fc8a7f4b18f5677c22b3a9673a1b51d4b7161f25177ed389b76560e/orjson-3.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:6adcaa85d79977659a448b4123a88eb33511a11ed2db243535ad7ea88a6668e0", size = 121378, upload-time = "2026-10-07T14:09:05.375Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/0a/9f4643f849e9918eab11983b83928af3aac14bedb04002e28e885ee1936f/orjson-3.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:83705c12b4afde10c62a5dd3fe6fdb21b7900bd0dcd5af1c85612ae94d0ee590", size = 126123, upload-time = "2026-10-07T14:09:07.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/15/d265f2b556c0c7c0b30ea830316d6e5af5b85dde08f234a1ebed60fab386/orjson-3.13.0-cp315-cp315-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5ef4d4157392a0439b74f7e49e5636b4ea43d9616bd0884effc0195fffcaa2d5", size = 223305, upload-time = "2026-10-07T14:09:08.84Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/97/781be8b80a33b8171b3f5acea941af47182c8b4b5827c2b7c3fea706f21c/orjson-3.13.0-cp315-cp315-macosx_15_0_arm64.whl", hash = "sha256:84d87e322e1674408f85adea63f11aa19201eba082755aec20ebc217f493bbd2", size = 123515, upload-time = "2026-10-07T14:09:10.792Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/68/011bb98fa7da7b430b363db1bb7ef9160c438fc5c43e7468fb593c220037/orjson-3.13.0-cp315-cp315-manylinux_2_39_aarch64.whl", hash = "sha256:8c2ac5c09b017c484df1b4c68b2cf250b4e8ba08204cb58e7cd6cbbc71a9c902", size = 129222, upload-time = "2026-10-07T14:09:12.542Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/7f/d96fa2aedaaec14c095ea9cd48d2158fdf33c0f4fd6e7a598d899d536b03/orjson-3.13.0-cp315-cp315-manylinux_2_39_armv7l.whl", hash = "sha256:51d11525bc3ca736fa97ce4e4c7da9999cc00bf261522bede43b4e7531bd7965", size = 113152, upload-time = "2026-10-07T14:09:14.059Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/2d/ee77aa685c54bd920a1f0e2936986b46269adb0d72bf5098c2c694dbeb36/orjson-3.13.0-cp315-cp315-manylinux_2_39_i686.whl", hash = "sha256:ac81530647c3423107cf61c3481e91f57134e9ddfb6ef83f5150ccbdcbc3a3ee", size = 130749, upload-time = "2026-10-07T14:09:15.835Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/eb/3411fbfdad61b3f3af22343b5af7ed5c8a1679e35f442e8f1b229b33040e/orjson-3.13.0-cp315-cp315-manylinux_2_39_x86_64.whl", hash = "sha256:0526a3456db67b264c6d661b5f090077f326b6cd074d0ef53a72763595dec5d7", size = 130471, upload-time = "2026-10-07T14:09:17.463Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/71/abdc2b8c70b8d85a6cb22f404da0f52d7d712f9d49cda039a0cb1adcb973/orjson-3.13.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:dd61e64802d51d1e4f16531c64536354fc3bc67932dc0cff254044f72bf0f187", size = 134793, upload-time = "2026-10-07T14:09:19.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/2e/1c13552d8b0241083116de02b2f284ee38501ef06ebfb79893f741538168/orjson-3.13.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:c5e3ccaac3106e8fa6e2f2f6962449d7c757d7b067e41b395a19d6f0d6cec892", size = 126711, upload-time = "2026-10-07T14:09:20.645Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/f8/d4ece953a519d064cf690adaa68cd389d5b64fd261726334841b32978d6a/orjson-3.13.0-cp315-cp315-win_amd64.whl", hash = "sha256:7804dd1d6161da0e53b284c2aebf20f23e78eaac617300803e1467d1828d987f", size = 121496, upload-time = "2026-10-07T14:09:22.359Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/cf/f691388c4a9bc4af7dcc1648c4b40845869908b517d7c0009d005c7d1fa1/orjson-3.13.0-cp315-cp315-win_arm64.whl", hash = "sha256:f5c05a8fee59309f537590a1ff12d3c1009c485e96a50a9ac60dd085c09d0fc0", size = 126260, upload-time = "2026-10-07T14:09:23.928Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pluggy" +version = "1.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" }, +] + +[[package]] +name = "pyarmor" +version = "9.2.7" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pyarmor-cli-core" }, +] +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/86/1a8937731f28e0fe6fe603da1fc4a8d27525dcd26f7b723326e8e83a562c/pyarmor-9.2.7-2-py3-none-any.whl", hash = "sha256:7cf7659f822b4da2434b2e19c3adc9f17868c5610d0e53f4dc2321ff9eefbf5d", size = 3074479, upload-time = "2026-08-29T04:16:04.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/0a/a95f56b2ecb352080d9f36d3ace313cdcc2b6662cc66ecbdebd137833f8d/pyarmor-9.2.7-3-py3-none-any.whl", hash = "sha256:7dc03740cd31ab38d6064fbd822a1b62aae7d2267aa1dd99be3055f2107b1909", size = 3075842, upload-time = "2026-09-03T08:02:02.361Z" }, +] + +[[package]] +name = "pyarmor-cli-core" +version = "8.1.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/32/899852afcfb2f520bc784ef55f49bd380ce8781e4c517e347eeb2b9f213b/pyarmor_cli_core-8.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8a97de083597e2b425066376f56ff351c9de9172360cb517a93821d969b4eee7", size = 510412, upload-time = "2026-08-26T04:40:44.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/14/e65e849f11a891ef0543225c2a76bde4bcd8a447d37d2d5bd84685a08acf/pyarmor_cli_core-8.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcb9a1b5b6ff39aa11181ec49ddeff83565d9ea2a5b3ec64b80f70e91ffc0809", size = 402425, upload-time = "2026-08-26T04:40:49.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/af/7a081f160a017c112111aa626a383931de75e8519577012a5c05b39defa2/pyarmor_cli_core-8.1.2-cp310-cp310-manylinux1_i686.whl", hash = "sha256:a15145116399b7fe49d41f853e772994ab3000a590458a4deacbe42fc99dbc0f", size = 481586, upload-time = "2026-08-26T04:40:54.196Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/8e/74e14efca8ea8c29a1c1c821d8224c81d343e2ae2f00e6d791e5fe16ab72/pyarmor_cli_core-8.1.2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4e443538421b751796ae325b985e52a242e6b80bd6546bb29f4a90c761dc450c", size = 452540, upload-time = "2026-08-26T04:40:59.213Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/98/c657485fb0f0a23ef3cf5697be1f736bfaac7c17a67b32261f8e210eb274/pyarmor_cli_core-8.1.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:01508189fa9f7067c3ee1f682db862a6e3e35614c5db65000ae027465f73b081", size = 598865, upload-time = "2026-08-26T04:41:04.637Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/88/6e8ad7d885f016a227153ae7b329d63f8a170aa330e1880645bf5f034dc7/pyarmor_cli_core-8.1.2-cp310-cp310-manylinux2014_armv7l.whl", hash = "sha256:a98df839f50d3a219029dd237c44d8624a02f618bedc387d91c37fc163cb2237", size = 994978, upload-time = "2026-08-26T04:41:09.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/c0/3211b5629f6b1acd6d929c14194dc061af2d12753950530a399ed1981aff/pyarmor_cli_core-8.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c6645667885a76e607acf19ed5296d4bd5a075a48d0da1aec6c7b9428c27f1d1", size = 643979, upload-time = "2026-08-26T04:41:15.33Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/cd/6635530492bd4c0d0132464b2897313f47997057e4c1cd1f678de4602100/pyarmor_cli_core-8.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43b53efc5f7fcc15a7f97de31b7bbd5ab2ff547fb4a8c27683f54211a6d3faa9", size = 439781, upload-time = "2026-08-26T04:41:20.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/65/0e956ca9d496c029152815e1a5af96435f578af6f4528ee1570173915f4f/pyarmor_cli_core-8.1.2-cp310-cp310-win32.whl", hash = "sha256:f925979a0ff416614302254280dd77091ff46e13c31e5811c11ef17be4f50825", size = 454725, upload-time = "2026-08-26T04:41:26.113Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/da/d1072582cf4b264dc777384e768c1a0d0a139c04d1172ccfb2d834953b70/pyarmor_cli_core-8.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:302c7390566193e0dafc690406b55ac083f5bd26ef5d1a16196e43c1b0c6536e", size = 414485, upload-time = "2026-08-26T04:41:31.425Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/a9/3d4c59c43efbfde6a197b11c489c82f83052dcfb3ad0f17b89f14523ae88/pyarmor_cli_core-8.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:0c697fd88cf7b5d556159bb68e5ac8568aead69bbc113aa8ffe7c539d34245b3", size = 511691, upload-time = "2026-08-26T04:41:47.198Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/37/f2777646cacd6eef89d54712d79adafa9455ff2bca8127a5700a4e958178/pyarmor_cli_core-8.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51fcb1c4713dde29acfd18404967365241720d7a3ccef044c643f7d3b3bfb85c", size = 403711, upload-time = "2026-08-26T04:41:52.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/c8/882f077406942798efe109f6d637a81d9cbec1d1eaed1cabc36cb5a361a4/pyarmor_cli_core-8.1.2-cp311-cp311-manylinux1_i686.whl", hash = "sha256:a7b1118a90b2b9c93b6a4e94ada6362ed943e9a9f1090eb399e7828fab108c61", size = 484046, upload-time = "2026-08-26T04:41:57.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/3d/0ba40c463739c8775c453754f1d5430c2beb8e040a5ec38092a5a008e48f/pyarmor_cli_core-8.1.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:72b3aa570bfb8237314afa6fdeb263b636c747665bde6870c721f786cc9515cc", size = 451888, upload-time = "2026-08-26T04:42:01.962Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/c5/a73071878e47ed4742214bd29e218ffb4488c3e985fbfa98aac654e462e9/pyarmor_cli_core-8.1.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:1f88c81f78634f343ce8238cc950363cc63a87aa588b847538102fb26772c837", size = 599086, upload-time = "2026-08-26T04:42:09.227Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/fd/331f5b07e8bdf24fd31ab1f4e207fbf12959619dfb997fd80f83a77c75d7/pyarmor_cli_core-8.1.2-cp311-cp311-manylinux2014_armv7l.whl", hash = "sha256:ced47b5a154942ca55122d807e1f8ba081b524363b5ebfdeef4364f7edb5a44d", size = 995532, upload-time = "2026-08-26T04:42:14.915Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/54/dd2534fcb8002b083b59fc3c4d4001b0b5e57a39b1c17cf7dee61ee11927/pyarmor_cli_core-8.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0507261ce7871223a3c0ae9c8d51a3419cdf90a0ab1bfb6c246c99da08417ee9", size = 644519, upload-time = "2026-08-26T04:42:20.288Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/3f/53d6f5fabbac040a6b2f7ff8b20f7a94fee7de761c4478cc6bf0ac76274f/pyarmor_cli_core-8.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:86350fea35a923826725867250de447a37f63194dbb2c3e6a345961c3be19124", size = 441398, upload-time = "2026-08-26T04:42:25.21Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/8f/ce7d1b62ae29919ddc229766c0971bed7c9d917d9c3bb623db1517c1eb1f/pyarmor_cli_core-8.1.2-cp311-cp311-win32.whl", hash = "sha256:9014186dacba8cc3fe8d869e01a3e4d8121f05dbc4bfce3fceeb1cda2b1cb779", size = 455042, upload-time = "2026-08-26T04:42:30.33Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/75/c5d0e8070538093c1570a8417b53166d965b7efda77340b999855a374d73/pyarmor_cli_core-8.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:fc6c6798631d4369bdff8249b264755b21dc05b94725d5b1351c03855d69fe12", size = 414901, upload-time = "2026-08-26T04:42:35.231Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/f2/3dbe618b9a2a21e7eb731b238c297cfd4da8301d2d8cf79ab4ed89537c73/pyarmor_cli_core-8.1.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0bfabe4d8db7510a09419a8cbf38d1952759ae48044854666b2a9e7d1e0bfc16", size = 515507, upload-time = "2026-08-26T04:42:40.407Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/47/de7e83d98d15882810bd865ca782101cf7c29b69ea0db42ed5e3b3f2a0a5/pyarmor_cli_core-8.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce0275112a7e502b18db4a67d292ee7a764dc48cd422334c02377b797a2ce91", size = 407727, upload-time = "2026-08-26T04:42:45.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/ce/326cb3f7ed988c6bfd8c7365837d2bc99f1105d8a5f029ca591201a962ac/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux1_i686.whl", hash = "sha256:d68e86e01b47f91c1f7b97ed7ff4079159d813624873e4e5d5c660a089f32432", size = 489298, upload-time = "2026-08-26T04:42:51.84Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/71/ce177e470033c27d58d519d5f2547edd09b3415ffaa3ac9fa60fa5c7b846/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:05212ececa5195d1c72113528b9379f691d5f7671ef3c0b5e1c72908c7a46ae8", size = 457692, upload-time = "2026-08-26T04:42:56.947Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/8b/3f68f677ddadf7e3730a91c98de10f048146a359e76542207eac7744b621/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:2cf87045cc73fe096721d0e7fd972849bca84da8dde3ba478de75b4abc85e3fe", size = 604351, upload-time = "2026-08-26T04:43:07.276Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/f3/1f26f562840586fdcccde66c602f44f3695bcda9b23efaa39227f39e6614/pyarmor_cli_core-8.1.2-cp312-cp312-manylinux2014_armv7l.whl", hash = "sha256:18adeee02d6d829741d28a2ac7a4229020583c491eea475d4164b1d311cc540e", size = 1001047, upload-time = "2026-08-26T04:43:12.975Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/4b/50a847ef3a0b8fa12d1891cae2383b6efe04cfafef5bd2137ab4468cb29a/pyarmor_cli_core-8.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f5f4c573a435619273442f1b9f8938d31c7d0f26100a80453e6d0ae054d1e0a2", size = 649548, upload-time = "2026-08-26T04:43:20.541Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/2d/0ca6b9036195971200975c69c0a65a2049304458ebfa73caeeb860a94414/pyarmor_cli_core-8.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35ac4f6ba07b20391ab9624c70bbc133537560e227579de6db51fe45535ab93c", size = 446449, upload-time = "2026-08-26T04:43:25.667Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/de/6969ae804814dc1842f363847484dfb05664c4ae2ecb60ac9a2ef69a1e3c/pyarmor_cli_core-8.1.2-cp312-cp312-win32.whl", hash = "sha256:609f5a63d0135d3723203f859e89f7a28c45e09474250705e995d59845d8c307", size = 459858, upload-time = "2026-08-26T04:43:30.669Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/b7/2f3de91992fd22f7d62d7f8c59cc47898e6a260e195c1131da1e7b6a9c25/pyarmor_cli_core-8.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:952558497cbe06bc12beaf014e2c53b20203e4ddef3c8b6028744abf72e14505", size = 420611, upload-time = "2026-08-26T04:43:35.533Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/7e/3d79d27c130736326503abc0ef358e4fb1b102c4a21f2e9affbfbdbd25f2/pyarmor_cli_core-8.1.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5dc5cbf1089a503544cadd63baf4a65953983459718d4e1aeb2954a80e8f73dd", size = 517370, upload-time = "2026-08-26T04:43:40.387Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/64/bd3419284798569f60ad9db0766cb7c0e3a9ea882489bad0fa8c5a754daa/pyarmor_cli_core-8.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b068b77395b3f74d4384e3c18d4684c56a4360c8fde848692444d5e6dac8d1e", size = 409769, upload-time = "2026-08-26T04:43:45.45Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/af/77fa373c09063b91b13579c7321c6e1a5f6f6be1851b3c72ec3c2a4cad32/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux1_i686.whl", hash = "sha256:405405e44855307ee26264ea253358009dc73ad08440ba89935efa4fde87bae4", size = 489760, upload-time = "2026-08-26T04:43:51.707Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/8e/b411da54be4bc2143dd35dd6b3423376c052906eb7d0cb63c58dc01a31f6/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:66d6d9c15aabb52f87ff98d4819a5ecdc32b13100348be2872c373354470d8d5", size = 458108, upload-time = "2026-08-26T04:43:56.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/69/82cd83355e1470eb5f7fb505bc8ff85a8f8caf67c0da02de86beb31505be/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e77c780dce6c0a50824927a03e0f9c39c642e1ca8fa1671c70c03b01c2e90cef", size = 604916, upload-time = "2026-08-26T04:44:01.603Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/ff/8be81c5d943cc14b8bae44780249b657565690bea65ff52ee692548e8bc0/pyarmor_cli_core-8.1.2-cp313-cp313-manylinux2014_armv7l.whl", hash = "sha256:49ecb1d3f76bedee50be3047d18ce50609db16bff8702a7f096531503d737785", size = 1001331, upload-time = "2026-08-26T04:44:06.741Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/6e/cc65256fa20d49dc704231530c693122e31c8c18783b2f7985624d3c2c1b/pyarmor_cli_core-8.1.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9b1f648743a32f417bf908acc5800711175edf411fa24bf729a6d2aeb24a2617", size = 651236, upload-time = "2026-08-26T04:44:12.025Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/69/5c6bf32fed77aed5b50aac0b97b7df0f1f72b3343e9bef4a014392e85cdd/pyarmor_cli_core-8.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d890021c640fb01dda0b7a4c8580b12e103bd349817bc86f01c04f3c9358cae1", size = 447212, upload-time = "2026-08-26T04:44:17.021Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/b6/58410290dae04ebcf85aa3084d31ba07930121a44ccc7714c738fdfe6810/pyarmor_cli_core-8.1.2-cp313-cp313-win32.whl", hash = "sha256:b3f2d55db32e6453a3e070fadeef827c147f21106e801a7f0688158f665c0726", size = 460848, upload-time = "2026-08-26T04:44:25.4Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/e8/d748641756c7a3353ce161b3316eae46fa5f7355a4208b7a3488e18764b1/pyarmor_cli_core-8.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:c61a86ab3f3a85550e148dd874e1ad2f2295870ad3204b1d92287c3fded3d087", size = 421322, upload-time = "2026-08-26T04:44:30.229Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/ce/bbfe583c3e7ee7b76b51173c0ba9a073b222cd3a3e7d28b13e6a5373d5d9/pyarmor_cli_core-8.1.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:3416df1e56f10a12f42c124ed4c29a849fca1fe33bbc70e725fd0bb395d2301b", size = 522725, upload-time = "2026-08-26T04:44:43.116Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/16/9bbc4f0591bf28b481e079c1c38deae5fa78d23b4eade36f4fa6bcdf1956/pyarmor_cli_core-8.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c5c06fee0a63cc8c3326f6f7e44a3c39d88dfdcc64465c50e584e4fdda6c4da6", size = 414438, upload-time = "2026-08-26T04:44:47.886Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/26/e24b62ad18e4c8f18abdae1b12915dbdd9a7e08118082a7f427a7922a93f/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux1_i686.whl", hash = "sha256:0b2a1d47555d750f6c9cc9a7883c12c4f6b81966a85815ba309ae887a0866209", size = 495370, upload-time = "2026-08-26T04:44:52.993Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/43/5d9dd141e4bd463092fa2c1b2da524f23e7f78d1a5180664ea9810ac7a85/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:27b5246ec4ef12cf9190da8d0caf4c69ea09c3082ca3b9317b8b27eadb665044", size = 462068, upload-time = "2026-08-26T04:44:58.693Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/91/84110437e6cef14eccc4555ddeed0964e36a4d33cde2c6827dbd059d743a/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:f83c6a2062aaba0a905c9274e53eb89a3054cf5c6d53ec25873dc8e8991408a5", size = 609512, upload-time = "2026-08-26T04:45:04.128Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/bf/bef14c1746e0a1ada92e066c899cbb8fc20878d8eeeff9c2893eade22333/pyarmor_cli_core-8.1.2-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:ed08a46cc52e9c1d22f1472452f7d0b02eb1cc3d8bd3494f20ecbb20179c8696", size = 1004951, upload-time = "2026-08-26T04:45:11.196Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/14/677a5246b700a30b541c4d3c83b654641946a5dc11ba8bb9efc3f647e973/pyarmor_cli_core-8.1.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6ee50a3307ad6c69cdd4873a32591fea6791cbcc040f9271678b6597d9214dc2", size = 655375, upload-time = "2026-08-26T04:45:17.309Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/26/8b33b3f3808f792fd35847346814709a722bb981096a785aa0c8dac35deb/pyarmor_cli_core-8.1.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b18735b1f3d26a6b8f3776d82a4457596972f7a2f986d729740b6443812b0f62", size = 451004, upload-time = "2026-08-26T04:45:23.568Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/ee/2b8260c1fafd042282eac62dad02285b2088049e6e1a8a1771e87891f707/pyarmor_cli_core-8.1.2-cp314-cp314-win32.whl", hash = "sha256:1baf50f8efde7817899313092de743129da063db2265c66c7ac06003a7b7e8bb", size = 465410, upload-time = "2026-08-26T04:45:28.365Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/d5/357b7850aa061eddf14b5fff3b0230eff65d62e3005e008b28f4807aeeed/pyarmor_cli_core-8.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:84a398bc0873890010f812b3190d4518bb393d3c0c17d7b2cdf2f547e65ce119", size = 424558, upload-time = "2026-08-26T04:45:32.967Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/8a/873ffc711607601460fffbf91895c04b754ab934bd29e8cb4de6c8cfb785/pyarmor_cli_core-8.1.2-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:794bf98430e6ac28ff2ab61a9771a9c30736182c098eb930d2b31d8cec8782c0", size = 522825, upload-time = "2026-08-26T04:45:39.577Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/3c/8f5b61f254324d308ba6164ee11c3970974356d0928bfaaed76d0c2a6cab/pyarmor_cli_core-8.1.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:d1bf8f4d9e66af72dcbd14dba919e64e3f415227d159b97813efd728728e8ca8", size = 414438, upload-time = "2026-08-26T04:45:44.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/33/f8a746bf9dfb634ee7b189668acd58eb730c09305be86fb70149c1c2c442/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux1_i686.whl", hash = "sha256:ed12d37d5be2d8f03248541285bfed0a5545495d7e6d8352652e40f3ff320725", size = 495326, upload-time = "2026-08-26T04:45:50.413Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/92/5a1e5fa2fecd0819844678bfa1b3ee3a1358922d47d4925b4f3d4d1f3f5d/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:3ba57f26a78a5ef82fc8b1a79176de9c0c32082e2874b3e71c32178cc3028d75", size = 461872, upload-time = "2026-08-26T04:45:55.631Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/45/770ab7dbf55e6af6b38511f14c691a26bfe60940a2e1138ab66f986fe44e/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:2f0b11a99e348567dc2f7d7ff32fa4c4aec5656db5369eaa8417422c654b9d00", size = 609345, upload-time = "2026-08-26T04:46:01.045Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/c7/e4ef2d2c43dff6ead90fb212605725646234b3d440fc38e49cf8d349b1d3/pyarmor_cli_core-8.1.2-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:135388b7b20054be4111f78c21fb171f7718bc763edd2395bbf00760d3b059ad", size = 1004824, upload-time = "2026-08-26T04:46:07.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/11/77da23b337acb01a6a2dcb372eea995bb8bcba4cdd7772cefccb5daf4eb9/pyarmor_cli_core-8.1.2-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:748c2906e29ef4de3bc83e7d39014d4416fa6a76ab1d07770067982fefcc1246", size = 655255, upload-time = "2026-08-26T04:46:13.011Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/45/c24cd03929ef50bb34407e58393121179b628c2561fecd7acda062408df7/pyarmor_cli_core-8.1.2-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:9ae31de935d673420cf3b5592a00ff2546d040c65923431f799966ff6e2976e5", size = 450939, upload-time = "2026-08-26T04:46:17.739Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/7a/31ea2e2983da14c6d41f4b350d2e127942c504e082b2c46aee0a836ba254/pyarmor_cli_core-8.1.2-cp315-cp315-win32.whl", hash = "sha256:816b34c1565c26d1a302ceae71e1791b4447606bfe8f6548ed4b0ed49ab4b372", size = 465364, upload-time = "2026-08-26T04:46:22.582Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/88/18309ad305f0ae6f462d62128d7cb882222ff0a29e8aa3461c60cad14c25/pyarmor_cli_core-8.1.2-cp315-cp315-win_amd64.whl", hash = "sha256:1a288d24fb3ca77f5060976eff3de70fc0a916074fdbe2485649a3cf9c383864", size = 424465, upload-time = "2026-08-26T04:46:27.282Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/58/7758001a43902bf162a41d6950cae17bf46432250c37800092b0aea7d815/pyarmor_cli_core-8.1.2-py3-none-any.whl", hash = "sha256:1f07c1cabe45511a4a91a615ded15adabeb8b5b93c4732dd633c4913299185bd", size = 5178, upload-time = "2026-08-26T04:49:14.149Z" }, +] + +[[package]] +name = "pycparser" +version = "3.11" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/a8/c5fdbeee588bb8ada9458774f43adf1bdd30bd59157055142183e769a024/pycparser-3.11.tar.gz", hash = "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc", size = 113796, upload-time = "2026-10-09T12:56:59.539Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/11/0e6f11117525ff0eec40ebac3d313376f102df93ca44ad9e893ee85e4f89/pycparser-3.11-py3-none-any.whl", hash = "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", size = 51178, upload-time = "2026-10-09T12:56:58.131Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "sophon-arm" +version = "3.11.1" +source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.4", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }, marker = "python_full_version >= '3.12'" }, +] +wheels = [ + { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/sophon-arm/3.11.1/sophon_arm-3.11.1-py3-none-any.whl", hash = "sha256:75ab4514562578faf9fe790ca0e147b0db481e691f1b4d165e2c675cd51c7d5d" }, +] + +[[package]] +name = "sqlite-vfs" +version = "0.4.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/b2/d4d1dcdbf13d9a9884b30d5cab90d518d86e5f1ec3531acddfbe8ebcdd1a/sqlite_vfs-0.4.0.tar.gz", hash = "sha256:970cc30d0cd16b3bc9f02b08af37eb8fcf89523b6c17740a86cd9cc260d544a4", size = 740965, upload-time = "2026-09-20T08:13:35.884Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/9f/5b32146512efc290df1a7c654f603317346d17b04684ea55dff19affe18d/sqlite_vfs-0.4.0-py3-none-any.whl", hash = "sha256:d2584e8d1cf5908f30aea6db6b95eaa01c62be426bde41e5ff7d226d40128a31", size = 94794, upload-time = "2026-09-20T08:13:34.216Z" }, +] + +[[package]] +name = "tomli" +version = "2.5.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/78/9ad63712633ed3ab5cc1a648d863d7e7da371e9425e209555a0fe711b695/tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6", size = 17662, upload-time = "2026-10-07T12:23:37.892Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/a6/ab99b60ee52acd949684febabc3005d0045d0f66bebd9cdebd67372d26dd/tomli-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4dc1c1781f2f716de763d1e9a7b34c6a894e167e291c7c5d16c72f7a9538545", size = 163901, upload-time = "2026-10-07T12:22:15.601Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/00/ee01b7ed4579180fff07142d290257f25ba786f23f3ec6005f620933c2f5/tomli-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eff8babca5a7999bc137acbc7482a8b7e17ffca5075ab41f5d770ab408c7bfef", size = 163756, upload-time = "2026-10-07T12:22:16.957Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/c2/4efebf65372f6583185f79799312109dddb61102d47e5c33dcfd1a297aca/tomli-2.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86665cee9c4835b7a7f1e8ec2c719b5258d4dc782887aded5a8ae7352a96843b", size = 268038, upload-time = "2026-10-07T12:22:18.135Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/07/5850468e925d898abb36038666f9c333a94d2a223e802a8ba5b6d319d23f/tomli-2.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e369fd63331746182360977b1892bfc215476a30d61612d732425311639f56", size = 276422, upload-time = "2026-10-07T12:22:19.567Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/87/f293984cdcf83c054196d4fd3dad44fc68ae55b4b8c44bc76cef360c3150/tomli-2.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ad1ea345759240d6463efa0ed1c704402752e49aa21476620738d74d72d8aa1", size = 272616, upload-time = "2026-10-07T12:22:20.794Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/ce/db582886b3c1219d3fec93ebd669332482e5aee7a91e0f7838d84f2d1759/tomli-2.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96243987194634bd411066ce40c952e108f86af04db533ecd8ac3ff2a85b1885", size = 276593, upload-time = "2026-10-07T12:22:22.12Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/72/7619b87dea4261fc27dd7b54c4461c129c1f7d9bb7ba3aec89c797a431b8/tomli-2.5.0-cp311-cp311-win32.whl", hash = "sha256:610b27d99f28ec5f191c7064a48f3ddb179a1fe6ca73d571483ae859f57b605e", size = 101830, upload-time = "2026-10-07T12:22:23.651Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/74/220106da34502304b6751a2a9b8a9fbca6c3fd47e737a2e2e3da7c61c9db/tomli-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c804ae44fe7b4bab5da295e4f980a1ff04670bca9d23fe0a4e887e08ebd741a8", size = 112742, upload-time = "2026-10-07T12:22:24.972Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/99/7d9c8b41837a7773613e169504147375c157a290167aa59ad74a085f521f/tomli-2.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:cfac177ebd6236003846ea339981f71457cb6eb748f23381eb257e45092e3980", size = 109332, upload-time = "2026-10-07T12:22:26.117Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/ed/7baa86f87493646a594de388c7c1c40a39dd0461f7e9c0359cbeefc91fe8/tomli-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df", size = 164854, upload-time = "2026-10-07T12:22:27.444Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/b1/44c0341f2224397855723c7a8a39f718ea6fcbcc3dacc66e5aeca0f334e3/tomli-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b", size = 164074, upload-time = "2026-10-07T12:22:28.679Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/04/e2d5b7d3fba47adedb23de616c16d428ea076c79a3d8e1d95d649ffe197e/tomli-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0", size = 274274, upload-time = "2026-10-07T12:22:29.804Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/90/6090e706ff27a6f89f4a40578e3324b95c3cd8c4150868aabf33a8f414c3/tomli-2.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6", size = 286435, upload-time = "2026-10-07T12:22:31.297Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/9e/a2c40768df16c408f22430afb0a73e9d7e5f79c950884954649d1146b74d/tomli-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc", size = 278119, upload-time = "2026-10-07T12:22:32.601Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/25/3c0cb485b98e9cfac495629b1c93c87ccf0b72fbe9d2689fd8fe62c6d5a3/tomli-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7", size = 286177, upload-time = "2026-10-07T12:22:33.745Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/8b/0144c65f0e37e51c18d04ae15c21b19431c165002d0131fe9aa8b0b8b1e8/tomli-2.5.0-cp312-cp312-win32.whl", hash = "sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2", size = 102760, upload-time = "2026-10-07T12:22:34.887Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/32/5d6d8f42fc9a05fce69354e00ff256484192f5f2fc9a2165718fa0de61ec/tomli-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7", size = 112722, upload-time = "2026-10-07T12:22:36.162Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/65/df18032218db0fb9b769fb23c8039a051f15c811993995ea04c350273a32/tomli-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea", size = 109534, upload-time = "2026-10-07T12:22:37.296Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/e5/51736d70da209350969e15aca5c5ab6e2ce1ea87a0a892a6c13aec172a86/tomli-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea", size = 163328, upload-time = "2026-10-07T12:22:38.373Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/55/086f80dab4ab497602644274e6dea7ec5dd0b4e262e443a8ad3bb7edee2d/tomli-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043", size = 162246, upload-time = "2026-10-07T12:22:39.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/eb/3ecc94459f3635c92321f4e7bde571323fdb2267c50e19e3188a281eae3b/tomli-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0", size = 272655, upload-time = "2026-10-07T12:22:41.08Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/d7/494fd1f0c37a621f1ad9975c2efadb523e8101f144ed6edb2e7fe64738f2/tomli-2.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b", size = 283595, upload-time = "2026-10-07T12:22:42.222Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/51/bb8d62b1317e6640866f6949b2d5855e5300f2c99d46de1cd245570bba65/tomli-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066", size = 276253, upload-time = "2026-10-07T12:22:43.625Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/f4/f46bd7f0763cd47de2db697dca9257c6a4adfd1a93b018cc75c8190ed5a8/tomli-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b", size = 283582, upload-time = "2026-10-07T12:22:44.983Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/03/70f2bcb2923a6db37818d917e124270a7f4cfd38ea576f5aa753a91c0ef5/tomli-2.5.0-cp313-cp313-win32.whl", hash = "sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68", size = 102628, upload-time = "2026-10-07T12:22:46.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/98/d52024bb5b0ff68b4f0d276d867f634c84a67319a7e9f6b7708a37742333/tomli-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc", size = 113301, upload-time = "2026-10-07T12:22:47.647Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/f2/540db3a70572a8c23a28aba3e9c358ce0ffffbafc990905c1343aa265b31/tomli-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84", size = 109744, upload-time = "2026-10-07T12:22:48.925Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/49/caf6b307766eb9567664a8707e9d6be5fcc0e8903f18781c6677a60d80c7/tomli-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105", size = 162899, upload-time = "2026-10-07T12:22:50.088Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/c8/68cfce773a2733a49c74f99d627fb461bd990756860099eac25617889585/tomli-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646", size = 162080, upload-time = "2026-10-07T12:22:51.558Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/b2/e5bb8651fdad593f670501a7d718b1a7f73f064d44dea15e04c04dfef45d/tomli-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b", size = 273380, upload-time = "2026-10-07T12:22:52.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/d2/9e2d7f8b1dfe0e2b34c245986ebd55c4c553ea4ce6c47c443b332673253f/tomli-2.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75", size = 283228, upload-time = "2026-10-07T12:22:54.173Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/df/ec7b876b7b1a2718bd74a3743c076fff565b04029ba33e8f61fac262739f/tomli-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb", size = 277189, upload-time = "2026-10-07T12:22:55.342Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/7b/e192d9eed0b9cb80da799f4d77052297fb9a2c3cc9b19f571f56ea88add6/tomli-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3", size = 283632, upload-time = "2026-10-07T12:22:56.735Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/50/ff94454e75461d75623e47401ed323d65c10aab8fe9033242c20cd2fdf32/tomli-2.5.0-cp314-cp314-win32.whl", hash = "sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b", size = 103535, upload-time = "2026-10-07T12:22:58.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/0b/bdacf05f963bd6026ebf6eeb0beda847d1d60e03e440725c64a4e08a0afd/tomli-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a", size = 114621, upload-time = "2026-10-07T12:22:59.2Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/99/53f438fa6ae4f9d4ed0ddde3e7242b3bdc34b48c8f9948b72b9e9b127676/tomli-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3", size = 111572, upload-time = "2026-10-07T12:23:00.479Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/20/1f88f19427d380a40e90a770e087489eaafe4aeee070ae88ed2bbec00acd/tomli-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4", size = 171814, upload-time = "2026-10-07T12:23:01.914Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/56/cbe5079c9f9a54b9b3e27fc82f08f3cb36edee75561679f53d2380c801d6/tomli-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d", size = 171324, upload-time = "2026-10-07T12:23:03.18Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/30/1d53fd3b0f1cb3ba542e345ec32c26aefdddc4e829e4f3429af8a4f27782/tomli-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9", size = 297441, upload-time = "2026-10-07T12:23:04.345Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/d9/0800acb6a111686f764c1b91ef15cc42a20a66a46013bb42220f1d2c61c1/tomli-2.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f", size = 307476, upload-time = "2026-10-07T12:23:05.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/63/30a8f3cd51b5bec37f04744bad0b0dc6160df84aad4f27b0e9283d66f221/tomli-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374", size = 296113, upload-time = "2026-10-07T12:23:07.202Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/18/0b9ffc597e69c5a1e20a7823cb60d54b39a9f54e91edcb8574f022186758/tomli-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442", size = 307725, upload-time = "2026-10-07T12:23:08.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/c7/18f8baae0b5607a60e8e19b4a7fedee43a8ff6458e3896dcbbadeeac9c22/tomli-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03", size = 108546, upload-time = "2026-10-07T12:23:09.956Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/34/4cca9739254130627bde87500b3f2b512154fe2f278efa7e2a5e10ad4bcb/tomli-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1", size = 117814, upload-time = "2026-10-07T12:23:11.486Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/fb/afa530d47dd80a78fce43beac6bc6e00f84558eafcffbc6f37b21e80d056/tomli-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0", size = 115188, upload-time = "2026-10-07T12:23:12.728Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/98/316fdc00f8c0939e6fe50461dd343c162d3ad51d1286eb25b7db54361d50/tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc", size = 162775, upload-time = "2026-10-07T12:23:13.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/22/7b10fa5bb01c9539f53f69b619361b19350acc73657772ea7ac70ba309a8/tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276", size = 161406, upload-time = "2026-10-07T12:23:15.215Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/e7/1a069d86dfd20f1f84f71c63faed9f83c1d890bc06c27d82dc7d888fb573/tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52", size = 273855, upload-time = "2026-10-07T12:23:16.471Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/83/d1ef43d1687d092ab9c235455c76e6e709483b346b056f086095c7c263a5/tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7", size = 284910, upload-time = "2026-10-07T12:23:18.166Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/05/f4d9cf7de61822ece0c3873f30d291e324911c71a378b8bfe5ced13fd9f5/tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391", size = 277723, upload-time = "2026-10-07T12:23:19.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/28/78262493141fa543151cf005760c3cb01d09fc28a11f993c05109902cb8c/tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859", size = 285115, upload-time = "2026-10-07T12:23:20.698Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/b9/e1dab9a30bcb677b5cc5cee810609cfd64f24306a3055767dd3fda00b1e0/tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb", size = 103475, upload-time = "2026-10-07T12:23:21.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/bd/31a3790c11d6ea95fcf5e6022ac0f8d0543c9b61120b730fc481bd43d3b4/tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5", size = 114589, upload-time = "2026-10-07T12:23:23.098Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/a2/4f6310fa699364f0e3af7ee3af88dddd9af066d33e716a0265bbe2b3ea84/tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd", size = 111493, upload-time = "2026-10-07T12:23:24.233Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/14/00853f0b396d8971107ae1921bb5b322fdee1650d2f16bf06c20adb532e5/tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57", size = 171380, upload-time = "2026-10-07T12:23:25.512Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/ad/fa6949321dadee46b27363974fb197b94c911c3b0f7a5fd26d7dc18fc2a0/tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd", size = 170553, upload-time = "2026-10-07T12:23:26.855Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/aa/3056c919eb3e084df3752b2cf5f865dcc04af0b27dba2f66d7b28af4633a/tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01", size = 294428, upload-time = "2026-10-07T12:23:28.132Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/b2/faeeb5d8769ea3832021d73e892c8391eae7b4b4f8b55a789127bd8b18a9/tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f", size = 304909, upload-time = "2026-10-07T12:23:29.381Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/52/f094c09e73fb654b621716d019acb5d29bdfd1be01df80c281d552bda48d/tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a", size = 293220, upload-time = "2026-10-07T12:23:30.608Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/f5/0c30541078ca4b505ce3bd76ed931facbfec524dd018535d691d1af0a6d2/tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142", size = 305705, upload-time = "2026-10-07T12:23:32.181Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/74/590e7d19d6a118fc5cc5704ff358e21d95b8573f6b9443b1519f29ca8825/tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5", size = 108432, upload-time = "2026-10-07T12:23:33.496Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/b8/63a75cfb27a17c38550e44025d3a6e7be64516fd8608a3b75703bf37d81b/tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571", size = 117281, upload-time = "2026-10-07T12:23:34.648Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/01/e8c1debb2173973372934c68fc8e46170ab60ef23ed4592dff4dec6e8993/tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7", size = 115069, upload-time = "2026-10-07T12:23:35.77Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/3f/3e3f8fd0919249b0200c80fbc4f9a1e70be19f9883da71dfb7f8b9ab8aca/tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b", size = 14765, upload-time = "2026-10-07T12:23:36.875Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +]