diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..1e9d62a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,52 @@ +name: tests + +# 说明:本文件是仓库里第一份 CI 配置,**尚未在 GitHub 上真跑过**(本机没有 +# 可用的 runner)。跑之前有两个已知的、需要按仓库实际情况确认的点: +# +# 1. pyproject.toml 把 uv 的默认索引指到了清华镜像;GitHub runner 在境外, +# 这里用 UV_DEFAULT_INDEX 覆盖回 PyPI,免得依赖国内镜像的可用性; +# 2. Ubuntu 上 Qt 需要一批系统库(libGL/libEGL/xkbcommon…),缺了会让 +# QApplication 建不起来——下面的 apt 步骤只为让 GUI 测试能跑,不想装 +# 就把那一步删掉,GUI 测试会因为 import 失败自动跳过(见 test_client_ui.py +# 顶部的 HAVE_QT 约定)。 +# +# 测试用标准库 unittest 编写,所以不需要 pytest;Windows 是客户端的目标平台, +# Ubuntu 用来兜住"纯标准库层 + 服务端示例"的跨平台回归。 + +on: + push: + branches: [main, master] + pull_request: + +env: + UV_DEFAULT_INDEX: https://pypi.org/simple + +jobs: + test: + name: ${{ matrix.os }} / py3.13 + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest] + steps: + - uses: actions/checkout@v4 + + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: 安装 Qt 运行库(Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libgl1 libegl1 libxkbcommon-x11-0 libdbus-1-3 libnss3 \ + libxcb-cursor0 libxcb-icccm4 libxcb-keysyms1 libxcb-shape0 \ + libasound2t64 + + - name: 同步依赖(GUI + FastAPI extras) + run: uv sync --extra gui --extra fastapi + + - name: 全量测试 + run: uv run python -m unittest discover -s tests -p "test_*.py" diff --git a/README.md b/README.md index 81ac540..8f56d55 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ - 增量打包: 只写入变化的条目,未变的文件不重写、不重压、不重复存储 - 可发布到对象存储: 导出成「按 sha256 命名的对象 + 一份小清单」,普通静态托管 / CDN 即可分发, 客户端按清单取回并逐个校验(不需要任何编译扩展) +- 插件化客户端: `svfs-client`(PySide6 + WebView)把 .svfs 当插件分发——完整前端直接 + 从包内加载进 WebView,多插件互相隔离,升级即新快照、可随时回滚 - 完整元数据: 保留文件权限、创建时间、修改时间等元数据 - 目录结构保持: 完整的目录层级结构保持 - 简单易用: 四个命令行工具(打包 / 解包 / 校验 / 迁移),零第三方依赖 @@ -42,6 +44,42 @@ uv sync ``` ## 使用方法 + +### 命令行入口:`svfs <子命令>`(推荐) + +所有功能收拢在一个入口下,git 风格的子命令;退出码统一为 0 成功 / 1 失败 / 2 用法错误: + +```bash +svfs pack <源文件夹> <输出.svfs> # 打包(--compress 压缩;--incremental 增量; + # --snapshot NAME 打成新快照) +svfs unpack <包> <目标目录> # 解包(--snapshot ID 选版本;--path 只导出一个路径) +svfs info <包> # 概要:统计、去重/压缩收益、快照列表(--json) +svfs verify <包> # 校验(--check-content 逐文件重算哈希;--key 验签) +svfs sign <包> --key <密钥> # 签名(--key-id 起标识;--snapshot ID 选版本) +svfs migrate <旧包> # v1/v2 → v3(默认先留 .bak 备份) +svfs publish <包> <输出目录> # 发布成对象 + manifest.json +svfs fetch <目标目录> # 按清单取回(对象按 sha256 校验) + +svfs --help # 总览 +svfs pack --help # 每个子命令的完整参数 +``` + +典型的发布/取回链路: + +```bash +svfs pack ./dist app.svfs --compress +svfs sign app.svfs --key "$SVFS_KEY" --key-id release-1 +svfs verify app.svfs --key "$SVFS_KEY" --check-content +svfs publish app.svfs public/ +aws s3 sync public/ s3://你的桶/ --cache-control "public, max-age=31536000, immutable" +svfs fetch https://cdn.example.com/app/manifest.json ./restored +``` + +> 兼容性说明:早期的六个独立命令 `vfspack` / `vfsunpack` / `vfsverify` / `vfsmigrate` / +> `vfspublish` / `vfsfetch` 全部保留为别名——它们与 `svfs` 的同名子命令跑的是 +> **同一段实现**,输出与退出码一致,已有脚本不必迁移。区别只有一点: +> `vfsverify --sign` 的功能在 `svfs` 里拆成了独立的 `svfs sign` 子命令。 + ### 打包文件夹 ```bash # 基本用法 @@ -187,13 +225,392 @@ zlib 把体积压到 1/8.7,而**读写延迟基本不变**——解压是 C ``` ```bash -vfsmigrate ./old.db # 原地迁移,先复制一份 ./old.db.bak -vfsmigrate ./old.db --output ./new.db # 输出到新文件,原库一个字节都不动 +svfs migrate ./old.db # 原地迁移,先复制一份 ./old.db.bak +svfs migrate ./old.db --output ./new.db # 输出到新文件,原库一个字节都不动 ``` 迁移会读旧库、在旁边的临时文件上重建、校验通过后原子替换,所以中途失败不会留下半成品。 -迁移过程顺带按 sha256 去重:历史库里同一份内容存了多次会被合并(`vfsmigrate` 会报告省下多少字节)。 -迁移完可以用 `vfsverify --check-content` 复核每一个文件的内容。 +迁移过程顺带按 sha256 去重:历史库里同一份内容存了多次会被合并(`svfs migrate` 会报告省下多少字节)。 +迁移完可以用 `svfs verify --check-content` 复核每一个文件的内容。 + +## 桌面工具:SVFS Studio + +`svfs-studio` 是配套的可视化工具(PySide6,暗色界面),把一个 `.svfs` 摊开来看: +文件树与内容预览、版本快照与两版差异、内容去重与压缩的实际收益、完整性校验与签名, +以及它作为一个 SQLite 数据库的内部结构(文件头、页、各表占用)和一个**只读** SQL 控制台。 + +它做的一切都调用本库的公开 API(`FolderPacker`、`migration.migrate`、`publish.export_objects`…), +没有第二份实现,所以界面上得到的结果与 CLI 逐字节一致。 + +```bash +# 安装(GUI 依赖在可选 extra 里,核心库与 CLI 依然零第三方依赖) +uv sync --extra gui +# 或:pip install "sqlite-vfs[gui]" + +# 启动 +uv run svfs-studio +# 或:python -m svfs_studio +``` + +工具里能做的不只是"看":打包(真实逐文件进度,因为 `pack_folder` 现在支持进度回调)、 +**包内编辑**、解包、迁移旧格式、回收未引用内容、发布成对象存储布局、从清单拉取、 +与本地目录逐文件对比——都跑在后台线程上,界面不卡。 + +**包内编辑**(「文件」页的编辑那一排,或右键菜单): + +* **添加文件 / 添加文件夹**:加进当前快照;文件夹整棵进去。同名路径会先问一句, + 选"替换"就覆盖(内容仍按 sha256 去重,重复字节不会多占空间); +* **新建目录**:在选中的目录下建(没选中就是根目录); +* **重命名**:改名或移动文件/目录(目录连同子项一起改); +* **删除**:删掉文件或整个子树。库里是内容寻址 + 引用计数,所以删路径不等于删内容—— + 只有一条引用都不剩时,那份内容才会被回收(「存储」页能看到可回收多少); +* **编辑内容**:选中文本文件后点「编辑内容」(或直接改完按 Ctrl+S),就地改完写回包内。 + 按 UTF-8/GBK 嗅探编码并以原编码保存;上限 1 MB——编辑器装不下的文件不给编辑, + 因为"只看到一半就保存"等于截断。二进制文件与超限文件请导出改好后用「添加文件」替换。 + +每次编辑结束都会调一次 `recount()`:刷新快照的统计与摘要,保证改完立刻能校验通过。 +代价是**之前做的签名会失效**(签名只覆盖摘要),界面上会明确提示这件事—— +这是"摘要跟着内容走"与"签名绑定摘要"这套设计的必然结果,不是 bug。 + +几个值得先试的地方: + +* 没有现成的包?点「打个示例包看看」,会在临时目录里造一棵含两份相同内容的小目录树并打包; +* 仓库自带的 `appcation.svfs` 是 schema v1,打开它会看到「旧格式」横幅和「去迁移」按钮, + 正好演示从旧格式升到 v3 的完整流程; +* 「存储」页把去重与压缩分开算:`logical_size → unique_bytes → stored_bytes` 三步各省了多少一目了然; +* 「内部」页可以直接 `SELECT`——包就是个普通 SQLite 库,这点骗不了人。 + +想要一份界面截图用于文档或评审(不需要显示器): + +```bash +python -m svfs_studio --shots ./shots ./某个包.svfs # 每个标签页各存一张 PNG +``` + +界面测试(offscreen 渲染,含像素级暗色检查、预览内容与库字节比对、中断语义): + +```bash +python -m unittest discover -s tests -p "test_studio_ui.py" -v +``` + +## 插件化客户端:svfs-client + +`svfs-client` 是基于同一套 SVFS 格式的**插件化桌面客户端**(PySide6 + QWebEngineView): +插件以 `.svfs` 单文件分发,客户端安装插件、把插件的 Web 界面直接从包内加载进 +WebView——不落盘、不起端口、不需要解包。 + +核心模型: + +* **一个插件 = 一个完整的 .svfs 包**:完整前端(HTML/JS/CSS/图标)+ 根目录一份 + `plugin.json` 清单。插件自带全部资源,不依赖宿主或其它插件的任何文件; +* **多插件并存、互相隔离**:每个插件一个包文件、一个独立 origin + (`svfs://<插件id>/`——浏览器按 origin 隔离 localStorage / IndexedDB / cookie)、 + 一份独立的键值存储。协议层拒绝跨插件请求,导航守卫禁止跳到别的插件或把插件页 + 变成远程内容容器; +* **快照即版本**:一个插件一个 .svfs,每次升级往里打一个新快照——没变的文件共享 + blob(升级通常只增加变化部分的字节),旧版本全部保留,**回滚 = 切换当前快照**, + 包文件一个字节都不用动; +* **完整性与签名贯穿始终**:安装时跑 `verify()`(可选逐文件重算 sha256), + 从远程安装时每个对象按 sha256 校验。 + +### 安装与体验 + +```bash +uv sync --extra gui # = [client] + [studio];WebEngine 需要完整版 PySide6 +uv run svfs-client # 或 python -m svfs_client +# 想指定数据目录:uv run svfs-client --data ./my-client-data +``` + +三个 extra 的区别只在 Qt 装多少:`client` = 完整 PySide6(WebEngine 在 +PySide6-Essentials 里**没有**)、`studio` = PySide6-Essentials(Studio 只用 +QtCore/QtGui/QtWidgets)、`gui` = 两者都装(旧写法保留,等价于 client + studio)。 +wheel 里三个包(sqlite_vfs / svfs_studio / svfs_client)一起分发——两个桌面 +应用的 console script 都声明在同一个发行版里,只打核心包会让 `svfs-client` +命令存在而模块缺失。 + +```bash +pip install "sqlite-vfs[client]" # 只装插件客户端(含 PySide6) +pip install sqlite-vfs # 只要核心库 + CLI(零第三方依赖) +``` + +启动后先经过一段启动动画和登录界面(强制登录,详见下文「登录与认证」; +搭配本仓库的示例服务端时,默认账号是 admin / admin123)。侧栏列出已安装的 +插件;插件管理(安装/升级/回滚/卸载)在「设置 → 插件管理」里。 + +### plugin.json 规范 + +包根(当前快照根)必须有一份 `plugin.json`: + +```json +{ + "id": "com.example.clock", + "name": "时钟", + "version": "1.2.0", + "api_version": 3, + "entry": "index.html", + "icon": "icon.png", + "permissions": ["storage", "notify", "title", "net"], + "description": "一个时钟插件" +} +``` + +* `id`:反域名形式(小写字母/数字/连字符),全局唯一,同时是 `svfs://` 的 host; +* `api_version`:插件**要求的最低宿主桥版本**——宿主接受 `api_version ≤ 自身 + PLUGIN_API_VERSION` 的插件(桥能力只增不破,升级客户端不会废掉旧插件; + 要求更高版本的插件会得到「请升级客户端」提示)。取值参考:v2 = net 与 + host.info 的 baseUrl;v3 = host.info 的 authUser、宿主给同源 net 请求自动 + 附加登录 token;v4 = dialog 权限与 `ui.confirm`/`ui.message` 原生对话框。 + 没用新能力就写 2(甚至 1)即可,兼容面更大; +* `entry`:SPA 入口。路径 404 且末段没有扩展名时回退到它(每个插件都是完整前端); +* `permissions`:声明这个插件要用哪些桥能力(storage / notify / title / net / + dialog),安装时向用户展示; +* `net_hosts`(可选,**net 目标白名单**):声明后,该插件经桥发的请求只能指向 + 列表里的主机,其它一律被宿主拒绝并给出明确错误。支持精确主机名与 `*.` 后缀 + 匹配(`"*.corp.example"` 含 `corp.example` 本身)。不写 = 不限制(老插件行为 + 不变)。有 net 权限的插件本质上能把数据发到任何地方,给内网/企业插件的清单 + 加上这一项是划算的。写了它就必须同时声明 `net` 权限(安装时校验会拦住写错的 + 清单)。 + +### 服务器地址(base_url) + +「插件管理 → 服务器」里配置一次服务端地址(`http(s)://`),它同时承担两件事: + +1. **插件下载**:发布端把每个插件的 publish 产物同步到服务器的 + `/<插件id>/` 子目录下;客户端安装时只填**插件 id**(或相对路径), + 自动解析成 `//manifest.json`。填完整 URL 也可以。 +2. **接口服务**:声明了 `net` 权限的插件经桥访问这个服务器的 API—— + 宿主代为发 HTTP 请求,**不经过浏览器的 CORS**,服务端不需要任何跨域配置。 + +配置存在 `/settings.json`,改动即时生效(已打开的插件页面下次调用就能拿到)。 + +### 登录与认证(强制登录) + +客户端启动时先播放一段启动动画(呼吸 logo + 状态文字,点击可跳过),随后进入 +登录界面——**必须登录成功才能使用客户端**。登录窗用同一套暗色主题,无头一次 +填写过服务器地址的话会自动预填,下次勾着「记住登录」登录后,启动动画期间就会 +静默校验 token(`GET /api/me`),有效则直接进主窗口,过期/网络不通则回到登录窗 +并提示原因。 + +* 登录请求:`POST /api/login`(JSON `{"username","password"}`)→ + `{"ok":true,"token","username"}`。服务端会话在内存里,重启服务端即全部失效; +* 凭据持久化:勾选「记住登录」才存 token,**且不落在 `settings.json` 里**—— + token 是能直接冒充用户的东西,改由 `credentials.py` 按可用后端加密保存: + Windows 用 DPAPI(密文只有当前用户在本机解得开),其它平台用系统凭据库 + (可选依赖 `keyring`),两者都没有才退化成明文并**在设置界面如实提示** + (「设置 → 登录凭据」显示当前存储方式)。老版本存在 `settings.json` 里的 + `auth_token` 会在启动时自动迁移并清空原字段。不勾「记住登录」则 token 只在 + 本次运行内有效(服务器地址与用户名仍会记住); +* 凭据绑定服务器:令牌只交还给**签发它的那个 base_url**,在设置里换了服务器 + 地址后旧令牌不会被当成新服务器的凭据发出去; +* 插件无感知:宿主桥给**发往服务器(与 base_url 同源)** 的 net 请求自动附加 + `Authorization: Bearer `,不会发给第三方地址,也不覆盖插件自带的 + Authorization 头; +* 会话失效有反馈:如果服务器不再认这个 token(服务端重启、会话过期),宿主 + 注入的凭据被 401 拒绝时会**主动提示**——状态灯显示「登录已失效」(黄,而不是 + 误报「离线」),横幅给出「重新登录…」一键入口,用户中心与侧栏头像同步显示 + 失效状态。提示只发一次,重新登录成功后自动恢复; +* `host.info` 新增 `authUser` 字段(当前登录用户名,未登录为空串); +* 「插件管理 → 服务器」卡片显示登录状态,可「重新登录…」或「退出登录」。 + +### 界面:侧栏 / 设置 / 用户中心 + +* 启动后**直接显示插件内容**(可配置):优先用设置里配置的启动插件, + 未配置则按默认规则(上次打开的 > 按 id 排序第一个启用的插件),都没有 + 才停在插件管理页; +* 侧栏顶部「«」把侧栏收成**图标栏**(宽度动画过渡,不再是瞬跳也不再让插件 + 不可达——图标带悬停提示,点击即打开),再点「»」展开;选择会记住, + 下次启动保持; +* 侧栏搜索框**模糊匹配**插件名与 id(包含匹配优先,其次首字母缩写: + 「csd」能搜到 `com.svfs.demo`、中文名直接搜汉字);无结果时给出提示, + 清空即恢复全部; +* 侧栏底部「设置」:**插件管理入口**(安装本地 .svfs、从服务器安装、 + 升级/回滚/卸载)、**启动时打开**的插件、修改服务器地址(与服务器卡片 + 同步生效)、**数据目录**(插件路径,写指针文件,重启生效)、**对象缓存 + 路径**(留空 = 系统默认,安装时即时生效)与缓存清理、**网络**(插件 net + 请求的超时与重试次数、代理、额外 CA 证书——内网自签 HTTPS 与公司代理都 + 在这里配,保存即生效,CA 路径写错会当场报错而不是等请求失败)、 + **登录凭据**存储方式、**诊断日志落盘**开关; +* 侧栏底部的圆形头像(登录用户首字母)点击进「用户中心」:当前登录 + 状态、服务器地址、本次登录时间,以及退出登录 / 重新登录。 +* 插件页面底色固定为主题底色;WebEngine 在启动阶段预热(Profile 提前 + 创建 + 隐藏预热视图 + 共享 OpenGL 上下文)——首次打开插件不再闪白屏 + 或整窗闪烁。 + +### 可靠性与诊断 + +* **插件更新检查**:插件管理页「检查更新」逐个比对服务器上的快照名与 + 本地版本,有新版就在卡片上显示「可升级 → x.y.z」徽章与一键升级按钮 + (走远程安装链路,升级后徽章自动消失); +* **客户端自更新检查**:启动时(以及改服务器地址后)顺带看一眼服务器上的 + `/client/manifest.json`(静态文件,与插件清单同一套托管方式, + 内容 `{"version","url","notes"}`);比本地新就在状态栏提示并在横幅给出 + 「下载新版本…」(交给系统浏览器打开 `url`)。没放这个文件 = 静态 404, + 静默跳过,不当错误; +* **服务器连通状态**:服务器卡片上有一个状态灯(灰=未配置 / 绿=在线并 + 显示延迟 / 黄=在线但登录已失效 / 红=离线),启动时探测一次并每 60 秒复查; +* **离线宽限**:启动时若服务器不可达(网络错误,而非凭据被拒),记住的 + 登录凭据仍然放行——带「离线模式」提示进入,本地插件照常用,接口类 + 功能恢复连接后自然可用; +* **网络可配置**:插件 net 请求的超时(默认 30 秒)与重试次数(默认 1,**只 + 对幂等方法** GET/HEAD/OPTIONS 生效,遇 502/503/504 或连接层错误按指数退避 + 重试——自动重发 POST 可能把"下单"变成两单,所以不重试);代理与额外 CA + 证书在设置里配,loopback 目标永远直连; +* **渲染崩溃恢复**:插件页面的渲染进程崩溃时显示可一键重载的占位页, + 而不是永久白屏; +* **诊断日志**:工作线程输出、插件页面控制台(`console.log` 等)、桥的 + net 失败(含重试与凭据被拒)、未捕获异常、服务器探测失败统一记入内存 + 环形缓冲(最近 600 条),**同时滚动写入 `/logs/client.log`** + (1 MB × 3 份,可在设置里关掉)——崩溃/重启之后还能回看现场,而内存那份 + 恰好会随崩溃一起消失;「设置 → 导出诊断报告…」生成含环境信息与日志的 + 文本文件(报告里带日志文件路径)。 + +### 插件前端拿到的桥:`window.svfs` + +宿主自动注入(插件作者**不需要**引用 qwebchannel.js),能力按 `permissions` 把关, +没声明的能力调用直接失败: + +```html + +``` + +`host.info` 不需要权限——它只包含插件自己的元数据、宿主版本号与配置的服务器 +地址,不含用户数据。 + +### 发布 → 安装的完整链路 + +发布端用现成的 `svfs publish`,客户端用 manifest 安装(对象逐个 sha256 校验): + +```bash +# 插件开发机:打包 + 发布到静态托管 +svfs pack ./my-plugin dist.svfs --compress +svfs publish dist.svfs ./public +aws s3 sync ./public s3://你的桶/plugin/com.example.clock/ \ + --cache-control "public, max-age=31536000, immutable" + +# 客户端「从服务器安装…」里填插件 id 即可: +# com.example.clock +# (publish 产物已按约定同步到 /com.example.clock/ 下; +# 同一个插件发新版本后,客户端再装一次就完成增量升级) +``` + +服务端如果还提供接口服务,加一个普通 HTTP API 即可——插件的 `net` 请求就是 +普通的服务端调用,无需为 `svfs://` origin 做任何跨域配置。 + +### 插件开发辅助:桥调试台与 Vue 模板 + +* **桥调试台**:客户端「设置 → 桥调试台 → 安装调试插件」一键装一个调试 + 插件(`com.svfs.demo`),把 `window.svfs` 的每类能力做成可点击面板 + (host.info / storage 键值 / net 自定义请求 / notify / title / 原生对话框), + 开发自己的插件时用来对照验证真实桥行为; +* **Vue 开发模板**:`examples/vue-plugin-template/` —— Vue 3 + Vite 起步 + 模板,含 `src/svfs.js`(Promise 封装)、`src/mock-svfs.js`(浏览器 dev + 模式下的宿主模拟器,`npm run dev` 不进客户端也能迭代 UI)与每类能力 + 一个的面板组件;`npm run build` 后 `svfs pack ./dist plugin.svfs` 即可 + 安装(详见模板内 README)。 + +### 服务端最小示例(FastAPI) + +`examples/plugin_server.py` 是一个单文件服务端,同时覆盖插件下载与接口服务: +`POST /plugins/` 收 .svfs 包并就地 publish(服务端用同一套清单规则校验), +静态吐出各插件的 `manifest.json + blobs/` 供客户端下载,`GET /api/ping` 等 +API 供插件经 `net` 调用;可选 `SVFS_UPLOAD_TOKEN` 环境变量保护上传。 + +`/api/*` 需要登录(`GET /api/ping`、`POST /api/echo` 校验 Bearer token), +登录体系三个接口: + +| 接口 | 说明 | +|---|---| +| `POST /api/login` | `{"username","password"}` → `{"ok",token,"username","login_time"}`,密码错 401 | +| `GET /api/me` | 带 Bearer token → `{"ok","username","login_time"}`,用于启动时静默校验 | +| `POST /api/logout` | 作废当前 token | + +另外提供可选的 `/client/manifest.json`(客户端自更新检查):设置环境变量 +`SVFS_CLIENT_VERSION=9.9.9 SVFS_CLIENT_URL=https://…` 即可看到「客户端有新版本」 +提示,不设置时返回 404(客户端静默跳过)。生产上这个文件直接静态托管即可, +内容就是 `{"version", "url", "notes"}`。 + +账号来源:默认内置演示账号 `admin / admin123`;设置环境变量 +`SVFS_USERS="user:pass,user2:pass2"` 即换成自己的账号表。插件下载与上传保持 +开放/沿用原有令牌机制,不受登录影响。 + +```bash +uv sync --extra fastapi # fastapi/starlette + uvicorn + python-multipart +uv run python examples/plugin_server.py # → http://127.0.0.1:8000 + # 启动时会打印登录账号提示 + +# 客户端「服务器」卡片填 http://127.0.0.1:8000,登录窗输 admin / admin123; +# 「从服务器安装」填插件 id(或 curl 上传一个包试试,上传不需要登录 token): +curl -F "file=@dist.svfs" http://127.0.0.1:8000/plugins/com.example.my-plugin + +# 直接调 /api/* 的外部脚本要先拿 token: +token=$(curl -s -X POST http://127.0.0.1:8000/api/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' | jq -r .token) +curl -H "Authorization: Bearer $token" http://127.0.0.1:8000/api/ping +``` + +它的行为有测试背书(`tests/test_plugin_server.py`,含"上传 → 服务端 publish → +真实 HTTP → 客户端 install_from_url 装回"的完整闭环)。 + +### 数据目录 + +```text +/ Windows: %LOCALAPPDATA%/svfs-client;其它: ~/.svfs-client +├── registry.json 已安装插件注册表(enabled、当前快照、来源…) +├── settings.json 客户端设置(服务器地址、网络、日志开关…) +├── credentials.dat 登录令牌(DPAPI/keyring 加密;见 credentials.py) +├── logs/client.log 诊断日志(1 MB × 3 份滚动,可关) +├── plugins//plugin.svfs 每个插件一个包文件(快照即版本) +├── storage/.json 桥 storage 的键值存储(按插件隔离) +└── web/ WebView 的持久化目录(localStorage 等) +``` + +`settings.json` 里**没有**登录令牌:老版本的 `auth_token` 字段会在启动时迁移到 +`credentials.dat`(并按可用的加密后端保存),迁移后原字段留空不再使用。 + +### 实现分层(与 svfs-studio 同一套约定) + +`manifest` / `paths` / `registry` / `installer` / `sample` / `auth` / +`credentials` / `diagnostics` / `netutil` / `search` 是**纯标准库** +(不 import Qt,可在无 PySide6 的环境单独测试);`scheme` / `bridge` / +`webview` / `worker` / `login`(启动动画与登录窗)/ `shell` / `app` 属于 Qt 层。 +界面复用 svfs-studio 的主题与基础部件,视觉与 Studio 一致。 + +测试(逻辑层纯标准库;界面层 offscreen,含一次真实的 WebEngine 冒烟——加载 +示例插件并验证桥注入与 storage 往返,环境起不来 WebEngine 时自动跳过): + +```bash +python -m unittest discover -s tests -p "test_client_logic.py" -v +python -m unittest discover -s tests -p "test_client_auth.py" -v # 登录认证/网络策略(纯层) +python -m unittest discover -s tests -p "test_client_credentials.py" -v # 凭据存储(纯层) +python -m unittest discover -s tests -p "test_client_login_ui.py" -v # 登录窗/启动动画/桥注入 +python -m unittest discover -s tests -p "test_client_ui.py" -v +``` + ## 数据能力 @@ -384,6 +801,26 @@ sqlite_vfs/ │ ├── migrate.py # vfsmigrate │ ├── publish.py # vfspublish(导出对象 + 清单) │ └── fetch.py # vfsfetch(按清单取回并校验) +├── src/svfs_studio/ # 桌面可视化工具(可选 extra: gui) +│ ├── format.py # [无 Qt] 大小/时间/权限/文本嗅探/hexdump 的显示格式化 +│ ├── analysis.py # [无 Qt] 快照差异、去重与占用汇总、SQLite 内部结构解析 +│ ├── worker.py # 所有库调用都跑在一个专用线程里;进度回调、中断、日志 +│ ├── theme.py # 暗色调色板 + QSS +│ ├── sample.py # 示例包(与 README 里的演示同一棵树) +│ ├── views/ # 七个标签页:概览/文件/快照/存储/完整性/内部/工具箱 +│ └── widgets/ # 外壳(主窗口)、卡片、自绘图表 +├── src/svfs_client/ # 插件化桌面客户端(可选 extra: gui,需要完整版 PySide6) +│ ├── manifest.py # [无 Qt] plugin.json 清单的读取与校验 +│ ├── paths.py # [无 Qt] 数据目录布局(注册表 / 设置 / 包 / 键值存储 / WebView) +│ ├── settings.py # [无 Qt] 客户端设置(base_url 的读写、规范化与地址解析) +│ ├── registry.py # [无 Qt] 已安装插件注册表(JSON 原子写) +│ ├── installer.py # [无 Qt] 安装 / 升级 / 回滚 / 卸载(复用核心库 API) +│ ├── sample.py # [无 Qt] 示例插件生成器(完整前端 + 桥能力演示) +│ ├── scheme.py # svfs:// 协议:从包内直接服务文件,host=插件id 实现隔离 +│ ├── bridge.py # 受限 QWebChannel 桥(storage / net / notify / title,按权限把关) +│ ├── webview.py # PluginView + 导航守卫 + window.svfs 注入 +│ ├── worker.py # 后台线程服务(request-id 分发、进度、独占 op) +│ └── shell.py # 主窗口:侧栏 + 插件管理页 + 每插件一个常驻 WebView ├── tests/ # 单元与集成测试(纯标准库 unittest) └── README.md ``` @@ -455,8 +892,12 @@ sqlite_vfs/ # 安装开发依赖 uv sync --dev -# 运行测试 -pytest +# 运行测试(测试是纯标准库 unittest,没有引入 pytest) +uv run python -m unittest discover -s tests + +# 只跑界面测试(offscreen,不需要显示器;未装含 GUI 的 extra 时会整体跳过) +uv sync --extra gui +uv run python -m unittest discover -s tests -p "test_studio_ui.py" -v # 构建包 uv build diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000..c413259 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,134 @@ +# 打包 svfs-studio / svfs-client 为独立可执行文件 + +把两个 PySide6 应用打成"发给谁都能双击运行"的形态。支持两个后端: +**PyInstaller**(推荐:构建快、Qt/QtWebEngine 钩子成熟)和 +**Nuitka**(真编译:启动更快、更难反编译,代价是构建慢 + 需要 C 编译器)。 + +## 环境准备(一次性) + +```bash +uv sync --extra gui # PySide6(完整版,含 WebEngine) +uv pip install pyinstaller # PyInstaller 路线 +# 或 +uv pip install nuitka # Nuitka 路线(还需 C 编译器,见下文) +``` + +## PyInstaller(推荐) + +```bash +# 目录版(推荐分发方式:启动快;整个目录一起拷贝分发) +uv run python packaging/build.py --tool pyinstaller --app both + +# 只打一个 +uv run python packaging/build.py --tool pyinstaller --app client +uv run python packaging/build.py --tool pyinstaller --app studio + +# 单文件版(SVFS_ONEFILE=1 传给 spec;QtWebEngine 启动前要先解包,明显更慢) +uv run python packaging/build.py --tool pyinstaller --app both --onefile +``` + +产物: + +```text +dist/SVFS-Studio/SVFS-Studio.exe +dist/SVFS-Client/SVFS-Client.exe # --onefile 时是 dist/SVFS-Client.exe +``` + +要点: + +* 配置在 `svfs_studio.spec` / `svfs_client.spec`(数据文件、隐藏导入都在那里声明); +* QtWebEngine 的运行时(`QtWebEngineProcess.exe`、icudtl.dat、资源包、locale) + 由 pyinstaller-hooks-contrib 的 PySide6 钩子自动收集,**请保持 PyInstaller ≥ 6**; +* 入口不是 `src/*/__main__.py`(有相对导入),而是本目录的 + `run_studio.py` / `run_client.py`(绝对导入); +* `svfs_studio` 的图标资源随包打进 `resources/`, + `sample.repo_root()` 已适配冻结环境(PyInstaller 找 `sys._MEIPASS`, + Nuitka 找可执行文件同级目录)。 + +## Nuitka + +```bash +# 先看看会执行什么命令 +uv run python packaging/build.py --tool nuitka --app client --dry-run + +# 正式构建(首次缺 C 编译器时会自动下载 MinGW64;也可自备 MSVC) +uv run python packaging/build.py --tool nuitka --app both +``` + +产物在 `<输出目录>/run_.dist/` 下(默认输出目录 `build/nuitka`; +**路径含非 ASCII 字符时自动退到系统临时目录**——Windows 的 MSVC 链接器 +在中文路径下会 LNK1104)。 + +要点: + +* `--enable-plugin=pyside6` 负责 Qt 全家桶(含 WebEngine 子进程与资源); +* standalone 模式 + `--windows-console-mode=disable`(GUI 程序不弹控制台); +* 构建慢(整包编译,几分钟到十几分钟),但产物启动比 PyInstaller 快、 + 没有解包临时目录的问题。 + +**⚠ 已知问题(2026-09 实测)**:Nuitka 4.2.1 + PySide6 6.11.2 的产物运行时报 +`Unable to import Shiboken`(编译后的 `shiboken6.Shiboken` 无法导入)。 +用最小 PySide6 程序即可复现,属工具链兼容问题,与本仓库代码无关; +`--include-package=shiboken6`、`--nofollow-import-to` + 数据目录两种绕法均无效。 +解决途径:等 Nuitka 发版修复,或临时降级 PySide6(`uv pip install "PySide6<6.10"`) +后再构建。**PyInstaller 路线不受影响,当前请优先使用。** + +## 体积裁剪 + +两个 spec 在 Analysis 之后按**白名单**过滤 `a.binaries` / `a.datas` +(PySide6 的钩子会把 wheel 里所有 Qt 模块的 DLL/pyd 整包搬进来,黑名单删不干净)。 +实测效果(PyInstaller 6.22 + PySide6 6.11,Windows x64): + +| 应用 | 原始 | 目录版(白名单) | 单文件版(LZMA) | +|---|---|---|---| +| SVFS-Studio | 116 MB | **66 MB** | **29 MB** | +| SVFS-Client | 559 MB | **300 MB** | **126 MB** | + +Client 的白名单来自 objdump 实测的导入表(不是猜的): + +* `Qt6WebEngineCore` 链 Core/Gui/Network/Positioning/Qml/Quick/WebChannel, + `WebEngineWidgets` 另需 PrintSupport/QuickWidgets/Widgets,`Qt6Quick` 另需 + Qt6OpenGL 与 QmlMeta/QmlModels —— 这些 DLL 必须留; +* pyd 只留 Python 层实测 import 链用到的 8 个绑定(QtCore/QtGui/QtWidgets/ + QtNetwork/QtPrintSupport/QtWebChannel/QtWebEngineCore/QtWebEngineWidgets), + 注意 pyd 的导入链比 DLL 的依赖多两个(QtNetwork/QtPrintSupport); +* 删掉的:Quick3D 全家、QuickControls2 样式、Pdf、ShaderTools、Svg、 + opengl32sw.dll(20M,Chromium 自带 SwiftShader 兜底)、DevTools 资源包(85M)、 + QML 组件目录(29M)、Qt 译文、虚拟键盘、qmltooling。 + +裁不动的:`Qt6WebEngineCore.dll` ~195M(Chromium 本体)+ `icudtl.dat` 10M。 + +已知取舍: + +* DevTools 资源包删了 → 插件页面里 F12 检查元素无资源,调试请用开发环境; +* opengl32sw 删了 → 极端无 GPU 环境若渲染异常,把它加回 spec 的保留例外重打; +* Qt 译文删了 → Qt 内建对话框退回英文,界面自有文案不受影响。 + +**UPX 实测无效**:对 Qt DLL 用 `--best` 只把 Client 目录版从 300M 压到 290M +(pyd 全部压缩失败),收益 3%,还要承担杀软误报——不推荐。 +想要更小就用单文件版(上表第三列),代价是每次启动先解包到临时目录 +(Client 单文件启动明显慢,QtWebEngine 尤甚)。 + +## 冒烟验证 + +```bash +# client:--help 在进入 GUI 之前返回(退出码 0 即装配成功) +./dist/SVFS-Client/SVFS-Client.exe --help + +# client:真实加载一个插件页面(--open 启动后直开指定插件; +# 页面加载后 web/ 下会出现 Chromium 的 Local Storage/GPUCache 等痕迹) +./dist/SVFS-Client/SVFS-Client.exe --data <已装插件的数据目录> --open <插件id> + +# studio:offscreen 截图模式,跑完自动退出并在目录里留下每页一张 PNG +./dist/SVFS-Studio/SVFS-Studio.exe --shots ./smoke-shots +``` + +## 常见问题 + +* **杀软误报**:PyInstaller 单文件版常见,目录版较少;Nuitka 编译产物基本不误报。 +* **体积**:QtWebEngine 应用的目录版约 150~250 MB,属正常(Chromium 运行时)。 +* **数据目录**:两个应用的用户数据都写在 + `%LOCALAPPDATA%/<应用名>`(client)与 QSettings 里(studio 的窗口几何), + 与安装位置无关,覆盖更新不需要迁移。 +* **离线构建**:`--assume-yes-for-downloads` 会在缺编译器时下载 MinGW64; + 全离线环境请预装 MSVC Build Tools 或自行提供 MinGW64。 diff --git a/packaging/build.py b/packaging/build.py new file mode 100644 index 0000000..b41f623 --- /dev/null +++ b/packaging/build.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +"""打包编排器:把 svfs_studio / svfs_client 打成独立可执行文件。 + +两个后端二选一: + +* **PyInstaller**(推荐,快、Qt 钩子成熟):使用本目录下的 ``*.spec``; +* **Nuitka**(真编译,启动快/更难反编译,但构建慢、需要 C 编译器)。 + +用法(在仓库根目录):: + + # 环境准备(一次性) + uv sync --extra gui + uv pip install pyinstaller # 或: uv pip install nuitka + + # PyInstaller(目录版,推荐分发方式) + uv run python packaging/build.py --tool pyinstaller --app both + + # PyInstaller 单文件版 + uv run python packaging/build.py --tool pyinstaller --app both --onefile + + # Nuitka(首次运行会自动下载 MinGW64,或自备 MSVC) + uv run python packaging/build.py --tool nuitka --app both + uv run python packaging/build.py --tool nuitka --app client --dry-run # 只看命令 + + # 已有 .dist 只做瘦身(裁剪 + UPX),不重新编译 + uv run python packaging/build.py --postprocess build/nuitka/run_client.dist + +产物:: + + dist/SVFS-Studio/… 或 dist/SVFS-Studio.exe + dist/SVFS-Client/… 或 dist/SVFS-Client.exe + +Nuitka 路线打包完成后会自动对 .dist 做"瘦身":删掉插件客户端用不到的 +DevTools/调试资源、无人使用的 .qm 翻译(~420M → ~269M),再对整目录做 +CompactOS 透明压缩(~269M → ~119M 磁盘占用)。依据与细节见本文件 +``prune_nuitka_dist`` 上方的注释;压缩不用 UPX 的原因也记在那里。 + +冻结环境下的资源路径适配见 ``src/svfs_studio/sample.py`` 的 ``repo_root()`` +(PyInstaller 在 sys._MEIPASS、Nuitka 在可执行文件同级目录找 resources/)。 +""" + +import argparse +import glob +import importlib.metadata +import os +import subprocess +import sys +import tempfile + +PACKAGING_DIR = os.path.dirname(os.path.abspath(__file__)) +REPO = os.path.dirname(PACKAGING_DIR) +SRC = os.path.join(REPO, "src") + +APPS = { + "studio": { + "package": "svfs_studio", + "exe": "SVFS-Studio", + "runner": os.path.join(PACKAGING_DIR, "run_studio.py"), + "spec": os.path.join(PACKAGING_DIR, "svfs_studio.spec"), + "needs_resources": True, + }, + "client": { + "package": "svfs_client", + "exe": "SVFS-Client", + "runner": os.path.join(PACKAGING_DIR, "run_client.py"), + "spec": os.path.join(PACKAGING_DIR, "svfs_client.spec"), + "needs_resources": False, + }, +} + +DIST_DIR = os.path.join(REPO, "dist") +WORK_DIR = os.path.join(REPO, "build") + + +# ---------------------------------------------------------------------- +# Nuitka 产物瘦身(打包完成后对 .dist 目录执行;也可 --postprocess 单独跑) +# +# 裁剪依据(与 svfs_client.spec 的 PyInstaller 白名单交叉验证过): +# +# * qtwebengine_devtools_resources*.pak(~89M):DevTools 调试资源,仅开发用; +# * *.debug.pak(~5M):Chromium 调试符号 pak; +# * PySide6/translations(~57M):代码从未安装 QTranslator,.qm 全是死重, +# 只留 Chromium 内置 UI(右键菜单/表单提示)要用的 zh-CN、en-US locale; +# * qt6pdf.dll / qt6svg.dll / QtSvg.pyd(~7M):客户端 import 链不含 +# QtPdf/QtSvg,PyInstaller 白名单版(实测可用)就没有这几个文件。 +# +# 压缩只用 Windows CompactOS(NTFS 透明压缩,LZX),对 .dist 整目录生效, +# 磁盘占用约 2.5:1,加载/读取时由系统透明解压。**不要换回 UPX**——实测三条 +# 死路(2026-09-16,别再试): +# 1) Qt 的 DLL 带 GUARD_CF(Control Flow Guard),UPX 必须 --force 才肯压; +# 2) qt6webenginecore.dll 压上去就废:LZMA 下 DllMain 初始化失败 +# (WinError 1114),换 NRV 加载直接段错误; +# 3) 更隐蔽:即便只压其余 qt6*.dll,Chromium 会深挖这些 DLL 的内部结构, +# "记住登录"自动进入主窗口 → WebEngine 初始化的路径照样 AV 崩溃 +# (Qt6WebEngineCore.dll 内 0xc0000005),而且"只到登录窗"的冒烟测试 +# 完全暴露不了——测试必须覆盖自动登录后的主窗口链路。 +# CompactOS 只改磁盘占用,文件逻辑大小不变;非 NTFS 卷自动原样,无副作用。 +# ---------------------------------------------------------------------- + +_WEBENGINE_LOCALE_KEEP = {"en-US.pak", "zh-CN.pak"} +_CLIENT_UNUSED_DLLS = ("qt6pdf.dll", "qt6svg.dll", + os.path.join("PySide6", "QtSvg.pyd")) + + +def prune_nuitka_dist(dist_dir): + """按上面的清单裁剪 .dist,返回释放的字节数。 + + WebEngine 相关项只在产物真带了 WebEngine(存在 qt6webenginecore.dll) + 时才删,所以对 studio 产物同样安全(顶多删掉没人用的 .qm)。 + """ + total = 0 + + def _remove(path): + nonlocal total + if os.path.isfile(path): + total += os.path.getsize(path) + os.remove(path) + + is_webengine = os.path.isfile(os.path.join(dist_dir, "qt6webenginecore.dll")) + + for pattern in ("qtwebengine_devtools_resources*.pak", "*.debug.pak"): + for path in glob.glob(os.path.join(dist_dir, pattern)): + _remove(path) + + translations = os.path.join(dist_dir, "PySide6", "translations") + if os.path.isdir(translations): + locales_dir = os.path.join(translations, "qtwebengine_locales") + for path in glob.glob(os.path.join(translations, "**", "*"), + recursive=True): + if not os.path.isfile(path): + continue + if (is_webengine + and os.path.dirname(path) == locales_dir + and os.path.basename(path) in _WEBENGINE_LOCALE_KEEP): + continue + _remove(path) + + if is_webengine: + for name in _CLIENT_UNUSED_DLLS: + _remove(os.path.join(dist_dir, name)) + + print(f"裁剪 .dist:释放 {total / 1048576:.1f} MB") + return total + + +def compact_compress_dist(dist_dir): + """对整个 .dist 目录做 CompactOS(NTFS 透明压缩,LZX)。 + + 只改磁盘占用,文件的逻辑大小与内容字节完全不变(加载/读取时由系统 + 透明解压),没有 UPX 那类兼容性风险(UPX 的实测死路见上方注释)。 + 非 NTFS 卷或 compact.exe 不可用时跳过,保持原样。 + """ + compact = os.path.join(os.environ.get("WINDIR", r"C:\Windows"), + "System32", "compact.exe") + if not os.path.isfile(compact): + print("提示: 找不到 compact.exe,跳过 CompactOS 压缩") + return + before = _disk_usage(dist_dir) + # /s::连同子目录一起压;/f:已压缩的也强制重压(幂等重跑安全)。 + # compact 输出是本机 ANSI 代码页(中文系统为 GBK),按 mbcs 解码 + proc = subprocess.run([compact, "/c", "/exe:lzx", "/f", "/q", + f"/s:{dist_dir}"], capture_output=True, + encoding="mbcs", errors="replace") + if proc.returncode != 0: + detail = (proc.stderr or proc.stdout or "").strip().splitlines() + tail = detail[-1] if detail else f"exit {proc.returncode}" + print(f"提示: CompactOS 压缩失败(保持原样,不影响功能): {tail}") + return + after = _disk_usage(dist_dir) + print(f"CompactOS 压缩整目录:{before / 1048576:.1f} MB → " + f"{after / 1048576:.1f} MB(磁盘占用)") + + +def _disk_usage(path): + """目录的真实磁盘占用(NTFS 压缩后按 allocated 计)。 + + os.path.getsize 对压缩文件返回逻辑大小,会高估,所以走 Win32 的 + GetCompressedFileSizeW;取不到的文件退回逻辑大小。""" + import ctypes + + kernel32 = ctypes.windll.kernel32 + get_compressed = kernel32.GetCompressedFileSizeW + get_compressed.argtypes = [ctypes.c_wchar_p, ctypes.POINTER(ctypes.c_ulong)] + get_compressed.restype = ctypes.c_ulong + invalid = ctypes.c_ulong(0xFFFFFFFF).value + + total = 0 + for root, _dirs, files in os.walk(path): + for name in files: + filepath = os.path.join(root, name) + high = ctypes.c_ulong(0) + size = get_compressed(filepath, ctypes.byref(high)) + if size == invalid and kernel32.GetLastError() != 0: + try: + size = os.path.getsize(filepath) + except OSError: + continue + total += size + else: + total += (high.value << 32) | size + return total + + +def _ascii_safe(path): + """Windows 的 MSVC 链接器在含非 ASCII 字符的路径下会直接 LNK1104 + (比如仓库放在"新建文件夹"里)。Nuitka 的输出目录必须 ASCII 安全。""" + try: + path.encode("ascii") + return True + except UnicodeEncodeError: + return False + + +def nuitka_output_dir(explicit=None): + if explicit: + return explicit + default = os.path.join(WORK_DIR, "nuitka") + if _ascii_safe(default): + return default + # 仓库路径不 ASCII:退到临时目录(用户名含中文时再退到盘根) + fallback = os.path.join(tempfile.gettempdir(), "nuitka-build") + if not _ascii_safe(fallback): + fallback = r"C:\nuitka-build" + print(f"提示: 输出目录 {default} 含非 ASCII 字符,改用 {fallback}") + return fallback + + +def run(cmd, dry_run=False, env=None): + printable = " ".join(f'"{c}"' if " " in c else c for c in cmd) + print(f"+ {printable}", flush=True) + if dry_run: + return 0 + return subprocess.run(cmd, env=env, cwd=REPO).returncode + + +def build_pyinstaller(app_key, onefile=False): + """PyInstaller:配置都在 spec 里,这里只负责调用与输出目录。""" + app = APPS[app_key] + env = dict(os.environ) + if onefile: + env["SVFS_ONEFILE"] = "1" + cmd = [ + sys.executable, "-m", "PyInstaller", + "--noconfirm", + "--distpath", DIST_DIR, + "--workpath", os.path.join(WORK_DIR, "pyinstaller", app_key), + app["spec"], + ] + return run(cmd, env=env) + + +def build_nuitka(app_key, dry_run=False, output_dir=None, no_compress=False): + """Nuitka standalone:pyside6 插件负责 Qt/QtWebEngine 的资源与运行时。 + + 打包完成后自动做瘦身(prune_nuitka_dist + compact_compress_dist), + ``no_compress=True`` 时只裁剪不压缩。 + + ⚠ 已知问题(2026-09 实测):Nuitka 4.2.1 + PySide6 6.11.2 的产物在运行期 + 报 "Unable to import Shiboken"——编译后的 shiboken6.Shiboken 无法导入。 + 最小 PySide6 程序即可复现,属工具链兼容问题,与本仓库代码无关。 + 解决途径:升级 Nuitka 等待修复,或临时降级 PySide6(< 6.10)再打包。 + PyInstaller 路线不受影响,推荐使用。 + """ + app = APPS[app_key] + out_dir = nuitka_output_dir(output_dir) + cmd = [ + sys.executable, "-m", "nuitka", + "--standalone", + "--enable-plugin=pyside6", + "--lto=yes", + "--clang", + "--remove-output", + "--python-flag=-OO", + # WebEngine 的子进程与资源由插件收集;首次编译缺编译器时自动装 MinGW64 + "--assume-yes-for-downloads", + "--windows-console-mode=disable", + "--include-windows-runtime-dlls=no", + # Nuitka 的选项一律用 --opt=value 形式(部分版本不接受分开传值) + f"--output-dir={out_dir}", + f"--output-filename={app['exe']}{'.exe' if os.name == 'nt' else ''}", + "--include-package=sqlite_vfs", + f"--include-package={app['package']}", + "--nofollow-import-to=tkinter", + "--nofollow-import-to=unittest", + ] + icon = os.path.join(REPO, "resources", "ico", "logo.ico") + if os.path.exists(icon): + cmd.append(f"--windows-icon-from-ico={icon}") + if app["needs_resources"]: + # 数据文件落在产物目录下,冻结版 repo_root() 从可执行文件旁找 resources/ + cmd.append(f"--include-data-dir={os.path.join(REPO, 'resources')}=resources") + cmd.append(app["runner"]) + code = run(cmd, dry_run=dry_run) + if code == 0 and not dry_run: + dist_name = os.path.splitext(os.path.basename(app["runner"]))[0] + ".dist" + dist_dir = os.path.join(out_dir, dist_name) + prune_nuitka_dist(dist_dir) + if not no_compress: + compact_compress_dist(dist_dir) + print(f"Nuitka 产物: {dist_dir}") + return code + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--tool", choices=("pyinstaller", "nuitka"), default=None) + parser.add_argument("--app", choices=("studio", "client", "both"), default="both") + parser.add_argument("--onefile", action="store_true", + help="仅 PyInstaller:打成单文件(启动较慢)") + parser.add_argument("--output-dir", default=None, + help="仅 Nuitka:产物输出目录(默认 build/nuitka;" + "路径含非 ASCII 字符时自动退到临时目录)") + parser.add_argument("--postprocess", metavar="DIST_DIR", default=None, + help="不打包:对已有的 Nuitka .dist 目录做瘦身" + "(裁剪 + UPX)") + parser.add_argument("--no-compress", action="store_true", + help="跳过 CompactOS 压缩(压缩后运行异常时用它排查)") + parser.add_argument("--dry-run", action="store_true", + help="只打印将要执行的命令") + args = parser.parse_args(argv) + + if args.postprocess: + if not os.path.isdir(args.postprocess): + print(f"目录不存在: {args.postprocess}", file=sys.stderr) + return 2 + prune_nuitka_dist(args.postprocess) + if not args.no_compress: + compact_compress_dist(args.postprocess) + return 0 + + if not args.tool: + parser.error("需要 --tool(pyinstaller/nuitka)," + "或用 --postprocess 对已有 .dist 瘦身") + + if not os.path.isdir(SRC): + print(f"找不到 src/:请在仓库根目录运行(当前认定的仓库根: {REPO})", + file=sys.stderr) + return 2 + + try: + importlib.metadata.version(args.tool) + except importlib.metadata.PackageNotFoundError: + if not args.dry_run: + print(f"未安装 {args.tool}:uv pip install {args.tool}", file=sys.stderr) + return 2 + + keys = ("studio", "client") if args.app == "both" else (args.app,) + failed = [] + for key in keys: + if args.tool == "pyinstaller": + code = build_pyinstaller(key, onefile=args.onefile) + else: + code = build_nuitka(key, dry_run=args.dry_run, + output_dir=args.output_dir, + no_compress=args.no_compress) + if code != 0: + failed.append(key) + + if failed: + print(f"打包失败: {', '.join(failed)}", file=sys.stderr) + return 1 + if not args.dry_run: + print(f"完成。产物在 {DIST_DIR}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/packaging/run_client.py b/packaging/run_client.py new file mode 100644 index 0000000..9f76536 --- /dev/null +++ b/packaging/run_client.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""SVFS Client 的打包入口(PyInstaller / Nuitka 共用)。 + +不能用 ``src/svfs_client/__main__.py`` 直接当主脚本——里面有相对导入 +(``from .app import main``),作为顶层脚本运行会 ImportError。 +冻结工具都从这里的绝对导入起步。 +""" + +import sys + +from svfs_client.__main__ import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/packaging/run_studio.py b/packaging/run_studio.py new file mode 100644 index 0000000..05af92f --- /dev/null +++ b/packaging/run_studio.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""SVFS Studio 的打包入口(PyInstaller / Nuitka 共用)。 + +不能用 ``src/svfs_studio/__main__.py`` 直接当主脚本——里面有相对导入 +(``from .app import main``),作为顶层脚本运行会 ImportError。 +冻结工具都从这里的绝对导入起步。 +""" + +import sys + +from svfs_studio.__main__ import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/packaging/svfs_client.spec b/packaging/svfs_client.spec new file mode 100644 index 0000000..f495d66 --- /dev/null +++ b/packaging/svfs_client.spec @@ -0,0 +1,142 @@ +# -*- mode: python ; coding: utf-8 -*- +"""PyInstaller 打包配置:SVFS Client(PySide6 + QtWebEngine 插件客户端)。 + +用法(或直接用 packaging/build.py 编排):: + + uv sync --extra gui + uv pip install pyinstaller + uv run pyinstaller --noconfirm packaging/svfs_client.spec + # 目录版产物: dist/SVFS-Client/SVFS-Client.exe + +单文件版(QtWebEngine 启动前要先解包到临时目录,明显更慢):: + + SVFS_ONEFILE=1 uv run pyinstaller --noconfirm packaging/svfs_client.spec + +QtWebEngine 的资源(icudtl.dat、qtwebengine_resources.pak、locale、 +QtWebEngineProcess.exe)由 pyinstaller-hooks-contrib 的 PySide6 钩子自动收集, +本 spec 不需要手工列——但要保证 PyInstaller >= 6。 +""" + +import os +import re + +from PyInstaller.utils.hooks import collect_submodules + +# spec 文件在 /packaging/ 下,仓库根与 src 由此反推 +REPO = os.path.dirname(os.path.dirname(os.path.abspath(SPEC))) +SRC = os.path.join(REPO, "src") +ONEFILE = os.environ.get("SVFS_ONEFILE", "") == "1" + +ICON = os.path.join(REPO, "resources", "ico", "logo.ico") + +a = Analysis( + [os.path.join(REPO, "packaging", "run_client.py")], + pathex=[SRC], + binaries=[], + datas=[], + hiddenimports=[ + # QtWebEngine 链条显式列出,确保 WebEngine 钩子必定触发 + "PySide6.QtWebEngineWidgets", + "PySide6.QtWebEngineCore", + "PySide6.QtWebChannel", + *collect_submodules("svfs_client"), + *collect_submodules("sqlite_vfs"), + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=["tkinter"], + noarchive=False, +) + +# ---------------------------------------------------------------------- +# 体积裁剪(白名单制):PySide6 的钩子会把 wheel 里**所有** Qt 模块的 +# DLL/pyd 整包搬进来(Quick3D 全家、QuickControls2、Pdf、OpenGL、Svg…… +# 约 70M 的没人用的东西),黑名单删不干净,这里只留依赖链上的: +# +# * Qt6WebEngineCore 实测导入(objdump):Core/Gui/Network/Positioning/Qml/Quick/ +# WebChannel;WebEngineWidgets 另需 PrintSupport/QuickWidgets/Widgets; +# Qt6Quick 另需 Qt6OpenGL 与 QmlMeta/QmlModels —— 这些必须留。 +# * pyd 只留 Python 层真实用到的(实测 import 链):QtCore/QtGui/QtWidgets/ +# QtNetwork/QtPrintSupport/QtWebChannel/QtWebEngineCore/QtWebEngineWidgets; +# 其余绑定(Qml/Quick/QuickWidgets/Positioning/OpenGL)没有 Python 调用者, +# 它们的 DLL 由上面的 Qt6 白名单保留。 +# * opengl32sw.dll(20M 软件渲染回退)删:Chromium 自带 SwiftShader, +# 冻结包深度冒烟(--open 加载页面)通过。 +# * DevTools 资源包(85M)、QML 组件目录(29M)、译文、虚拟键盘、qmltooling: +# 生产分发用不到(DevTools 代价:插件页面里检查元素无资源,调试用开发环境)。 +# 裁不动:Qt6WebEngineCore.dll ~195M(Chromium 本体)与 icudtl.dat 10M。 +# ---------------------------------------------------------------------- +KEEP_QT6_DLLS = { + "Qt6Core", "Qt6Gui", "Qt6Widgets", "Qt6Network", "Qt6Positioning", + "Qt6Qml", "Qt6QmlMeta", "Qt6QmlModels", "Qt6QmlWorkerScript", + "Qt6Quick", "Qt6OpenGL", "Qt6WebChannel", "Qt6PrintSupport", + "Qt6QuickWidgets", "Qt6WebEngineCore", "Qt6WebEngineWidgets", +} +KEEP_PYDS = {"QtCore", "QtGui", "QtWidgets", "QtNetwork", "QtPrintSupport", + "QtWebChannel", "QtWebEngineCore", "QtWebEngineWidgets"} + +QT_DLL_RE = re.compile(r"PySide6[/\\](Qt6[^/\\]*\.dll)$", re.IGNORECASE) +PYD_RE = re.compile(r"PySide6[/\\]([^/\\]*\.pyd)$", re.IGNORECASE) +DROP_CLIENT = re.compile( + r"PySide6[/\\](?:" + r"resources[/\\]qtwebengine_devtools_resources[^/\\]*\.pak" + r"|qml[/\\]" + r"|translations[/\\]" + r"|plugins[/\\]virtualkeyboard[/\\]" + r"|plugins[/\\]qmltooling[/\\]" + r"|opengl32sw\.dll" + r")", + re.IGNORECASE, +) + + +def _keep(entry_name): + if DROP_CLIENT.search(entry_name): + return False + match = QT_DLL_RE.search(entry_name) + if match: + return match.group(1).rsplit(".", 1)[0] in KEEP_QT6_DLLS + match = PYD_RE.search(entry_name) + if match: + return match.group(1).rsplit(".", 1)[0] in KEEP_PYDS + return True + + +a.binaries = [entry for entry in a.binaries if _keep(entry[0])] +a.datas = [entry for entry in a.datas if _keep(entry[0])] + +pyz = PYZ(a.pure) + +exe_kwargs = dict( + name="SVFS-Client", + console=False, # GUI 程序,不弹控制台 + icon=ICON if os.path.exists(ICON) else None, + upx=False, +) + +if ONEFILE: + exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + **exe_kwargs, + ) +else: + exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + **exe_kwargs, + ) + coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=False, + name="SVFS-Client", + ) diff --git a/packaging/svfs_studio.spec b/packaging/svfs_studio.spec new file mode 100644 index 0000000..ef7e7af --- /dev/null +++ b/packaging/svfs_studio.spec @@ -0,0 +1,126 @@ +# -*- mode: python ; coding: utf-8 -*- +"""PyInstaller 打包配置:SVFS Studio(PySide6 桌面工具,无 WebEngine)。 + +用法(或直接用 packaging/build.py 编排):: + + uv sync --extra gui + uv pip install pyinstaller + uv run pyinstaller --noconfirm packaging/svfs_studio.spec + # 目录版产物: dist/SVFS-Studio/SVFS-Studio.exe + +单文件版(启动稍慢,Qt 会先解包到临时目录):: + + SVFS_ONEFILE=1 uv run pyinstaller --noconfirm packaging/svfs_studio.spec +""" + +import os +import re + +from PyInstaller.utils.hooks import collect_submodules + +# spec 文件在 /packaging/ 下,仓库根与 src 由此反推 +REPO = os.path.dirname(os.path.dirname(os.path.abspath(SPEC))) +SRC = os.path.join(REPO, "src") +ONEFILE = os.environ.get("SVFS_ONEFILE", "") == "1" + +ICON = os.path.join(REPO, "resources", "ico", "logo.ico") + +a = Analysis( + [os.path.join(REPO, "packaging", "run_studio.py")], + pathex=[SRC], + binaries=[], + datas=[ + # app_icon() / repo_root() 在冻结环境下从包内找 resources/(见 sample.py) + (os.path.join(REPO, "resources", "ico"), "resources/ico"), + (os.path.join(REPO, "resources", "images"), "resources/images"), + ], + hiddenimports=[ + # 界面里通过 getattr(build_) 取视图工厂,收集整个包最稳妥 + *collect_submodules("svfs_studio"), + *collect_submodules("sqlite_vfs"), + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=["tkinter"], + noarchive=False, +) + +# ---------------------------------------------------------------------- +# 体积裁剪(白名单制):PySide6 的钩子会把 wheel 里**所有** Qt 模块的 +# DLL/pyd 整包搬进来,而本应用只用 QtCore/QtGui/QtWidgets: +# * 未使用的 Qt 模块 DLL(Qml/Quick/Quick3D/Pdf/Network/OpenGL/Svg/ +# VirtualKeyboard/ShaderTools…,约 25M) +# * 未使用的 python 绑定 pyd +# * opengl32sw.dll(20M 软件渲染回退;Widgets 走光栅绘制用不到, +# 无显卡的机器出问题就把它加回 KEEP 外的例外) +# * Qt 自带译文(*.qm,6.7M;界面文案全部硬编码中文) +# * 依赖被删模块的插件(networkinformation/tls/qsvg) +# ---------------------------------------------------------------------- +KEEP_QT6_DLLS = {"Qt6Core", "Qt6Gui", "Qt6Widgets"} +KEEP_PYDS = {"QtCore", "QtGui", "QtWidgets"} + +QT_DLL_RE = re.compile(r"PySide6[/\\](Qt6[^/\\]*\.dll)$", re.IGNORECASE) +PYD_RE = re.compile(r"PySide6[/\\]([^/\\]*\.pyd)$", re.IGNORECASE) +DROP_STUDIO = re.compile( + r"PySide6[/\\](?:" + r"opengl32sw\.dll" + r"|d3dcompiler_47\.dll" + r"|translations[/\\]" + r"|plugins[/\\](?:networkinformation|tls)[/\\]" + r"|plugins[/\\]imageformats[/\\]qsvg\.dll" + r"|plugins[/\\]iconengines[/\\]qsvgicon\.dll" + r")", + re.IGNORECASE, +) + + +def _keep(entry_name): + if DROP_STUDIO.search(entry_name): + return False + match = QT_DLL_RE.search(entry_name) + if match: + return match.group(1).rsplit(".", 1)[0] in KEEP_QT6_DLLS + match = PYD_RE.search(entry_name) + if match: + return match.group(1).rsplit(".", 1)[0] in KEEP_PYDS + return True + + +a.binaries = [entry for entry in a.binaries if _keep(entry[0])] +a.datas = [entry for entry in a.datas if _keep(entry[0])] + +pyz = PYZ(a.pure) + +exe_kwargs = dict( + name="SVFS-Studio", + console=False, # GUI 程序,不弹控制台 + icon=ICON if os.path.exists(ICON) else None, + upx=False, +) + +if ONEFILE: + exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + **exe_kwargs, + ) +else: + exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + **exe_kwargs, + ) + coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=False, + name="SVFS-Studio", + ) diff --git a/pyproject.toml b/pyproject.toml index c59ab28..ff910bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,5 @@ [project] -dependencies = [ - "twine>=6.2.0", -] +dependencies = [] description = "sqlite virtual file system" name = "sqlite-vfs" readme = "README.md" @@ -12,7 +10,7 @@ readme = "README.md" # 仓库里没有 3.10+ 的语法(无 match、无 PEP 604 注解、无 zip(strict=))。 # 说明:这里只在本机的 3.13 上跑过,"3.9 可用"是代码审查的结论,没有在 3.9 上实测。 requires-python = ">=3.9" -version = "0.2.0" +version = "0.3.0" # 可选集成:核心功能只用标准库,第三方框架一律走 extras。 # 例如 pip install "sqlite-vfs[fastapi]" 才会装上 FastAPI/Starlette。 @@ -20,6 +18,25 @@ version = "0.2.0" fastapi = [ "fastapi>=0.100", "starlette>=0.27", + "uvicorn>=0.23", + "python-multipart>=0.0.6", +] +# 桌面工具用的 Qt 绑定。核心库与 CLI 依然零第三方依赖,所以放在 extra 里。 +# +# 三个 extra 的分工(都装着 PySide6,区别只在要不要 WebEngine): +# studio —— svfs-studio(仅 QtCore/QtGui/QtWidgets,Essentials 就够) +# client —— svfs-client(QWebEngineView 在 PySide6-Essentials 里**没有**, +# 必须完整版) +# gui —— 两个桌面应用都装的合集,保留它是为了不破坏既有文档里的 +# `uv sync --extra gui` / `sqlite-vfs[gui]` 写法 +studio = [ + "PySide6-Essentials>=6.6", +] +client = [ + "PySide6>=6.6", +] +gui = [ + "PySide6>=6.6", ] [[tool.uv.index]] @@ -37,15 +54,27 @@ requires = ["hatchling"] [dependency-groups] dev = [ "twine>=4.0.2", + # starlette 1.6+ 的 TestClient(tests/test_plugin_server.py 用)需要它 + "httpx2>=0.1", ] [project.scripts] +# 统一入口(推荐):svfs <子命令> +svfs = "sqlite_vfs.cli.main:main" +# 旧的独立命令保留为别名(与 svfs <同名子命令> 同一段实现) vfspack = "sqlite_vfs.cli.packer:pack" vfsunpack = "sqlite_vfs.cli.unpacker:unpack" vfsverify = "sqlite_vfs.cli.verify:verify" vfsmigrate = "sqlite_vfs.cli.migrate:main" vfspublish = "sqlite_vfs.cli.publish:publish_objects" vfsfetch = "sqlite_vfs.cli.fetch:fetch_objects" +svfs-studio = "svfs_studio.__main__:main" +svfs-client = "svfs_client.__main__:main" [tool.hatch.build.targets.wheel] -packages = ["src/sqlite_vfs"] +# 三个包必须一起打:`svfs-studio` / `svfs-client` 两个 console script 在上面 +# 声明了,svfs_client 还 import svfs_studio 的主题与基础部件(UI 套件)—— +# 只打 sqlite_vfs 的话,pip 装完命令存在但模块不在(ImportError)。 +# 不装 Qt([gui] extra)也能安全安装:这三个包在导入前都不会碰 PySide6 +# (纯标准库层见 svfs_client/__init__.py 的分层说明)。 +packages = ["src/sqlite_vfs", "src/svfs_studio", "src/svfs_client"] diff --git a/uv.lock b/uv.lock index c530f30..445ca08 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.9" resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", "python_full_version > '3.9' and python_full_version < '3.10'", "python_full_version <= '3.9'", ] @@ -34,7 +35,8 @@ name = "annotated-types" version = "0.8.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } wheels = [ @@ -64,7 +66,8 @@ name = "anyio" version = "4.15.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, @@ -150,10 +153,10 @@ name = "cffi" version = "2.1.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ - { name = "pycparser", version = "3.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", version = "3.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "(python_full_version >= '3.10' and python_full_version < '3.12' and implementation_name != 'PyPy') or (python_full_version >= '3.10' and implementation_name != 'PyPy' and sys_platform != 'emscripten')" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } wheels = [ @@ -382,6 +385,44 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, ] +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "47.0.0" @@ -433,12 +474,12 @@ name = "cryptography" version = "50.0.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", "python_full_version > '3.9' and python_full_version < '3.10'", ] dependencies = [ { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version > '3.9' and python_full_version < '3.10' and platform_python_implementation != 'PyPy'" }, - { name = "cffi", version = "2.1.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", version = "2.1.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "(python_full_version >= '3.10' and python_full_version < '3.12' and platform_python_implementation != 'PyPy') or (python_full_version >= '3.10' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten')" }, { name = "typing-extensions", marker = "python_full_version > '3.9' and python_full_version < '3.11'" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } @@ -490,7 +531,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform != 'emscripten') or (python_full_version < '3.12' and sys_platform == 'emscripten')" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -522,7 +563,8 @@ name = "fastapi" version = "0.141.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "annotated-doc", marker = "python_full_version >= '3.10'" }, @@ -536,13 +578,104 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" }, ] +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore2" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "h11", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/18/e51f5729684acaaab4d1cfca0777ac0f86440c65d2814992442ed5ef02e1/httpcore2-2.0.0.tar.gz", hash = "sha256:403692e0a0e8ea6de90993cdea815b2454d2ff5426e61d2a244846c12506af76", size = 63864, upload-time = "2026-05-12T17:59:22.073Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/67/784fd58a5b4dbe8b2aee0cafc6959cb8b53c1b7e9d6e4281f8a4c6935c10/httpcore2-2.0.0-py3-none-any.whl", hash = "sha256:4bd78675c48edafd522c5224a18e3a5f6e71dbe95a73497177d432072a7b6b43", size = 79673, upload-time = "2026-05-12T17:59:18.996Z" }, +] + +[[package]] +name = "httpcore2" +version = "2.13.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +dependencies = [ + { name = "h11", marker = "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')" }, + { name = "truststore", marker = "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/8c/e925b1c92018abb3a1863ce1549d76d2381e334d21d65d4ac8f65dabd78a/httpcore2-2.13.0.tar.gz", hash = "sha256:2adc8be4fb285fbcd6d894298db3b52c177e74b6674eda3a76bd36be3292a3db", size = 67740, upload-time = "2026-09-14T14:18:04.717Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/0d/117a771a2bb91df334b66bf4da14cd02f21aefbcfe53180f336ce55e8f90/httpcore2-2.13.0-py3-none-any.whl", hash = "sha256:35ae5be347aa40467b4a5dc032ac67ebb6d27189fc97e8cebcf99616f6a1bb9e", size = 83162, upload-time = "2026-09-14T14:18:02.529Z" }, +] + +[[package]] +name = "httpx2" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "anyio", version = "4.12.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "certifi", marker = "python_full_version < '3.10'" }, + { name = "httpcore2", version = "2.0.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "idna", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/95/868b3df4aa6f9cecb42aaf82a55a5a2c8ef40df76d709b812e1553d6a928/httpx2-2.0.0.tar.gz", hash = "sha256:f354249d2a9edce26e08fd2ad2276e98317b5763f673bb0d90f3ce9382cc2aed", size = 79506, upload-time = "2026-05-12T17:59:23.174Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/25/66215a750faeb79b698286c0d0b94894482cfae10371ae4968ef6e165a28/httpx2-2.0.0-py3-none-any.whl", hash = "sha256:dfb5ae245f3985681296a000453f1e5fffee322eb531feabcdaf1557ad978b6a", size = 73424, upload-time = "2026-05-12T17:59:20.403Z" }, +] + +[[package]] +name = "httpx2" +version = "2.13.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +dependencies = [ + { name = "anyio", version = "4.15.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, + { name = "httpcore2", version = "2.13.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, + { name = "idna", marker = "python_full_version >= '3.10'" }, + { name = "truststore", marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/a0/e9deef4654132857b5a5dbe4eddd0ac59c2814500e11f2f5044cd81103ee/httpx2-2.13.0.tar.gz", hash = "sha256:81bd07dc67a3701729ef1f777a3c00c915d4539604fdb5afd327f8682f6b7b44", size = 100290, upload-time = "2026-09-14T14:18:05.486Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/d1/a0c72b0e006df654709fbc366cc5bcb53e5aee13e1e3395152c6dd293376/httpx2-2.13.0-py3-none-any.whl", hash = "sha256:fc12720cedf72faa26cca6b4ca394e05c894e7d7933fc45cafe767960804e49a", size = 95565, upload-time = "2026-09-14T14:18:03.553Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + [[package]] name = "id" version = "1.6.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } dependencies = [ { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.8.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } wheels = [ @@ -579,10 +712,10 @@ name = "importlib-metadata" version = "9.0.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ - { name = "zipp", version = "4.1.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "zipp", version = "4.1.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/7e/1e7e8dc30634b93ebb3d58a3dea569ad146e656218d3960ab04f62047b29/importlib_metadata-9.0.1.tar.gz", hash = "sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99", size = 59124, upload-time = "2026-08-28T15:30:34.646Z" } wheels = [ @@ -623,7 +756,8 @@ name = "jaraco-context" version = "6.1.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "backports-tarfile", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, @@ -654,7 +788,8 @@ name = "jaraco-functools" version = "4.6.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "more-itertools", version = "11.1.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, @@ -716,7 +851,8 @@ name = "markdown-it-py" version = "4.2.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "mdurl", marker = "python_full_version >= '3.10'" }, @@ -753,7 +889,8 @@ name = "more-itertools" version = "11.1.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } wheels = [ @@ -821,7 +958,7 @@ name = "pycparser" version = "3.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ @@ -984,6 +1121,153 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/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 = "pyside6" +version = "6.10.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "pyside6-addons", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyside6-essentials", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "shiboken6", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/7a/0423ed0ac2794ae082c2cb303490cde245c880df6d6e4bd2f42633cb97e7/pyside6-6.10.3-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:2d31695fcce3c89ffc621011c6409c0ba1930000d7172dea48994a7744205deb", size = 563312, upload-time = "2026-04-02T08:51:11.566Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/7f/0e4927abee64073df6fb4065ec1ee8be243247aae14e8354162e9f949379/pyside6-6.10.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:36a12f000622e77ef0d98597d2640ae8775954ed900b578eacb6c9fca6cf78fb", size = 563485, upload-time = "2026-04-02T08:51:13.134Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/32/a966aa87e72c3e2429fb4f02472c43f5993cc895c18acc315c62253e40f8/pyside6-6.10.3-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:236a5f4782cbd821cdb92d76c3ff409e6197736bf328d18c483074a7d988793f", size = 563485, upload-time = "2026-04-02T08:51:14.769Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/b2/0b12acdc85a05034917ceecea4fe1aaed91c72fa3de6211666a572566174/pyside6-6.10.3-cp39-abi3-win_amd64.whl", hash = "sha256:c7847e4ae7b1aef1aad822ca7f175c2100c4f9f3af679125cbbdf2b364b32007", size = 569697, upload-time = "2026-04-02T08:51:16.114Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/31/23d4017b777ead5253772b95e5c97bdc4ae4861137ed5302d17cd8bcd11a/pyside6-6.10.3-cp39-abi3-win_arm64.whl", hash = "sha256:cd6b5eb0ebaf950b5de1d5333b6bd4c94eeffb8f449b6257f259e263ab7781a5", size = 553965, upload-time = "2026-04-02T08:51:17.705Z" }, +] + +[[package]] +name = "pyside6" +version = "6.11.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +dependencies = [ + { name = "pyside6-addons", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyside6-essentials", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "shiboken6", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/b5/99ff75f604d69eda1c5d1fe85cc43fb697d95eabc30ba86af099c2ddcd96/pyside6-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:13a3c79816879d9743672a669f1988f74459a78f89a97a2624fae4fe059ffa3a", size = 571875, upload-time = "2026-08-18T06:37:27.794Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/fe/968844def58150a41568b505edca0d68df6901b20ea4222ccb0c069cc703/pyside6-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:dc6d03990489a5085842770718392ef5de36352d8f608d9a9fe03e60af4d7b66", size = 572120, upload-time = "2026-08-18T06:37:30.298Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/5c/f4ecc6defaec645e5ac3ca2f99e3da286a28d1c647c2d10b584e0392b94c/pyside6-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:57fe867a6c93821a085d74e8a26f863fc2363516848181c466032595b33b1729", size = 572121, upload-time = "2026-08-18T06:37:32.383Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/fe/4aea9d30300fff4cda9bb729c1363b9e1095a8544e0d6b57065465e16599/pyside6-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:3201d67e3c10be2eaedd3910ff0f02351eca7e88c95a291cde5e7f2f55ef207f", size = 578382, upload-time = "2026-08-18T06:37:34.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/b0/8e2fb01e9ee15ad29fe10d0dd2d37f503e4aa3714c9fbb07aca380618de7/pyside6-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:0444ac71d0791a19bded35f6d9a94515941f23a6eb7098ca45a1b64a338be697", size = 561768, upload-time = "2026-08-18T06:37:36.437Z" }, +] + +[[package]] +name = "pyside6-addons" +version = "6.10.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "pyside6-essentials", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "shiboken6", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/ca/e898773690b41908d83b137e8ef08e3de7c6b22b5fdff3074bade12af2b3/pyside6_addons-6.10.3-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:f2a2e3a7650cd1599996871da5bea76b7fb4b219bc2b4f7927739e86bc1fb9d9", size = 322840060, upload-time = "2026-04-02T08:47:23.58Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/fb/9df1103a6b71c35a223ea7ba7898c3aa3b1fb91d1aaf85b78f8a9a468121/pyside6_addons-6.10.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a9a53a824ba8429953345c89a2dd5dc65d1f1d7e88b6cf1b975a6ef4f4d9760f", size = 170760635, upload-time = "2026-04-02T08:47:40.365Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/85/c357b77987339a5eb7ff35a50bb16bcf900780b9a17c36e4f8ac7eccb0e6/pyside6_addons-6.10.3-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9938da9ad0d47966cf2064e5d26536e8b6ebcca074c54b2e2bfd0648cd248962", size = 166476210, upload-time = "2026-04-02T08:47:56.771Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/5d/28dc9b8c926c86530c42cab4ed4f1bc43f025da8b7d8a39d3e18f8a937f3/pyside6_addons-6.10.3-cp39-abi3-win_amd64.whl", hash = "sha256:b465b38d1ec3476a9197441fe6a62a1fac97c45a8094f11c2d7e318aee1e3fb3", size = 164743124, upload-time = "2026-04-02T08:48:13.811Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/e8/d2e0b729e13ea9004a57dbf96cd0a5620c8d7379c147a61be8739cafda30/pyside6_addons-6.10.3-cp39-abi3-win_arm64.whl", hash = "sha256:2a23d7ba09deaa35dbcdd171eb4d6251147b6a197a6f4c647d8e6416706861d0", size = 34048174, upload-time = "2026-04-02T08:48:20.015Z" }, +] + +[[package]] +name = "pyside6-addons" +version = "6.11.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +dependencies = [ + { name = "pyside6-essentials", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "shiboken6", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/91/339fe9f7c94163815004ccd6b5c58b926b2dfb4e51c3b5563d412eab151f/pyside6_addons-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:354c574a839f7d0751960ba03f33bec87c95e0f2796dca3287f54d03abe97dac", size = 332047075, upload-time = "2026-08-18T06:35:33.037Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/cd/ce470e20f68901f4dfa5f742d6a3a58ba865684a232e4b87d5a1a2179cc0/pyside6_addons-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a8b00956925fbdeffc7052cf933506391289f35ac4f3f71a0a167ec195cd47b4", size = 175062576, upload-time = "2026-08-18T06:35:45.583Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/bd/f725a8984703612c72cd17d0871d047132bacaccc13fc0d288a5e8a37dad/pyside6_addons-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a2ca3c73e5f060d4aca49451abd9fba68dd0e1c66ff40cea3a1fe0415def579a", size = 170827254, upload-time = "2026-08-18T06:35:56.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/c1/08bf6594101b42fc88834009f1dac59a983c5b6805c62c5081f077d09b4a/pyside6_addons-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:f449ea4431da20e7b86752cca8d166f93434516fe417f981c27e5f8e1b554407", size = 168208836, upload-time = "2026-08-18T06:36:07.295Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/60/e4b7f09343e9f29e565f488fb44c4f1fe260db481ba2c08ad8a1a7d48216/pyside6_addons-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:d5606af369484b862b4d5741cfc179e19d2b8819c3e04d210b8188e0c9f85988", size = 35807245, upload-time = "2026-08-18T06:36:12.485Z" }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.10.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "shiboken6", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/09/b14fae948aa38140079dd585c4c11fe42f12ef3be5a417603512547c232f/pyside6_essentials-6.10.3-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:8d7e2b826ac7f043dbfff487552c220517003927f6cfa14ab6a4824e8623a686", size = 106014872, upload-time = "2026-04-02T08:48:41.107Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/78/1b2bc96e720c17b35f8d6f54a13d32fbcd4cb8a8fc4a3dba8741711a3ab0/pyside6_essentials-6.10.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:5fb32b48010f513335a2166ac3df279c5996fb96402fd91a4875fc717fe55de0", size = 77077386, upload-time = "2026-04-02T08:48:47.9Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/9d/90c6c356f7a261be3e40d655baaec394622cd58b14fd98fdd582ed0c644a/pyside6_essentials-6.10.3-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:6f1b226930828caef622aef0f1fe718db54849b29102617bc978c0120368ed6b", size = 76166253, upload-time = "2026-04-02T08:48:56.2Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/11/7cfcfc74213b8e33aecef6fa05dcedf45e65385155f07486a514a9777b0d/pyside6_essentials-6.10.3-cp39-abi3-win_amd64.whl", hash = "sha256:051a70e2df79cf1c012a208ad82a98e8de23ca7fe3b0f3344d07152d15705b59", size = 74610897, upload-time = "2026-04-02T08:49:06.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/53/d43a9f3ac21fbaa9ba517b9d5f28297a4eb11ded6a85b625d71b1e6f3f49/pyside6_essentials-6.10.3-cp39-abi3-win_arm64.whl", hash = "sha256:f9124e2f4a4cedba83bea11f712b152093ff91be3512e17c64ffc4f43f02bddd", size = 55296520, upload-time = "2026-04-02T08:49:15.249Z" }, +] + +[[package]] +name = "pyside6-essentials" +version = "6.11.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +dependencies = [ + { name = "shiboken6", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/16/0b7ecf89ebada82ed0430be33809ff325761ece83104f8635b1bd101fcd0/pyside6_essentials-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:77795c145202e65a78d88f7cd409d186e3ba23d159bdb3ba2dcd159ae5e5f0d9", size = 110800753, upload-time = "2026-08-18T06:36:33.674Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/1a/84a84bbc75835ee88abdcb66eb875b8e1a21834af9b833bc1893f2c1bfaf/pyside6_essentials-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:aaf9f25f0f324874085fa5b26a610318db8a8e243cf85bb3e5400595191c7778", size = 80108947, upload-time = "2026-08-18T06:36:40.637Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/4c/d92b08a2151d095b9a3725fa0e71b215ec686d40bbd5d8b702d21a9b5eaa/pyside6_essentials-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:d3ec6e1885c46e57f16364f38ea8040ffc5dc0b18341058f608fede3ba930567", size = 79575497, upload-time = "2026-08-18T06:36:46.97Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/09/d2662684aee4d3f027ff9b1e965f22301bc28ffc5b7f167658508e4475dc/pyside6_essentials-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:c8a29def77032773a30879f7f24415b5395ad08592d147c170824ef4c735dfc1", size = 76913043, upload-time = "2026-08-18T06:36:54.038Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/87/4dd2a6468cea600da1fcf9c7f02bc248eeaa98cdc75658e48e669d95326a/pyside6_essentials-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:fadd75c5c20800d64dd0a586ea8cb337c5e630aa2246df0e5105738afa46e02c", size = 58125520, upload-time = "2026-08-18T06:36:59.073Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -1016,7 +1300,8 @@ name = "readme-renderer" version = "46.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "docutils", marker = "python_full_version >= '3.10'" }, @@ -1052,13 +1337,14 @@ name = "requests" version = "2.34.2" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "certifi", marker = "python_full_version >= '3.10'" }, { name = "charset-normalizer", marker = "python_full_version >= '3.10'" }, { name = "idna", marker = "python_full_version >= '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.8.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } wheels = [ @@ -1124,11 +1410,11 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ - { name = "cryptography", version = "50.0.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "jeepney", marker = "python_full_version >= '3.10'" }, + { name = "cryptography", version = "50.0.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')" }, + { name = "jeepney", marker = "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -1136,24 +1422,70 @@ wheels = [ ] [[package]] -name = "sqlite-vfs" -version = "0.2.0" -source = { editable = "." } -dependencies = [ - { name = "twine", version = "6.2.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, - { name = "twine", version = "7.0.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, +name = "shiboken6" +version = "6.10.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/0d/ac1380fbb4d247be6b82931797826d8d18d59d96a48d8395d61cca0a4862/shiboken6-6.10.3-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:3827489c2b3059541a71678916716f2867eeeac8724adb6f82de6e69d1b5cfe8", size = 479931, upload-time = "2026-04-02T08:49:31.875Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/c5/cbc20e31c0d7c47adeb5a6557991ce0eaab7a0288ee3b6aa25edaf3215d7/shiboken6-6.10.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:99e8ac3319a2c93a4a8f8bb4512d71a9d43bc9f226f094a80a728d6dcc3e4826", size = 273084, upload-time = "2026-04-02T08:49:33.36Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/e3/ee446f92a8fd84a46230e6fb811cd914857802a0987ee91d87738e6bad20/shiboken6-6.10.3-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:49d08bff0c36edb839aca0a8059fd771c81b4a0770f38f374542aae03f860c07", size = 269936, upload-time = "2026-04-02T08:49:34.662Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/6a/488cf05549e3888dff5424e7713e29e45955c14f2041bf99f3f493b1acc8/shiboken6-6.10.3-cp39-abi3-win_amd64.whl", hash = "sha256:38261c88e87a1c32d1302b642b755470d2369c78af3c7d7d9e95d631f79b556c", size = 1222126, upload-time = "2026-04-02T08:49:36.154Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/5a/5be8092d6b7ad9f0c8d96a9db8e3fe0e76f9a75e2a8a96814d06844e9de1/shiboken6-6.10.3-cp39-abi3-win_arm64.whl", hash = "sha256:7f196245985bcf85d5c6d1cfd870f139484d5259c16e8a42deddacca0671bfbe", size = 1784185, upload-time = "2026-04-02T08:49:38.01Z" }, ] +[[package]] +name = "shiboken6" +version = "6.11.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/44/11bf71c36e71936ab4e923e5dd23599dab527c4488b01860b0f689ce9947/shiboken6-6.11.2-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:53659683b1f7a08e9f87eff9b1065f1ceb7110cd7a4bc09fdf5efe43d286604d", size = 476674, upload-time = "2026-08-18T06:37:11.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/9a/f41bea4316d0c1bec4800842dcb4bd8fd3e29d6a79c496bc32e1aec42583/shiboken6-6.11.2-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7a7a0a72a9ed26c9bf77d42246b1c736486befb8f31aa2fb29957ea4cdd1c1c2", size = 272435, upload-time = "2026-08-18T06:37:13.397Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/05/79ed0697515d4fc7435459e4ed4c4a3b5621a55b7c91d0f3f2cc3d617aa0/shiboken6-6.11.2-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:4bbbd6fa4d7cff5ec5e12bc4c10e1d845fa30c89457ecb13cc64f7bddb77f6f9", size = 270668, upload-time = "2026-08-18T06:37:15.025Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/6b/fee1b4814317e4664e318644c9c007676c65a71e5b2b9d3ac4121a94dc0a/shiboken6-6.11.2-cp310-abi3-win_amd64.whl", hash = "sha256:6ab0eba1c904455df621f9a6df3ca2bb896bab8670572d2bc4e37804ae91f19a", size = 1226578, upload-time = "2026-08-18T06:37:18.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/b5/80c4e5aafeb8ac777cf5a1becfe87ac7197c35051df00b5f7819787e09d5/shiboken6-6.11.2-cp310-abi3-win_arm64.whl", hash = "sha256:97c49432488df958f0308736f3d15b6915d8826fc380af17bb4b9ec5c4a5e81b", size = 1784669, upload-time = "2026-08-18T06:37:21.046Z" }, +] + +[[package]] +name = "sqlite-vfs" +version = "0.3.0" +source = { editable = "." } + [package.optional-dependencies] +client = [ + { name = "pyside6", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyside6", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, +] fastapi = [ { name = "fastapi", version = "0.128.8", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, { name = "fastapi", version = "0.141.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "python-multipart", version = "0.0.32", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, { name = "starlette", version = "1.6.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "uvicorn", version = "0.53.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, +] +gui = [ + { name = "pyside6", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyside6", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, +] +studio = [ + { name = "pyside6-essentials", version = "6.10.3", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyside6-essentials", version = "6.11.2", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, ] [package.dev-dependencies] dev = [ + { name = "httpx2", version = "2.0.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "httpx2", version = "2.13.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, { name = "twine", version = "6.2.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, { name = "twine", version = "7.0.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -1161,13 +1493,20 @@ dev = [ [package.metadata] requires-dist = [ { name = "fastapi", marker = "extra == 'fastapi'", specifier = ">=0.100" }, + { name = "pyside6", marker = "extra == 'client'", specifier = ">=6.6" }, + { name = "pyside6", marker = "extra == 'gui'", specifier = ">=6.6" }, + { name = "pyside6-essentials", marker = "extra == 'studio'", specifier = ">=6.6" }, + { name = "python-multipart", marker = "extra == 'fastapi'", specifier = ">=0.0.6" }, { name = "starlette", marker = "extra == 'fastapi'", specifier = ">=0.27" }, - { name = "twine", specifier = ">=6.2.0" }, + { name = "uvicorn", marker = "extra == 'fastapi'", specifier = ">=0.23" }, ] -provides-extras = ["fastapi"] +provides-extras = ["fastapi", "studio", "client", "gui"] [package.metadata.requires-dev] -dev = [{ name = "twine", specifier = ">=4.0.2" }] +dev = [ + { name = "httpx2", specifier = ">=0.1" }, + { name = "twine", specifier = ">=4.0.2" }, +] [[package]] name = "starlette" @@ -1191,7 +1530,8 @@ name = "starlette" version = "1.6.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "anyio", version = "4.15.1", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, @@ -1202,6 +1542,69 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" }, ] +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "twine" version = "6.2.0" @@ -1232,7 +1635,8 @@ name = "twine" version = "7.0.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "id", marker = "python_full_version >= '3.10'" }, @@ -1243,7 +1647,7 @@ dependencies = [ { name = "requests-toolbelt", marker = "python_full_version >= '3.10'" }, { name = "rfc3986", marker = "python_full_version >= '3.10'" }, { name = "rich", marker = "python_full_version >= '3.10'" }, - { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3", version = "2.8.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz", hash = "sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size = 215032, upload-time = "2026-07-27T15:59:00.825Z" } wheels = [ @@ -1280,7 +1684,8 @@ name = "typing-inspection" version = "0.4.4" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] dependencies = [ { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, @@ -1305,14 +1710,51 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] -sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.39.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version < '3.10'" }, + { name = "h11", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/4f/f9fdac7cf6dd79790eb165639b5c452ceeabc7bbabbba4569155470a287d/uvicorn-0.39.0.tar.gz", hash = "sha256:610512b19baa93423d2892d7823741f6d27717b642c8964000d7194dded19302", size = 82001, upload-time = "2025-12-21T13:05:17.973Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/25/db2b1c6c35bf22e17fe5412d2ee5d3fd7a20d07ebc9dac8b58f7db2e23a0/uvicorn-0.39.0-py3-none-any.whl", hash = "sha256:7beec21bd2693562b386285b188a7963b06853c0d006302b3e4cfed950c9929a", size = 68491, upload-time = "2025-12-21T13:05:16.291Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.53.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'emscripten'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", +] +dependencies = [ + { name = "click", version = "8.5.0", source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "h11", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/ad/04bbb797c84fc1f26cb171f7394716f4865ffb8d8c5e1eef42565c2dfa6b/uvicorn-0.53.0.tar.gz", hash = "sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e", size = 110881, upload-time = "2026-09-14T07:44:23.815Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/18/0eea75741ee812e9f598b687619ce2454f6c3a1c5cd21ea990ec6bd26f45/uvicorn-0.53.0-py3-none-any.whl", hash = "sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e", size = 87081, upload-time = "2026-09-14T07:44:22.179Z" }, ] [[package]] @@ -1333,7 +1775,7 @@ name = "zipp" version = "4.1.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } resolution-markers = [ - "python_full_version >= '3.10'", + "(python_full_version >= '3.10' and python_full_version < '3.12') or (python_full_version >= '3.10' and sys_platform != 'emscripten')", ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } wheels = [