diff --git a/.gitignore b/.gitignore index 505a3b1..4be2b89 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ wheels/ # Virtual environments .venv + +# Database +*.db +*.sqlite3 + +# Logs +*.log diff --git a/CHANGELOG b/CHANGELOG index c316006..57397a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ # CHANGELOG +- [2026-09-20 08:14] FEAT: 订单模块 v0.1 实现与二维平面图全量采集——FastAPI 分层架构(api/services/repositories/models)、订单状态机(意向单→正式单→生产中→完工/取消)、7节点生产进度、审计日志、JWT认证、样板库API;创建契约对齐目标站{contract_id,drawing_data,pic_url};254张样板平面图全部下载成功并静态挂载;端到端冒烟测试7/7通过 (Files: src/huamenchuang/**, pyproject.toml, README.md, doc/API.md, data/windoor-images/*, data/fetch-images.py) - [2026-09-20 07:18] FEAT: 整站全量采集——GET 扫描 279 个接口全部留档(业务成功76)、POST 安全探测 24 个(付费墙/参数契约确认)、订单写入接口契约经源码静态分析补全(orders CRUD + 创建订单请求体);doc/API.md 新增 §11 采集结果与付费墙语义 (Files: data/full-site/*, doc/API.md, doc/克隆实施方案.md) - [2026-09-20 06:41] FEAT: 完成目标站逆向采集——样板库 254 套模板 drawing_data 全量采集(254/254)、16 个核心接口响应样本、约 300 条接口清单;输出《克隆实施方案》《API 接口文档》(Files: doc/克隆实施方案.md, doc/API.md, data/api-endpoint-inventory.txt, data/api-samples/*, data/windoor-drawings/*) - [2026-09-20 06:41] DOCS: 初始化 README 项目导航 (Files: README.md) + diff --git a/README.md b/README.md index 1480b44..a72e580 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,16 @@ 门窗行业云平台(功能对标 windoorcraft.com,全部自研实现)。 +## 当前进度 + +- ✅ 目标站逆向与数据采集(样板库 254 套全量 + 二维平面图 254 张 + 接口全量留档) +- ✅ 后端 v0.1:认证 / 订单模块(状态机+生产进度+审计日志)/ 样板库 API +- ⏳ 前端 Vue3 与画布编辑器(规划中,见方案 §3.3) + ## 文档导航 - [克隆实施方案](doc/克隆实施方案.md) — 产品画像、架构设计、拖拽画布编辑器复刻方案、里程碑 -- [API 接口文档](doc/API.md) — 接口规范与目标站对照表 +- [API 接口文档](doc/API.md) — 目标站对照表 + 自研接口规范(§12) - [变更日志](CHANGELOG) ## 目录 @@ -15,14 +21,21 @@ doc/ 设计文档 data/ ├── api-endpoint-inventory.txt 目标站接口名清单(≈300 条) ├── api-samples/ 核心接口真实响应样本 + 样板库全量清单 -└── windoor-drawings/ 254 套官方样板库完整绘制数据 (drawing_data) -src/huamenchuang/ 后端(FastAPI,规划中) +├── windoor-drawings/ 254 套官方样板完整绘制数据 (drawing_data) +├── windoor-images/ 254 张样板二维平面图 +└── full-site/ 整站接口全量扫描留档 +src/huamenchuang/ 后端(FastAPI,分层: api/services/repositories/models) frontend/ 前端(Vue3 + TS,规划中) ``` -## 开发 +## 快速开始 ```bash -uv sync # 安装后端依赖(规划) -uv run uvicorn huamenchuang.main:app --reload +uv sync # 安装依赖 +uv run python -m huamenchuang.scripts.seed # 管理员 + 254 套样板入库 +uv run uvicorn huamenchuang.main:app --reload # 启动 → http://localhost:8000/docs ``` + +管理员账号(开发):`13800000000 / 123456`。 + +环境变量(均可选):`HMC_DATABASE_URL`(默认 SQLite,可切 MySQL)、`HMC_JWT_SECRET`(生产必设)、`HMC_ENABLED_MODULES`(套餐模块开关)。 diff --git a/data/fetch-images.py b/data/fetch-images.py new file mode 100644 index 0000000..fce7018 --- /dev/null +++ b/data/fetch-images.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""下载 254 套样板的二维平面图(pic_url,公开 OSS)。限速 0.2s。""" +import json, os, time, urllib.request, urllib.error + +templates = json.load(open(r"D:\Code\huamenchuang\data\api-samples\windoor-templates-clean.json", encoding="utf-8")) +OUT = r"D:\Code\huamenchuang\data\windoor-images" +os.makedirs(OUT, exist_ok=True) +ok, skip, fail = 0, 0, [] +for i, t in enumerate(templates): + ext = ".png" + url = t["pic_url"] + if url.lower().endswith((".jpg", ".jpeg")): + ext = ".jpg" + out = os.path.join(OUT, f"{t['id']}{ext}") + if os.path.exists(out) and os.path.getsize(out) > 100: + skip += 1 + continue + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + try: + with urllib.request.urlopen(req, timeout=20) as r: + data = r.read() + open(out, "wb").write(data) + ok += 1 + except Exception as e: + fail.append((t["id"], str(e)[:60])) + if (i + 1) % 50 == 0: + print(f"进度 {i+1}/{len(templates)} 成功{ok} 跳过{skip}", flush=True) + time.sleep(0.2) +print(f"完成: 新下载 {ok}, 已存在 {skip}, 失败 {len(fail)}") +if fail: + print(fail[:10]) diff --git a/data/windoor-images/31337.png b/data/windoor-images/31337.png new file mode 100644 index 0000000..1f8a0ef Binary files /dev/null and b/data/windoor-images/31337.png differ diff --git a/data/windoor-images/31338.png b/data/windoor-images/31338.png new file mode 100644 index 0000000..2951c54 Binary files /dev/null and b/data/windoor-images/31338.png differ diff --git a/data/windoor-images/31340.png b/data/windoor-images/31340.png new file mode 100644 index 0000000..e5f69d7 Binary files /dev/null and b/data/windoor-images/31340.png differ diff --git a/data/windoor-images/31341.png b/data/windoor-images/31341.png new file mode 100644 index 0000000..e5137c3 Binary files /dev/null and b/data/windoor-images/31341.png differ diff --git a/data/windoor-images/31342.png b/data/windoor-images/31342.png new file mode 100644 index 0000000..a23b43a Binary files /dev/null and b/data/windoor-images/31342.png differ diff --git a/data/windoor-images/31343.png b/data/windoor-images/31343.png new file mode 100644 index 0000000..8a756b1 Binary files /dev/null and b/data/windoor-images/31343.png differ diff --git a/data/windoor-images/31344.png b/data/windoor-images/31344.png new file mode 100644 index 0000000..a32f2e4 Binary files /dev/null and b/data/windoor-images/31344.png differ diff --git a/data/windoor-images/31345.png b/data/windoor-images/31345.png new file mode 100644 index 0000000..24c12d9 Binary files /dev/null and b/data/windoor-images/31345.png differ diff --git a/data/windoor-images/31346.png b/data/windoor-images/31346.png new file mode 100644 index 0000000..cf3356e Binary files /dev/null and b/data/windoor-images/31346.png differ diff --git a/data/windoor-images/31347.png b/data/windoor-images/31347.png new file mode 100644 index 0000000..436ef1d Binary files /dev/null and b/data/windoor-images/31347.png differ diff --git a/data/windoor-images/31348.png b/data/windoor-images/31348.png new file mode 100644 index 0000000..281ac90 Binary files /dev/null and b/data/windoor-images/31348.png differ diff --git a/data/windoor-images/31349.png b/data/windoor-images/31349.png new file mode 100644 index 0000000..66b3d8f Binary files /dev/null and b/data/windoor-images/31349.png differ diff --git a/data/windoor-images/31350.png b/data/windoor-images/31350.png new file mode 100644 index 0000000..c9b985a Binary files /dev/null and b/data/windoor-images/31350.png differ diff --git a/data/windoor-images/31351.png b/data/windoor-images/31351.png new file mode 100644 index 0000000..4965a33 Binary files /dev/null and b/data/windoor-images/31351.png differ diff --git a/data/windoor-images/31352.png b/data/windoor-images/31352.png new file mode 100644 index 0000000..6db9af7 Binary files /dev/null and b/data/windoor-images/31352.png differ diff --git a/data/windoor-images/31353.png b/data/windoor-images/31353.png new file mode 100644 index 0000000..799e0d8 Binary files /dev/null and b/data/windoor-images/31353.png differ diff --git a/data/windoor-images/31374.png b/data/windoor-images/31374.png new file mode 100644 index 0000000..a852e6f Binary files /dev/null and b/data/windoor-images/31374.png differ diff --git a/data/windoor-images/31375.png b/data/windoor-images/31375.png new file mode 100644 index 0000000..4d92255 Binary files /dev/null and b/data/windoor-images/31375.png differ diff --git a/data/windoor-images/31376.png b/data/windoor-images/31376.png new file mode 100644 index 0000000..11559ea Binary files /dev/null and b/data/windoor-images/31376.png differ diff --git a/data/windoor-images/31593.png b/data/windoor-images/31593.png new file mode 100644 index 0000000..8f37bce Binary files /dev/null and b/data/windoor-images/31593.png differ diff --git a/data/windoor-images/31594.png b/data/windoor-images/31594.png new file mode 100644 index 0000000..48c53b5 Binary files /dev/null and b/data/windoor-images/31594.png differ diff --git a/data/windoor-images/31595.png b/data/windoor-images/31595.png new file mode 100644 index 0000000..dd4c406 Binary files /dev/null and b/data/windoor-images/31595.png differ diff --git a/data/windoor-images/31596.png b/data/windoor-images/31596.png new file mode 100644 index 0000000..944252c Binary files /dev/null and b/data/windoor-images/31596.png differ diff --git a/data/windoor-images/31597.png b/data/windoor-images/31597.png new file mode 100644 index 0000000..c1fb168 Binary files /dev/null and b/data/windoor-images/31597.png differ diff --git a/data/windoor-images/31598.png b/data/windoor-images/31598.png new file mode 100644 index 0000000..2fe99de Binary files /dev/null and b/data/windoor-images/31598.png differ diff --git a/data/windoor-images/31600.png b/data/windoor-images/31600.png new file mode 100644 index 0000000..f99f690 Binary files /dev/null and b/data/windoor-images/31600.png differ diff --git a/data/windoor-images/31603.png b/data/windoor-images/31603.png new file mode 100644 index 0000000..a0854fa Binary files /dev/null and b/data/windoor-images/31603.png differ diff --git a/data/windoor-images/31604.png b/data/windoor-images/31604.png new file mode 100644 index 0000000..512771e Binary files /dev/null and b/data/windoor-images/31604.png differ diff --git a/data/windoor-images/31605.png b/data/windoor-images/31605.png new file mode 100644 index 0000000..b5e975f Binary files /dev/null and b/data/windoor-images/31605.png differ diff --git a/data/windoor-images/31606.png b/data/windoor-images/31606.png new file mode 100644 index 0000000..89b4461 Binary files /dev/null and b/data/windoor-images/31606.png differ diff --git a/data/windoor-images/31607.png b/data/windoor-images/31607.png new file mode 100644 index 0000000..2756664 Binary files /dev/null and b/data/windoor-images/31607.png differ diff --git a/data/windoor-images/31608.png b/data/windoor-images/31608.png new file mode 100644 index 0000000..a79ee3a Binary files /dev/null and b/data/windoor-images/31608.png differ diff --git a/data/windoor-images/31609.png b/data/windoor-images/31609.png new file mode 100644 index 0000000..9854de4 Binary files /dev/null and b/data/windoor-images/31609.png differ diff --git a/data/windoor-images/31610.png b/data/windoor-images/31610.png new file mode 100644 index 0000000..84d3d63 Binary files /dev/null and b/data/windoor-images/31610.png differ diff --git a/data/windoor-images/31611.png b/data/windoor-images/31611.png new file mode 100644 index 0000000..daebac4 Binary files /dev/null and b/data/windoor-images/31611.png differ diff --git a/data/windoor-images/31612.png b/data/windoor-images/31612.png new file mode 100644 index 0000000..02a2684 Binary files /dev/null and b/data/windoor-images/31612.png differ diff --git a/data/windoor-images/31613.png b/data/windoor-images/31613.png new file mode 100644 index 0000000..87ed1eb Binary files /dev/null and b/data/windoor-images/31613.png differ diff --git a/data/windoor-images/31614.png b/data/windoor-images/31614.png new file mode 100644 index 0000000..d385f6e Binary files /dev/null and b/data/windoor-images/31614.png differ diff --git a/data/windoor-images/31615.png b/data/windoor-images/31615.png new file mode 100644 index 0000000..f152061 Binary files /dev/null and b/data/windoor-images/31615.png differ diff --git a/data/windoor-images/31616.png b/data/windoor-images/31616.png new file mode 100644 index 0000000..d192458 Binary files /dev/null and b/data/windoor-images/31616.png differ diff --git a/data/windoor-images/31635.png b/data/windoor-images/31635.png new file mode 100644 index 0000000..7770fe2 Binary files /dev/null and b/data/windoor-images/31635.png differ diff --git a/data/windoor-images/31636.png b/data/windoor-images/31636.png new file mode 100644 index 0000000..8a43808 Binary files /dev/null and b/data/windoor-images/31636.png differ diff --git a/data/windoor-images/31637.png b/data/windoor-images/31637.png new file mode 100644 index 0000000..3a39cbe Binary files /dev/null and b/data/windoor-images/31637.png differ diff --git a/data/windoor-images/31639.png b/data/windoor-images/31639.png new file mode 100644 index 0000000..b0afd0d Binary files /dev/null and b/data/windoor-images/31639.png differ diff --git a/data/windoor-images/31640.png b/data/windoor-images/31640.png new file mode 100644 index 0000000..d6ae015 Binary files /dev/null and b/data/windoor-images/31640.png differ diff --git a/data/windoor-images/31641.png b/data/windoor-images/31641.png new file mode 100644 index 0000000..a0edf41 Binary files /dev/null and b/data/windoor-images/31641.png differ diff --git a/data/windoor-images/31642.png b/data/windoor-images/31642.png new file mode 100644 index 0000000..16fc30f Binary files /dev/null and b/data/windoor-images/31642.png differ diff --git a/data/windoor-images/31643.png b/data/windoor-images/31643.png new file mode 100644 index 0000000..0e17b76 Binary files /dev/null and b/data/windoor-images/31643.png differ diff --git a/data/windoor-images/31653.png b/data/windoor-images/31653.png new file mode 100644 index 0000000..d25b73f Binary files /dev/null and b/data/windoor-images/31653.png differ diff --git a/data/windoor-images/31654.png b/data/windoor-images/31654.png new file mode 100644 index 0000000..cde7de5 Binary files /dev/null and b/data/windoor-images/31654.png differ diff --git a/data/windoor-images/31655.png b/data/windoor-images/31655.png new file mode 100644 index 0000000..045e7e2 Binary files /dev/null and b/data/windoor-images/31655.png differ diff --git a/data/windoor-images/31656.png b/data/windoor-images/31656.png new file mode 100644 index 0000000..b0d4a40 Binary files /dev/null and b/data/windoor-images/31656.png differ diff --git a/data/windoor-images/31657.png b/data/windoor-images/31657.png new file mode 100644 index 0000000..1a79d2d Binary files /dev/null and b/data/windoor-images/31657.png differ diff --git a/data/windoor-images/31658.png b/data/windoor-images/31658.png new file mode 100644 index 0000000..5c5e693 Binary files /dev/null and b/data/windoor-images/31658.png differ diff --git a/data/windoor-images/31659.png b/data/windoor-images/31659.png new file mode 100644 index 0000000..7dcf1f5 Binary files /dev/null and b/data/windoor-images/31659.png differ diff --git a/data/windoor-images/31660.png b/data/windoor-images/31660.png new file mode 100644 index 0000000..6818e05 Binary files /dev/null and b/data/windoor-images/31660.png differ diff --git a/data/windoor-images/31686.png b/data/windoor-images/31686.png new file mode 100644 index 0000000..73ad344 Binary files /dev/null and b/data/windoor-images/31686.png differ diff --git a/data/windoor-images/31687.png b/data/windoor-images/31687.png new file mode 100644 index 0000000..5f79f72 Binary files /dev/null and b/data/windoor-images/31687.png differ diff --git a/data/windoor-images/31688.png b/data/windoor-images/31688.png new file mode 100644 index 0000000..0a6a691 Binary files /dev/null and b/data/windoor-images/31688.png differ diff --git a/data/windoor-images/31689.png b/data/windoor-images/31689.png new file mode 100644 index 0000000..b924fdb Binary files /dev/null and b/data/windoor-images/31689.png differ diff --git a/data/windoor-images/31690.png b/data/windoor-images/31690.png new file mode 100644 index 0000000..35d0ca8 Binary files /dev/null and b/data/windoor-images/31690.png differ diff --git a/data/windoor-images/31691.png b/data/windoor-images/31691.png new file mode 100644 index 0000000..7d90d46 Binary files /dev/null and b/data/windoor-images/31691.png differ diff --git a/data/windoor-images/31692.png b/data/windoor-images/31692.png new file mode 100644 index 0000000..f0a4f4e Binary files /dev/null and b/data/windoor-images/31692.png differ diff --git a/data/windoor-images/31693.png b/data/windoor-images/31693.png new file mode 100644 index 0000000..e76b81a Binary files /dev/null and b/data/windoor-images/31693.png differ diff --git a/data/windoor-images/31694.png b/data/windoor-images/31694.png new file mode 100644 index 0000000..037b274 Binary files /dev/null and b/data/windoor-images/31694.png differ diff --git a/data/windoor-images/31697.png b/data/windoor-images/31697.png new file mode 100644 index 0000000..f283f38 Binary files /dev/null and b/data/windoor-images/31697.png differ diff --git a/data/windoor-images/31698.png b/data/windoor-images/31698.png new file mode 100644 index 0000000..8a056c9 Binary files /dev/null and b/data/windoor-images/31698.png differ diff --git a/data/windoor-images/31699.png b/data/windoor-images/31699.png new file mode 100644 index 0000000..4469e87 Binary files /dev/null and b/data/windoor-images/31699.png differ diff --git a/data/windoor-images/31700.png b/data/windoor-images/31700.png new file mode 100644 index 0000000..beca13b Binary files /dev/null and b/data/windoor-images/31700.png differ diff --git a/data/windoor-images/31701.png b/data/windoor-images/31701.png new file mode 100644 index 0000000..ccd542c Binary files /dev/null and b/data/windoor-images/31701.png differ diff --git a/data/windoor-images/31702.png b/data/windoor-images/31702.png new file mode 100644 index 0000000..5f6555e Binary files /dev/null and b/data/windoor-images/31702.png differ diff --git a/data/windoor-images/31703.png b/data/windoor-images/31703.png new file mode 100644 index 0000000..80a5746 Binary files /dev/null and b/data/windoor-images/31703.png differ diff --git a/data/windoor-images/31704.png b/data/windoor-images/31704.png new file mode 100644 index 0000000..809085b Binary files /dev/null and b/data/windoor-images/31704.png differ diff --git a/data/windoor-images/31705.png b/data/windoor-images/31705.png new file mode 100644 index 0000000..7c8a47a Binary files /dev/null and b/data/windoor-images/31705.png differ diff --git a/data/windoor-images/31706.png b/data/windoor-images/31706.png new file mode 100644 index 0000000..57ba04a Binary files /dev/null and b/data/windoor-images/31706.png differ diff --git a/data/windoor-images/31707.png b/data/windoor-images/31707.png new file mode 100644 index 0000000..2a89757 Binary files /dev/null and b/data/windoor-images/31707.png differ diff --git a/data/windoor-images/31708.png b/data/windoor-images/31708.png new file mode 100644 index 0000000..dd02abf Binary files /dev/null and b/data/windoor-images/31708.png differ diff --git a/data/windoor-images/31709.png b/data/windoor-images/31709.png new file mode 100644 index 0000000..efcc5fa Binary files /dev/null and b/data/windoor-images/31709.png differ diff --git a/data/windoor-images/31711.png b/data/windoor-images/31711.png new file mode 100644 index 0000000..39a563b Binary files /dev/null and b/data/windoor-images/31711.png differ diff --git a/data/windoor-images/31712.png b/data/windoor-images/31712.png new file mode 100644 index 0000000..9ae8b4a Binary files /dev/null and b/data/windoor-images/31712.png differ diff --git a/data/windoor-images/31713.png b/data/windoor-images/31713.png new file mode 100644 index 0000000..044a82e Binary files /dev/null and b/data/windoor-images/31713.png differ diff --git a/data/windoor-images/31714.png b/data/windoor-images/31714.png new file mode 100644 index 0000000..1aed49c Binary files /dev/null and b/data/windoor-images/31714.png differ diff --git a/data/windoor-images/31715.png b/data/windoor-images/31715.png new file mode 100644 index 0000000..efcc5fa Binary files /dev/null and b/data/windoor-images/31715.png differ diff --git a/data/windoor-images/31716.png b/data/windoor-images/31716.png new file mode 100644 index 0000000..fb045c9 Binary files /dev/null and b/data/windoor-images/31716.png differ diff --git a/data/windoor-images/31717.png b/data/windoor-images/31717.png new file mode 100644 index 0000000..28b1581 Binary files /dev/null and b/data/windoor-images/31717.png differ diff --git a/data/windoor-images/31718.png b/data/windoor-images/31718.png new file mode 100644 index 0000000..f1e68cd Binary files /dev/null and b/data/windoor-images/31718.png differ diff --git a/data/windoor-images/31719.png b/data/windoor-images/31719.png new file mode 100644 index 0000000..0fc567a Binary files /dev/null and b/data/windoor-images/31719.png differ diff --git a/data/windoor-images/31720.png b/data/windoor-images/31720.png new file mode 100644 index 0000000..617e702 Binary files /dev/null and b/data/windoor-images/31720.png differ diff --git a/data/windoor-images/31721.png b/data/windoor-images/31721.png new file mode 100644 index 0000000..84b1d92 Binary files /dev/null and b/data/windoor-images/31721.png differ diff --git a/data/windoor-images/31722.png b/data/windoor-images/31722.png new file mode 100644 index 0000000..e8cb543 Binary files /dev/null and b/data/windoor-images/31722.png differ diff --git a/data/windoor-images/31723.png b/data/windoor-images/31723.png new file mode 100644 index 0000000..6b79945 Binary files /dev/null and b/data/windoor-images/31723.png differ diff --git a/data/windoor-images/31724.png b/data/windoor-images/31724.png new file mode 100644 index 0000000..84fb61e Binary files /dev/null and b/data/windoor-images/31724.png differ diff --git a/data/windoor-images/31725.png b/data/windoor-images/31725.png new file mode 100644 index 0000000..a0b053c Binary files /dev/null and b/data/windoor-images/31725.png differ diff --git a/data/windoor-images/31726.png b/data/windoor-images/31726.png new file mode 100644 index 0000000..10f6984 Binary files /dev/null and b/data/windoor-images/31726.png differ diff --git a/data/windoor-images/31727.png b/data/windoor-images/31727.png new file mode 100644 index 0000000..e346a07 Binary files /dev/null and b/data/windoor-images/31727.png differ diff --git a/data/windoor-images/31728.png b/data/windoor-images/31728.png new file mode 100644 index 0000000..b13cbf4 Binary files /dev/null and b/data/windoor-images/31728.png differ diff --git a/data/windoor-images/31729.png b/data/windoor-images/31729.png new file mode 100644 index 0000000..084515e Binary files /dev/null and b/data/windoor-images/31729.png differ diff --git a/data/windoor-images/31730.png b/data/windoor-images/31730.png new file mode 100644 index 0000000..9ae4163 Binary files /dev/null and b/data/windoor-images/31730.png differ diff --git a/data/windoor-images/31731.png b/data/windoor-images/31731.png new file mode 100644 index 0000000..bb41ed3 Binary files /dev/null and b/data/windoor-images/31731.png differ diff --git a/data/windoor-images/31733.png b/data/windoor-images/31733.png new file mode 100644 index 0000000..b87a063 Binary files /dev/null and b/data/windoor-images/31733.png differ diff --git a/data/windoor-images/31734.png b/data/windoor-images/31734.png new file mode 100644 index 0000000..e573aab Binary files /dev/null and b/data/windoor-images/31734.png differ diff --git a/data/windoor-images/31735.png b/data/windoor-images/31735.png new file mode 100644 index 0000000..3395146 Binary files /dev/null and b/data/windoor-images/31735.png differ diff --git a/data/windoor-images/31736.png b/data/windoor-images/31736.png new file mode 100644 index 0000000..5e5ec85 Binary files /dev/null and b/data/windoor-images/31736.png differ diff --git a/data/windoor-images/31737.png b/data/windoor-images/31737.png new file mode 100644 index 0000000..bdac081 Binary files /dev/null and b/data/windoor-images/31737.png differ diff --git a/data/windoor-images/31738.png b/data/windoor-images/31738.png new file mode 100644 index 0000000..51bbf07 Binary files /dev/null and b/data/windoor-images/31738.png differ diff --git a/data/windoor-images/31739.png b/data/windoor-images/31739.png new file mode 100644 index 0000000..583a952 Binary files /dev/null and b/data/windoor-images/31739.png differ diff --git a/data/windoor-images/31740.png b/data/windoor-images/31740.png new file mode 100644 index 0000000..6de71ae Binary files /dev/null and b/data/windoor-images/31740.png differ diff --git a/data/windoor-images/31741.png b/data/windoor-images/31741.png new file mode 100644 index 0000000..b2fd47e Binary files /dev/null and b/data/windoor-images/31741.png differ diff --git a/data/windoor-images/31742.png b/data/windoor-images/31742.png new file mode 100644 index 0000000..99d1ee8 Binary files /dev/null and b/data/windoor-images/31742.png differ diff --git a/data/windoor-images/31934.png b/data/windoor-images/31934.png new file mode 100644 index 0000000..589c4b4 Binary files /dev/null and b/data/windoor-images/31934.png differ diff --git a/data/windoor-images/31935.png b/data/windoor-images/31935.png new file mode 100644 index 0000000..6cbf2f4 Binary files /dev/null and b/data/windoor-images/31935.png differ diff --git a/data/windoor-images/31936.png b/data/windoor-images/31936.png new file mode 100644 index 0000000..19d6e39 Binary files /dev/null and b/data/windoor-images/31936.png differ diff --git a/data/windoor-images/31937.png b/data/windoor-images/31937.png new file mode 100644 index 0000000..5c53e91 Binary files /dev/null and b/data/windoor-images/31937.png differ diff --git a/data/windoor-images/31938.png b/data/windoor-images/31938.png new file mode 100644 index 0000000..39842f1 Binary files /dev/null and b/data/windoor-images/31938.png differ diff --git a/data/windoor-images/31939.png b/data/windoor-images/31939.png new file mode 100644 index 0000000..b93ab0f Binary files /dev/null and b/data/windoor-images/31939.png differ diff --git a/data/windoor-images/31940.png b/data/windoor-images/31940.png new file mode 100644 index 0000000..b852f31 Binary files /dev/null and b/data/windoor-images/31940.png differ diff --git a/data/windoor-images/31941.png b/data/windoor-images/31941.png new file mode 100644 index 0000000..d218c9d Binary files /dev/null and b/data/windoor-images/31941.png differ diff --git a/data/windoor-images/31942.png b/data/windoor-images/31942.png new file mode 100644 index 0000000..fbf469e Binary files /dev/null and b/data/windoor-images/31942.png differ diff --git a/data/windoor-images/31943.png b/data/windoor-images/31943.png new file mode 100644 index 0000000..0ea561d Binary files /dev/null and b/data/windoor-images/31943.png differ diff --git a/data/windoor-images/31944.png b/data/windoor-images/31944.png new file mode 100644 index 0000000..136720f Binary files /dev/null and b/data/windoor-images/31944.png differ diff --git a/data/windoor-images/31945.png b/data/windoor-images/31945.png new file mode 100644 index 0000000..a89e345 Binary files /dev/null and b/data/windoor-images/31945.png differ diff --git a/data/windoor-images/31946.png b/data/windoor-images/31946.png new file mode 100644 index 0000000..4c240a9 Binary files /dev/null and b/data/windoor-images/31946.png differ diff --git a/data/windoor-images/31947.png b/data/windoor-images/31947.png new file mode 100644 index 0000000..27e7994 Binary files /dev/null and b/data/windoor-images/31947.png differ diff --git a/data/windoor-images/31948.png b/data/windoor-images/31948.png new file mode 100644 index 0000000..a72fa78 Binary files /dev/null and b/data/windoor-images/31948.png differ diff --git a/data/windoor-images/31949.png b/data/windoor-images/31949.png new file mode 100644 index 0000000..6337edf Binary files /dev/null and b/data/windoor-images/31949.png differ diff --git a/data/windoor-images/31950.png b/data/windoor-images/31950.png new file mode 100644 index 0000000..9b26fec Binary files /dev/null and b/data/windoor-images/31950.png differ diff --git a/data/windoor-images/32154.png b/data/windoor-images/32154.png new file mode 100644 index 0000000..48ee8e5 Binary files /dev/null and b/data/windoor-images/32154.png differ diff --git a/data/windoor-images/32155.png b/data/windoor-images/32155.png new file mode 100644 index 0000000..3b579af Binary files /dev/null and b/data/windoor-images/32155.png differ diff --git a/data/windoor-images/32156.png b/data/windoor-images/32156.png new file mode 100644 index 0000000..e1ec39f Binary files /dev/null and b/data/windoor-images/32156.png differ diff --git a/data/windoor-images/32157.png b/data/windoor-images/32157.png new file mode 100644 index 0000000..d82bc1d Binary files /dev/null and b/data/windoor-images/32157.png differ diff --git a/data/windoor-images/32158.png b/data/windoor-images/32158.png new file mode 100644 index 0000000..f4783d9 Binary files /dev/null and b/data/windoor-images/32158.png differ diff --git a/data/windoor-images/32159.png b/data/windoor-images/32159.png new file mode 100644 index 0000000..93a71e4 Binary files /dev/null and b/data/windoor-images/32159.png differ diff --git a/data/windoor-images/32170.png b/data/windoor-images/32170.png new file mode 100644 index 0000000..749c393 Binary files /dev/null and b/data/windoor-images/32170.png differ diff --git a/data/windoor-images/32171.png b/data/windoor-images/32171.png new file mode 100644 index 0000000..f228f65 Binary files /dev/null and b/data/windoor-images/32171.png differ diff --git a/data/windoor-images/32172.png b/data/windoor-images/32172.png new file mode 100644 index 0000000..1257e3e Binary files /dev/null and b/data/windoor-images/32172.png differ diff --git a/data/windoor-images/32173.png b/data/windoor-images/32173.png new file mode 100644 index 0000000..f1245e4 Binary files /dev/null and b/data/windoor-images/32173.png differ diff --git a/data/windoor-images/32175.png b/data/windoor-images/32175.png new file mode 100644 index 0000000..d3ad6c8 Binary files /dev/null and b/data/windoor-images/32175.png differ diff --git a/data/windoor-images/32176.png b/data/windoor-images/32176.png new file mode 100644 index 0000000..08dd094 Binary files /dev/null and b/data/windoor-images/32176.png differ diff --git a/data/windoor-images/32177.png b/data/windoor-images/32177.png new file mode 100644 index 0000000..a88058c Binary files /dev/null and b/data/windoor-images/32177.png differ diff --git a/data/windoor-images/32178.png b/data/windoor-images/32178.png new file mode 100644 index 0000000..5eacdd4 Binary files /dev/null and b/data/windoor-images/32178.png differ diff --git a/data/windoor-images/32179.png b/data/windoor-images/32179.png new file mode 100644 index 0000000..17fce59 Binary files /dev/null and b/data/windoor-images/32179.png differ diff --git a/data/windoor-images/32180.png b/data/windoor-images/32180.png new file mode 100644 index 0000000..7e4540d Binary files /dev/null and b/data/windoor-images/32180.png differ diff --git a/data/windoor-images/32181.png b/data/windoor-images/32181.png new file mode 100644 index 0000000..19b7969 Binary files /dev/null and b/data/windoor-images/32181.png differ diff --git a/data/windoor-images/32182.png b/data/windoor-images/32182.png new file mode 100644 index 0000000..19b3b45 Binary files /dev/null and b/data/windoor-images/32182.png differ diff --git a/data/windoor-images/32183.png b/data/windoor-images/32183.png new file mode 100644 index 0000000..1377dd9 Binary files /dev/null and b/data/windoor-images/32183.png differ diff --git a/data/windoor-images/32184.png b/data/windoor-images/32184.png new file mode 100644 index 0000000..5a05492 Binary files /dev/null and b/data/windoor-images/32184.png differ diff --git a/data/windoor-images/32185.png b/data/windoor-images/32185.png new file mode 100644 index 0000000..0607d5a Binary files /dev/null and b/data/windoor-images/32185.png differ diff --git a/data/windoor-images/32186.png b/data/windoor-images/32186.png new file mode 100644 index 0000000..6a7a849 Binary files /dev/null and b/data/windoor-images/32186.png differ diff --git a/data/windoor-images/32187.png b/data/windoor-images/32187.png new file mode 100644 index 0000000..cd0f266 Binary files /dev/null and b/data/windoor-images/32187.png differ diff --git a/data/windoor-images/34584.png b/data/windoor-images/34584.png new file mode 100644 index 0000000..4409218 Binary files /dev/null and b/data/windoor-images/34584.png differ diff --git a/data/windoor-images/34585.png b/data/windoor-images/34585.png new file mode 100644 index 0000000..3af54c7 Binary files /dev/null and b/data/windoor-images/34585.png differ diff --git a/data/windoor-images/34586.png b/data/windoor-images/34586.png new file mode 100644 index 0000000..fb62e1e Binary files /dev/null and b/data/windoor-images/34586.png differ diff --git a/data/windoor-images/34587.png b/data/windoor-images/34587.png new file mode 100644 index 0000000..12d7060 Binary files /dev/null and b/data/windoor-images/34587.png differ diff --git a/data/windoor-images/34588.png b/data/windoor-images/34588.png new file mode 100644 index 0000000..52177f7 Binary files /dev/null and b/data/windoor-images/34588.png differ diff --git a/data/windoor-images/34589.png b/data/windoor-images/34589.png new file mode 100644 index 0000000..a3dca25 Binary files /dev/null and b/data/windoor-images/34589.png differ diff --git a/data/windoor-images/34590.png b/data/windoor-images/34590.png new file mode 100644 index 0000000..ea6829d Binary files /dev/null and b/data/windoor-images/34590.png differ diff --git a/data/windoor-images/34591.png b/data/windoor-images/34591.png new file mode 100644 index 0000000..bc46145 Binary files /dev/null and b/data/windoor-images/34591.png differ diff --git a/data/windoor-images/34592.png b/data/windoor-images/34592.png new file mode 100644 index 0000000..ac75eab Binary files /dev/null and b/data/windoor-images/34592.png differ diff --git a/data/windoor-images/34594.png b/data/windoor-images/34594.png new file mode 100644 index 0000000..cc4d28e Binary files /dev/null and b/data/windoor-images/34594.png differ diff --git a/data/windoor-images/34595.png b/data/windoor-images/34595.png new file mode 100644 index 0000000..51505dd Binary files /dev/null and b/data/windoor-images/34595.png differ diff --git a/data/windoor-images/34596.png b/data/windoor-images/34596.png new file mode 100644 index 0000000..af9228a Binary files /dev/null and b/data/windoor-images/34596.png differ diff --git a/data/windoor-images/34597.png b/data/windoor-images/34597.png new file mode 100644 index 0000000..9fa45a6 Binary files /dev/null and b/data/windoor-images/34597.png differ diff --git a/data/windoor-images/34598.png b/data/windoor-images/34598.png new file mode 100644 index 0000000..6b48dcb Binary files /dev/null and b/data/windoor-images/34598.png differ diff --git a/data/windoor-images/34599.png b/data/windoor-images/34599.png new file mode 100644 index 0000000..19938a6 Binary files /dev/null and b/data/windoor-images/34599.png differ diff --git a/data/windoor-images/34600.png b/data/windoor-images/34600.png new file mode 100644 index 0000000..c802cfa Binary files /dev/null and b/data/windoor-images/34600.png differ diff --git a/data/windoor-images/34601.png b/data/windoor-images/34601.png new file mode 100644 index 0000000..299027b Binary files /dev/null and b/data/windoor-images/34601.png differ diff --git a/data/windoor-images/34602.png b/data/windoor-images/34602.png new file mode 100644 index 0000000..ee898b5 Binary files /dev/null and b/data/windoor-images/34602.png differ diff --git a/data/windoor-images/34603.png b/data/windoor-images/34603.png new file mode 100644 index 0000000..7b7e7e1 Binary files /dev/null and b/data/windoor-images/34603.png differ diff --git a/data/windoor-images/34604.png b/data/windoor-images/34604.png new file mode 100644 index 0000000..1805f32 Binary files /dev/null and b/data/windoor-images/34604.png differ diff --git a/data/windoor-images/34606.png b/data/windoor-images/34606.png new file mode 100644 index 0000000..70bbfda Binary files /dev/null and b/data/windoor-images/34606.png differ diff --git a/data/windoor-images/34795.png b/data/windoor-images/34795.png new file mode 100644 index 0000000..2cb4fa3 Binary files /dev/null and b/data/windoor-images/34795.png differ diff --git a/data/windoor-images/34796.png b/data/windoor-images/34796.png new file mode 100644 index 0000000..a72e794 Binary files /dev/null and b/data/windoor-images/34796.png differ diff --git a/data/windoor-images/34797.png b/data/windoor-images/34797.png new file mode 100644 index 0000000..95d1120 Binary files /dev/null and b/data/windoor-images/34797.png differ diff --git a/data/windoor-images/34798.png b/data/windoor-images/34798.png new file mode 100644 index 0000000..4a929d9 Binary files /dev/null and b/data/windoor-images/34798.png differ diff --git a/data/windoor-images/34799.png b/data/windoor-images/34799.png new file mode 100644 index 0000000..6fb8ab4 Binary files /dev/null and b/data/windoor-images/34799.png differ diff --git a/data/windoor-images/34800.png b/data/windoor-images/34800.png new file mode 100644 index 0000000..e2c8d90 Binary files /dev/null and b/data/windoor-images/34800.png differ diff --git a/data/windoor-images/34801.png b/data/windoor-images/34801.png new file mode 100644 index 0000000..4e3f349 Binary files /dev/null and b/data/windoor-images/34801.png differ diff --git a/data/windoor-images/34802.png b/data/windoor-images/34802.png new file mode 100644 index 0000000..e3e2036 Binary files /dev/null and b/data/windoor-images/34802.png differ diff --git a/data/windoor-images/34803.png b/data/windoor-images/34803.png new file mode 100644 index 0000000..e86f800 Binary files /dev/null and b/data/windoor-images/34803.png differ diff --git a/data/windoor-images/34804.png b/data/windoor-images/34804.png new file mode 100644 index 0000000..3296bdc Binary files /dev/null and b/data/windoor-images/34804.png differ diff --git a/data/windoor-images/34805.png b/data/windoor-images/34805.png new file mode 100644 index 0000000..a7980bb Binary files /dev/null and b/data/windoor-images/34805.png differ diff --git a/data/windoor-images/34806.png b/data/windoor-images/34806.png new file mode 100644 index 0000000..a1f3254 Binary files /dev/null and b/data/windoor-images/34806.png differ diff --git a/data/windoor-images/34807.png b/data/windoor-images/34807.png new file mode 100644 index 0000000..90abeaf Binary files /dev/null and b/data/windoor-images/34807.png differ diff --git a/data/windoor-images/34808.png b/data/windoor-images/34808.png new file mode 100644 index 0000000..209d08e Binary files /dev/null and b/data/windoor-images/34808.png differ diff --git a/data/windoor-images/34809.png b/data/windoor-images/34809.png new file mode 100644 index 0000000..114fd3f Binary files /dev/null and b/data/windoor-images/34809.png differ diff --git a/data/windoor-images/34810.png b/data/windoor-images/34810.png new file mode 100644 index 0000000..8744e22 Binary files /dev/null and b/data/windoor-images/34810.png differ diff --git a/data/windoor-images/34811.png b/data/windoor-images/34811.png new file mode 100644 index 0000000..bdad324 Binary files /dev/null and b/data/windoor-images/34811.png differ diff --git a/data/windoor-images/34812.png b/data/windoor-images/34812.png new file mode 100644 index 0000000..82119ab Binary files /dev/null and b/data/windoor-images/34812.png differ diff --git a/data/windoor-images/34816.png b/data/windoor-images/34816.png new file mode 100644 index 0000000..822cbeb Binary files /dev/null and b/data/windoor-images/34816.png differ diff --git a/data/windoor-images/34818.png b/data/windoor-images/34818.png new file mode 100644 index 0000000..52ce98a Binary files /dev/null and b/data/windoor-images/34818.png differ diff --git a/data/windoor-images/34819.png b/data/windoor-images/34819.png new file mode 100644 index 0000000..2188fa7 Binary files /dev/null and b/data/windoor-images/34819.png differ diff --git a/data/windoor-images/34820.png b/data/windoor-images/34820.png new file mode 100644 index 0000000..a276007 Binary files /dev/null and b/data/windoor-images/34820.png differ diff --git a/data/windoor-images/34821.png b/data/windoor-images/34821.png new file mode 100644 index 0000000..16978aa Binary files /dev/null and b/data/windoor-images/34821.png differ diff --git a/data/windoor-images/34822.png b/data/windoor-images/34822.png new file mode 100644 index 0000000..386a98e Binary files /dev/null and b/data/windoor-images/34822.png differ diff --git a/data/windoor-images/34823.png b/data/windoor-images/34823.png new file mode 100644 index 0000000..f6ada84 Binary files /dev/null and b/data/windoor-images/34823.png differ diff --git a/data/windoor-images/34824.png b/data/windoor-images/34824.png new file mode 100644 index 0000000..0076bfc Binary files /dev/null and b/data/windoor-images/34824.png differ diff --git a/data/windoor-images/34825.png b/data/windoor-images/34825.png new file mode 100644 index 0000000..5c71d8e Binary files /dev/null and b/data/windoor-images/34825.png differ diff --git a/data/windoor-images/34826.png b/data/windoor-images/34826.png new file mode 100644 index 0000000..b0089c3 Binary files /dev/null and b/data/windoor-images/34826.png differ diff --git a/data/windoor-images/34827.png b/data/windoor-images/34827.png new file mode 100644 index 0000000..592bf5a Binary files /dev/null and b/data/windoor-images/34827.png differ diff --git a/data/windoor-images/34828.png b/data/windoor-images/34828.png new file mode 100644 index 0000000..66d7a5c Binary files /dev/null and b/data/windoor-images/34828.png differ diff --git a/data/windoor-images/34829.png b/data/windoor-images/34829.png new file mode 100644 index 0000000..3c05701 Binary files /dev/null and b/data/windoor-images/34829.png differ diff --git a/data/windoor-images/34830.png b/data/windoor-images/34830.png new file mode 100644 index 0000000..b7902b0 Binary files /dev/null and b/data/windoor-images/34830.png differ diff --git a/data/windoor-images/34831.png b/data/windoor-images/34831.png new file mode 100644 index 0000000..711296d Binary files /dev/null and b/data/windoor-images/34831.png differ diff --git a/data/windoor-images/34833.png b/data/windoor-images/34833.png new file mode 100644 index 0000000..154eb66 Binary files /dev/null and b/data/windoor-images/34833.png differ diff --git a/data/windoor-images/34834.png b/data/windoor-images/34834.png new file mode 100644 index 0000000..0e6787e Binary files /dev/null and b/data/windoor-images/34834.png differ diff --git a/data/windoor-images/34835.png b/data/windoor-images/34835.png new file mode 100644 index 0000000..13541dc Binary files /dev/null and b/data/windoor-images/34835.png differ diff --git a/data/windoor-images/34836.png b/data/windoor-images/34836.png new file mode 100644 index 0000000..19fe539 Binary files /dev/null and b/data/windoor-images/34836.png differ diff --git a/data/windoor-images/34837.png b/data/windoor-images/34837.png new file mode 100644 index 0000000..f11f5b3 Binary files /dev/null and b/data/windoor-images/34837.png differ diff --git a/data/windoor-images/34838.png b/data/windoor-images/34838.png new file mode 100644 index 0000000..0f548df Binary files /dev/null and b/data/windoor-images/34838.png differ diff --git a/data/windoor-images/34839.png b/data/windoor-images/34839.png new file mode 100644 index 0000000..bc559d7 Binary files /dev/null and b/data/windoor-images/34839.png differ diff --git a/data/windoor-images/34840.png b/data/windoor-images/34840.png new file mode 100644 index 0000000..23978e8 Binary files /dev/null and b/data/windoor-images/34840.png differ diff --git a/data/windoor-images/34841.png b/data/windoor-images/34841.png new file mode 100644 index 0000000..e6aaf7c Binary files /dev/null and b/data/windoor-images/34841.png differ diff --git a/data/windoor-images/34842.png b/data/windoor-images/34842.png new file mode 100644 index 0000000..441dd49 Binary files /dev/null and b/data/windoor-images/34842.png differ diff --git a/data/windoor-images/34844.png b/data/windoor-images/34844.png new file mode 100644 index 0000000..85afaed Binary files /dev/null and b/data/windoor-images/34844.png differ diff --git a/data/windoor-images/34936.png b/data/windoor-images/34936.png new file mode 100644 index 0000000..9f61c0d Binary files /dev/null and b/data/windoor-images/34936.png differ diff --git a/data/windoor-images/35692.png b/data/windoor-images/35692.png new file mode 100644 index 0000000..1d8410b Binary files /dev/null and b/data/windoor-images/35692.png differ diff --git a/data/windoor-images/35693.png b/data/windoor-images/35693.png new file mode 100644 index 0000000..9654151 Binary files /dev/null and b/data/windoor-images/35693.png differ diff --git a/data/windoor-images/35694.png b/data/windoor-images/35694.png new file mode 100644 index 0000000..3ccb9d4 Binary files /dev/null and b/data/windoor-images/35694.png differ diff --git a/data/windoor-images/35695.png b/data/windoor-images/35695.png new file mode 100644 index 0000000..7eebbd8 Binary files /dev/null and b/data/windoor-images/35695.png differ diff --git a/data/windoor-images/35697.png b/data/windoor-images/35697.png new file mode 100644 index 0000000..7730839 Binary files /dev/null and b/data/windoor-images/35697.png differ diff --git a/data/windoor-images/35698.png b/data/windoor-images/35698.png new file mode 100644 index 0000000..c8a6c15 Binary files /dev/null and b/data/windoor-images/35698.png differ diff --git a/data/windoor-images/35699.png b/data/windoor-images/35699.png new file mode 100644 index 0000000..42be653 Binary files /dev/null and b/data/windoor-images/35699.png differ diff --git a/data/windoor-images/35700.png b/data/windoor-images/35700.png new file mode 100644 index 0000000..6c45ee8 Binary files /dev/null and b/data/windoor-images/35700.png differ diff --git a/data/windoor-images/35701.png b/data/windoor-images/35701.png new file mode 100644 index 0000000..d333cb1 Binary files /dev/null and b/data/windoor-images/35701.png differ diff --git a/data/windoor-images/35702.png b/data/windoor-images/35702.png new file mode 100644 index 0000000..0104e1c Binary files /dev/null and b/data/windoor-images/35702.png differ diff --git a/data/windoor-images/35705.png b/data/windoor-images/35705.png new file mode 100644 index 0000000..e2948a0 Binary files /dev/null and b/data/windoor-images/35705.png differ diff --git a/data/windoor-images/35706.png b/data/windoor-images/35706.png new file mode 100644 index 0000000..10fa2cf Binary files /dev/null and b/data/windoor-images/35706.png differ diff --git a/data/windoor-images/35707.png b/data/windoor-images/35707.png new file mode 100644 index 0000000..c0c5294 Binary files /dev/null and b/data/windoor-images/35707.png differ diff --git a/data/windoor-images/35708.png b/data/windoor-images/35708.png new file mode 100644 index 0000000..2d9810e Binary files /dev/null and b/data/windoor-images/35708.png differ diff --git a/data/windoor-images/35709.png b/data/windoor-images/35709.png new file mode 100644 index 0000000..c03fc24 Binary files /dev/null and b/data/windoor-images/35709.png differ diff --git a/data/windoor-images/35710.png b/data/windoor-images/35710.png new file mode 100644 index 0000000..eb2e47c Binary files /dev/null and b/data/windoor-images/35710.png differ diff --git a/data/windoor-images/35711.png b/data/windoor-images/35711.png new file mode 100644 index 0000000..b3c6ce7 Binary files /dev/null and b/data/windoor-images/35711.png differ diff --git a/data/windoor-images/35712.png b/data/windoor-images/35712.png new file mode 100644 index 0000000..ae6f097 Binary files /dev/null and b/data/windoor-images/35712.png differ diff --git a/data/windoor-images/35713.png b/data/windoor-images/35713.png new file mode 100644 index 0000000..0d381c1 Binary files /dev/null and b/data/windoor-images/35713.png differ diff --git a/data/windoor-images/35714.png b/data/windoor-images/35714.png new file mode 100644 index 0000000..cb7330a Binary files /dev/null and b/data/windoor-images/35714.png differ diff --git a/data/windoor-images/35715.png b/data/windoor-images/35715.png new file mode 100644 index 0000000..d4b2115 Binary files /dev/null and b/data/windoor-images/35715.png differ diff --git a/data/windoor-images/35717.png b/data/windoor-images/35717.png new file mode 100644 index 0000000..34455d0 Binary files /dev/null and b/data/windoor-images/35717.png differ diff --git a/data/windoor-images/35719.png b/data/windoor-images/35719.png new file mode 100644 index 0000000..56f8f49 Binary files /dev/null and b/data/windoor-images/35719.png differ diff --git a/data/windoor-images/35721.png b/data/windoor-images/35721.png new file mode 100644 index 0000000..ccdc729 Binary files /dev/null and b/data/windoor-images/35721.png differ diff --git a/data/windoor-images/35723.png b/data/windoor-images/35723.png new file mode 100644 index 0000000..915e2b1 Binary files /dev/null and b/data/windoor-images/35723.png differ diff --git a/data/windoor-images/35724.png b/data/windoor-images/35724.png new file mode 100644 index 0000000..38d31b5 Binary files /dev/null and b/data/windoor-images/35724.png differ diff --git a/data/windoor-images/35725.png b/data/windoor-images/35725.png new file mode 100644 index 0000000..4d17ac2 Binary files /dev/null and b/data/windoor-images/35725.png differ diff --git a/data/windoor-images/35726.png b/data/windoor-images/35726.png new file mode 100644 index 0000000..ed492a7 Binary files /dev/null and b/data/windoor-images/35726.png differ diff --git a/data/windoor-images/35727.png b/data/windoor-images/35727.png new file mode 100644 index 0000000..6b0592d Binary files /dev/null and b/data/windoor-images/35727.png differ diff --git a/data/windoor-images/36287.png b/data/windoor-images/36287.png new file mode 100644 index 0000000..cf08635 Binary files /dev/null and b/data/windoor-images/36287.png differ diff --git a/data/windoor-images/36288.png b/data/windoor-images/36288.png new file mode 100644 index 0000000..08c4754 Binary files /dev/null and b/data/windoor-images/36288.png differ diff --git a/data/windoor-images/36289.png b/data/windoor-images/36289.png new file mode 100644 index 0000000..9ea9078 Binary files /dev/null and b/data/windoor-images/36289.png differ diff --git a/data/windoor-images/36290.png b/data/windoor-images/36290.png new file mode 100644 index 0000000..0a11750 Binary files /dev/null and b/data/windoor-images/36290.png differ diff --git a/data/windoor-images/36291.png b/data/windoor-images/36291.png new file mode 100644 index 0000000..800f49c Binary files /dev/null and b/data/windoor-images/36291.png differ diff --git a/data/windoor-images/36292.png b/data/windoor-images/36292.png new file mode 100644 index 0000000..1984a21 Binary files /dev/null and b/data/windoor-images/36292.png differ diff --git a/data/windoor-images/36293.png b/data/windoor-images/36293.png new file mode 100644 index 0000000..d1cb16c Binary files /dev/null and b/data/windoor-images/36293.png differ diff --git a/data/windoor-images/36294.png b/data/windoor-images/36294.png new file mode 100644 index 0000000..14f1205 Binary files /dev/null and b/data/windoor-images/36294.png differ diff --git a/data/windoor-images/36296.png b/data/windoor-images/36296.png new file mode 100644 index 0000000..68850ab Binary files /dev/null and b/data/windoor-images/36296.png differ diff --git a/data/windoor-images/704.png b/data/windoor-images/704.png new file mode 100644 index 0000000..727fa1c Binary files /dev/null and b/data/windoor-images/704.png differ diff --git a/doc/API.md b/doc/API.md index 9efa678..1cf644a 100644 --- a/doc/API.md +++ b/doc/API.md @@ -182,3 +182,62 @@ - 付费墙模块(code=10086):订单全流程、报价(offer-price)、产品库、订单打印模板、AI 产品 - 到期语义:`pay-page-list` 返回 code=0 "您的账号已经到期,请联系销售人员!";`try_render` 返回 "体验版无法体验实景渲染" - 角色权限:`contract-stat` 返回 code=210 "该账号没有该权限" + +## 12. 自研系统接口规范(已实现 v0.1) + +> 自研基址 `http://localhost:8000/api/`,envelope 与分页结构完全对齐 §0。认证:`Authorization: Bearer `(HS256/24h)。 +> 分层:`api/`(Controller) → `services/`(业务) → `repositories/`(数据) → `models/`(ORM)。 + +### 12.1 认证模块 `/api/auth` + +| 接口名称 | 方式 | URL 路径 | 请求参数 | 响应示例 | +|---|---|---|---|---| +| 登录 | POST | `/api/auth/login` | `{"phone_number":"13800000000","password":"123456"}` | `{"code":1,"data":{"token":"...","expires_hours":24,"user":{...}}}` | +| 注册 | POST | `/api/auth/register` | `{"phone_number","password"}` | `{"code":1,"data":{"token","user_id"}}` | +| 当前用户 | GET | `/api/auth/current-user` | Bearer token | `{"code":1,"data":{"id,nickname,phone_number,company_id,language,try_expiration_time}}` | + +### 12.2 订单模块 `/api/orders`(本节核心,自研设计) + +**订单状态机**:`0意向单 → 1正式单 → 2生产中 → 3已完工`;任意未完工状态可 `4已取消`(终态)。非法迁移返回 code=0。 + +**单号规则**:`DO + YYYYMMDD + 4位流水`(同企业同日递增),例如 `DO202609200001`。 + +**创建订单后自动初始化生产进度节点**:下料/组装/玻璃/打胶/五金/质检/完工(7 节点,与目标站 get_order_process 语义对齐)。 + +| 接口名称 | 方式 | URL 路径 | 请求参数 | 响应示例 | +|---|---|---|---|---| +| **创建订单** | POST | `/api/orders` | `{"contract_id":1,"drawing_data":"{画布JSON}","pic_url":"/static/windoor-images/1.png","customer_id":1,"money":0,"count":0,"area":0,"note":"","status":0}` | `{"code":1,"message":"创建成功","data":{"id":1,"order_no":"DO202609200001","status":"意向单",...}}` | +| 订单搜索 | POST | `/api/orders/search` | `{"page":1,"page_size":15,"keyword":"","status":null,"contract_id":null,"start_date":"","end_date":"","order_by":"created_desc"}` | `{"code":1,"data":{"current_page":1,"data":[...],"total":0,"per_page":15}}` | +| 订单列表 | GET | `/api/orders?page=1&page_size=15` | query | 同上分页结构 | +| **订单详情** | GET | `/api/orders/{id}` | 路径 | `data` 含 `drawing_data`(画布 openFile 直接渲染)与 `processes[7]` | +| 更新订单 | PUT | `/api/orders/{id}` | 基础字段任意子集;`{"action":"change_status","target_status":1}` 执行状态迁移 | `{"code":1,"data":{...}}`;非法迁移 `{"code":0,"message":"不允许的状态迁移: 意向单→已完工"}` | +| 删除订单 | DELETE | `/api/orders/{id}` | 路径 | `{"code":1,"message":"删除成功"}` | +| **更新生产进度** | POST | `/api/orders/{id}/process` | `{"name":"下料","status":2}` (status: 0未开始/1进行中/2完成,自动记 finish_time) | `{"code":1,"data":{"id,"name","status","finish_time"}}` | +| 订单操作日志 | GET | `/api/orders/{id}/logs` | 路径 | `{"code":1,"data":[{"action":"create","detail":"创建订单 DO202609200001","created_at":...}]}` | + +### 12.3 样板库模块 `/api/windoor` + +| 接口名称 | 方式 | URL 路径 | 请求参数 | 响应示例 | +|---|---|---|---|---| +| 分类树 | GET | `/api/windoor/catelogs` | Bearer | 树结构同目标站 `default_windoor_catelog` | +| 模板搜索 | GET | `/api/windoor/templates?name=&catelog_id=&page=1&page_size=50` | query | 列表含 `pic_url`(本地平面图 `/static/windoor-images/{id}.png`),不含大字段 | +| 模板详情 | GET | `/api/windoor/templates/{id}` | 路径 | `data.drawing_data` — 与采集的 254 套样本同构,画布可直接渲染 | + +### 12.4 系统模块 + +| 接口名称 | 方式 | URL 路径 | 说明 | +|---|---|---|---| +| 健康检查 | GET | `/api/health` | `{"code":1,"data":{"status":"up"}}` | +| 接口文档 | GET | `/docs` | FastAPI 自动生成 OpenAPI | +| 平面图静态 | GET | `/static/windoor-images/{id}.png` | 254 张采集平面图(开发参照,上线前须替换为自绘素材) | + +### 12.5 种子数据 + +```bash +uv sync # 安装依赖 +uv run python -m huamenchuang.scripts.seed # 管理员 + 分类树 + 254 套样板入库 +uv run uvicorn huamenchuang.main:app --reload # 启动,http://localhost:8000/docs +``` + +管理员账号:`13800000000 / 123456`(开发用,生产必须修改)。 +默认 SQLite(`HMC_DATABASE_URL` 可切 MySQL),JWT 密钥 `HMC_JWT_SECRET` 生产必须设置。 diff --git a/pyproject.toml b/pyproject.toml index 5f59f0d..376d1a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,21 @@ [project] name = "huamenchuang" version = "0.1.0" -description = "Add your description here" +description = "门窗行业云平台 - 订单/画图报价/样板库" readme = "README.md" requires-python = ">=3.14" -dependencies = [] +dependencies = [ + "fastapi>=0.115", + "uvicorn[standard]>=0.30", + "sqlalchemy>=2.0", + "aiosqlite>=0.20", + "pydantic>=2.7", + "pydantic-settings>=2.3", + "pyjwt>=2.8", +] [project.scripts] -huamenchuang = "huamenchuang:main" +huamenchuang = "huamenchuang.main:main" [build-system] requires = ["uv_build>=0.12.5,<0.13.0"] diff --git a/src/huamenchuang/api/__init__.py b/src/huamenchuang/api/__init__.py new file mode 100644 index 0000000..7bd17b1 --- /dev/null +++ b/src/huamenchuang/api/__init__.py @@ -0,0 +1 @@ +"""华门窗包。""" diff --git a/src/huamenchuang/api/auth.py b/src/huamenchuang/api/auth.py new file mode 100644 index 0000000..bd86a2c --- /dev/null +++ b/src/huamenchuang/api/auth.py @@ -0,0 +1,49 @@ +"""认证路由:登录/注册/当前用户。""" +from fastapi import APIRouter + +from huamenchuang.core.deps import CurrentUser, DbSession +from huamenchuang.core.response import fail, ok +from huamenchuang.schemas.auth import LoginRequest +from huamenchuang.services.auth_service import AuthService + +router = APIRouter(prefix="/auth", tags=["认证"]) + + +@router.post("/login") +async def login(body: LoginRequest, db: DbSession): + """账号密码登录,签发 JWT。""" + try: + result = await AuthService(db).login(body.phone_number, body.password) + except Exception: + return fail("登录失败,请稍后再试") + if result is None: + return fail("手机号或密码错误") + token, hours, user = result + return ok(data={ + "token": token, + "expires_hours": hours, + "user": {"id": user.id, "nickname": user.nickname, "phone_number": user.phone_number, + "company_id": user.company_id}, + }, message="登录成功") + + +@router.post("/register") +async def register(body: LoginRequest, db: DbSession): + """注册并直接返回 token。""" + service = AuthService(db) + try: + user = await service.register(body.phone_number, body.password) + except ValueError as e: + return fail(str(e)) + from huamenchuang.core.security import create_token + return ok(data={"token": create_token(user.id), "user_id": user.id}, message="注册成功") + + +@router.get("/current-user") +async def current_user(user: CurrentUser): + """当前登录用户信息。""" + return ok(data={ + "id": user.id, "nickname": user.nickname, "phone_number": user.phone_number, + "company_id": user.company_id, "language": user.language, + "try_expiration_time": str(user.try_expiration_time) if user.try_expiration_time else None, + }) diff --git a/src/huamenchuang/api/orders.py b/src/huamenchuang/api/orders.py new file mode 100644 index 0000000..4bae10f --- /dev/null +++ b/src/huamenchuang/api/orders.py @@ -0,0 +1,120 @@ +"""订单路由(Controller 层,只做参数编排,业务在 Service)。""" +from fastapi import APIRouter, Depends + +from huamenchuang.core.deps import CurrentUser, DbSession, require_module +from huamenchuang.core.response import fail, ok, paginated +from huamenchuang.schemas.order import OrderCreate, OrderProcessUpdate, OrderSearch, OrderUpdate +from huamenchuang.services.order_service import OrderService + +router = APIRouter(prefix="/orders", tags=["订单"], + dependencies=[Depends(require_module("order"))]) + + +def _to_out(order) -> dict: + return { + "id": order.id, + "order_no": order.order_no, + "contract_id": order.contract_id, + "pic_url": order.pic_url, + "customer_id": order.customer_id, + "customer_name": order.customer_name, + "phone": order.phone, + "address": order.address, + "area": float(order.area), + "count": order.count, + "money": float(order.money), + "status": order.status, + "status_label": order.status_label, + "cut_status": order.cut_status, + "sort": order.sort, + "note": order.note, + "created_at": str(order.created_at), + "updated_at": str(order.updated_at), + } + + +@router.post("") +async def create_order(body: OrderCreate, user: CurrentUser, db: DbSession): + """创建订单。契约对齐目标站:{contract_id, drawing_data, pic_url}。""" + try: + order = await OrderService(db).create_order(user, body) + except Exception: + return fail("创建订单失败") + return ok(data=_to_out(order), message="创建成功") + + +@router.post("/search") +async def search_orders(body: OrderSearch, user: CurrentUser, db: DbSession): + """订单搜索(POST 搜索语义对齐目标站)。""" + items, total = await OrderService(db).search(user, body) + return ok(data=paginated([_to_out(o) for o in items], body.page, body.page_size, total)) + + +@router.get("") +async def list_orders(user: CurrentUser, db: DbSession, page: int = 1, page_size: int = 15): + """订单分页列表(GET 简化入口)。""" + items, total = await OrderService(db).search( + user, OrderSearch(page=page, page_size=page_size)) + return ok(data=paginated([_to_out(o) for o in items], page, page_size, total)) + + +@router.get("/{order_id}") +async def order_detail(order_id: int, user: CurrentUser, db: DbSession): + """订单详情:含 drawing_data 与生产进度。""" + try: + order = await OrderService(db).get_detail(user, order_id) + except KeyError as e: + return fail(str(e)) + data = _to_out(order) + data["drawing_data"] = order.drawing_data + data["processes"] = [ + {"id": p.id, "name": p.name, "status": p.status, + "finish_time": str(p.finish_time) if p.finish_time else None} + for p in order.processes + ] + return ok(data=data) + + +@router.put("/{order_id}") +async def update_order(order_id: int, body: OrderUpdate, user: CurrentUser, db: DbSession): + """更新订单基础字段;action=change_status 时执行状态迁移。""" + try: + order = await OrderService(db).update_order(user, order_id, body) + except KeyError as e: + return fail(str(e)) + except ValueError as e: + return fail(str(e)) + return ok(data=_to_out(order), message="更新成功") + + +@router.delete("/{order_id}") +async def delete_order(order_id: int, user: CurrentUser, db: DbSession): + """删除订单(软动作:记日志后物理删除)。""" + try: + await OrderService(db).delete_order(user, order_id) + except KeyError as e: + return fail(str(e)) + return ok(message="删除成功") + + +@router.post("/{order_id}/process") +async def update_process(order_id: int, body: OrderProcessUpdate, user: CurrentUser, db: DbSession): + """更新生产进度节点。""" + try: + proc = await OrderService(db).update_process(user, order_id, body) + except KeyError as e: + return fail(str(e)) + return ok(data={"id": proc.id, "name": proc.name, "status": proc.status, + "finish_time": str(proc.finish_time) if proc.finish_time else None}, + message="操作成功") + + +@router.get("/{order_id}/logs") +async def order_logs(order_id: int, user: CurrentUser, db: DbSession): + """订单操作日志(对齐 order_up_logs 语义)。""" + try: + logs = await OrderService(db).list_logs(user, order_id) + except KeyError as e: + return fail(str(e)) + return ok(data=[{"id": g.id, "action": g.action, "detail": g.detail, + "created_at": str(g.created_at)} for g in logs]) diff --git a/src/huamenchuang/api/router.py b/src/huamenchuang/api/router.py new file mode 100644 index 0000000..f1906ad --- /dev/null +++ b/src/huamenchuang/api/router.py @@ -0,0 +1,9 @@ +"""路由汇总。""" +from fastapi import APIRouter + +from huamenchuang.api import auth, orders, windoor + +api_router = APIRouter(prefix="/api") +api_router.include_router(auth.router) +api_router.include_router(orders.router) +api_router.include_router(windoor.router) diff --git a/src/huamenchuang/api/windoor.py b/src/huamenchuang/api/windoor.py new file mode 100644 index 0000000..a53de9f --- /dev/null +++ b/src/huamenchuang/api/windoor.py @@ -0,0 +1,66 @@ +"""样板库路由:分类树、模板搜索、模板详情(含 drawing_data)。""" +from fastapi import APIRouter, Query +from sqlalchemy import select + +from huamenchuang.core.deps import CurrentUser, DbSession +from huamenchuang.core.response import fail, ok +from huamenchuang.models.windoor_template import WindoorCatelog, WindoorTemplate + +router = APIRouter(prefix="/windoor", tags=["样板库"]) + + +@router.get("/catelogs") +async def catelogs(db: DbSession, user: CurrentUser): + """分类树(与目标站 default_windoor_catelog 同构)。""" + stmt = select(WindoorCatelog).where(WindoorCatelog.company_id == user.company_id) + nodes = list((await db.execute(stmt)).scalars()) + by_fid: dict[int, list] = {} + for n in nodes: + by_fid.setdefault(n.fid, []).append(n) + + def build(fid: int) -> list[dict]: + return [ + {"id": n.id, "name": n.name, "fid": n.fid, "note": n.note, "children": build(n.id)} + for n in by_fid.get(fid, []) + ] + + return ok(data=build(0)) + + +@router.get("/templates") +async def search_templates( + db: DbSession, user: CurrentUser, + name: str = Query(default="", max_length=64), + catelog_id: int | None = None, + page: int = Query(default=1, ge=1), + page_size: int = Query(default=50, ge=1, le=200), +): + """模板搜索(列表不含大字段,含平面图地址)。""" + conds = [WindoorTemplate.company_id == user.company_id] + if name: + conds.append(WindoorTemplate.name.like(f"%{name}%")) + if catelog_id is not None: + conds.append(WindoorTemplate.catelog_id == catelog_id) + stmt = (select(WindoorTemplate).where(*conds) + .order_by(WindoorTemplate.id).offset((page - 1) * page_size).limit(page_size)) + items = list((await db.execute(stmt)).scalars()) + return ok(data=[{ + "id": t.id, "name": t.name, "pic_url": t.pic_url, "catelog_id": t.catelog_id, + "note": t.note, "times": t.times, + "low_width": t.low_width, "high_width": t.high_width, + "low_height": t.low_height, "high_height": t.high_height, + } for t in items]) + + +@router.get("/templates/{template_id}") +async def template_detail(template_id: int, db: DbSession, user: CurrentUser): + """模板详情:返回 drawing_data(画布可直接 openFile 渲染)。""" + t = await db.get(WindoorTemplate, template_id) + if t is None or t.company_id != user.company_id: + return fail("模板不存在") + return ok(data={ + "id": t.id, "name": t.name, "drawing_data": t.drawing_data, "pic_url": t.pic_url, + "catelog_id": t.catelog_id, "note": t.note, + "low_width": t.low_width, "high_width": t.high_width, + "low_height": t.low_height, "high_height": t.high_height, + }) diff --git a/src/huamenchuang/core/__init__.py b/src/huamenchuang/core/__init__.py new file mode 100644 index 0000000..7bd17b1 --- /dev/null +++ b/src/huamenchuang/core/__init__.py @@ -0,0 +1 @@ +"""华门窗包。""" diff --git a/src/huamenchuang/core/config.py b/src/huamenchuang/core/config.py new file mode 100644 index 0000000..d58b4c7 --- /dev/null +++ b/src/huamenchuang/core/config.py @@ -0,0 +1,21 @@ +"""全局配置。开发默认 SQLite,生产可通过环境变量切 MySQL。""" +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + app_name: str = "huamenchuang" + # 开发默认 SQLite;生产示例: mysql+aiomysql://user:pwd@localhost/hmc + database_url: str = "sqlite+aiosqlite:///./huamenchuang.db" + jwt_secret: str = "change-me-in-production" + jwt_algorithm: str = "HS256" + jwt_expire_hours: int = 24 + # 套餐/模块开关(code=10086 付费墙语义),key 为模块名 + enabled_modules: list[str] = [ + "template", "customer", "contract", "warehouse", "order", + "quote", "optimize", "render", + ] + + model_config = {"env_prefix": "HMC_", "env_file": ".env"} + + +settings = Settings() diff --git a/src/huamenchuang/core/database.py b/src/huamenchuang/core/database.py new file mode 100644 index 0000000..f994bb9 --- /dev/null +++ b/src/huamenchuang/core/database.py @@ -0,0 +1,31 @@ +"""异步 SQLAlchemy 引擎与 会话。""" +from collections.abc import AsyncGenerator + +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from sqlalchemy.orm import DeclarativeBase + +from .config import settings + +engine = create_async_engine(settings.database_url, echo=False, pool_pre_ping=True) +SessionLocal = async_sessionmaker(engine, expire_on_commit=False) + + +class Base(DeclarativeBase): + pass + + +async def get_db() -> AsyncGenerator[AsyncSession, None]: + """请求级 会话。""" + async with SessionLocal() as session: + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise + + +async def init_db() -> None: + """建表(开发用;生产请使用迁移工具)。""" + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) diff --git a/src/huamenchuang/core/deps.py b/src/huamenchuang/core/deps.py new file mode 100644 index 0000000..958e216 --- /dev/null +++ b/src/huamenchuang/core/deps.py @@ -0,0 +1,66 @@ +"""FastAPI 依赖:当前用户解析、模块付费墙校验。""" +from typing import Annotated + +import jwt as jwt_exc +from fastapi import Depends, Header +from sqlalchemy.ext.asyncio import AsyncSession + +from .config import settings +from .database import get_db +from .response import fail, paywall +from .security import decode_token + +from huamenchuang.models.user import User + + +async def get_current_user( + authorization: Annotated[str | None, Header()] = None, + db: AsyncSession = Depends(get_db), +) -> User: + """解析 Bearer token 得到当前用户;无效则 401 语义响应。""" + if not authorization or not authorization.startswith("Bearer "): + raise NotAuthedError("未登录或凭证缺失") + token = authorization.removeprefix("Bearer ").strip() + try: + user_id = decode_token(token) + except jwt_exc.PyJWTError: + raise NotAuthedError("登录已过期,请重新登录") + user = await db.get(User, user_id) + if user is None: + raise NotAuthedError("用户不存在") + return user + + +class NotAuthedError(Exception): + """401 语义异常,由全局异常处理器转换为标准 envelope。""" + + +def require_module(module: str): + """模块付费墙依赖(code=10086 语义)。用法: dependencies=[Depends(require_module("order"))]""" + + async def _check() -> None: + if module not in settings.enabled_modules: + raise ModuleLockedError(module) + + return _check + + +class ModuleLockedError(Exception): + def __init__(self, module: str) -> None: + self.module = module + + +CurrentUser = Annotated[User, Depends(get_current_user)] +DbSession = Annotated[AsyncSession, Depends(get_db)] + + +__all__ = [ + "CurrentUser", + "DbSession", + "ModuleLockedError", + "NotAuthedError", + "get_current_user", + "paywall", + "require_module", + "fail", +] diff --git a/src/huamenchuang/core/response.py b/src/huamenchuang/core/response.py new file mode 100644 index 0000000..2f95e0d --- /dev/null +++ b/src/huamenchuang/core/response.py @@ -0,0 +1,39 @@ +"""统一响应封装,对齐目标站 envelope:{code, message, data, other_data, other_data2}。""" +from typing import Any + +from fastapi import responses + + +def ok(data: Any = None, message: str = "获取成功", other_data: Any = "", other_data2: Any = "") -> dict: + """业务成功响应体(HTTP 200 + code=1)。""" + return {"code": 1, "message": message, "data": data, "other_data": other_data, "other_data2": other_data2} + + +def fail(message: str, code: int | str = 0, data: Any = "") -> dict: + """业务失败响应体(HTTP 200 + code≠1)。""" + return {"code": code, "message": message, "data": data, "other_data": "", "other_data2": ""} + + +def paywall(module: str) -> dict: + """付费墙响应(对齐目标站 code=10086 语义)。""" + return fail(f"购买后解锁此功能({module}),点击此处去购买", code="10086") + + +def no_permission(code: int = 210, message: str = "该账号没有该权限") -> dict: + """角色权限不足(对齐目标站 code=210 语义)。""" + return fail(message, code=code) + + +def paginated(items: list, page: int, page_size: int, total: int) -> dict: + """Laravel 风格分页结构。""" + return { + "current_page": page, + "data": items, + "per_page": page_size, + "total": total, + "last_page": (total + page_size - 1) // page_size if page_size else 1, + } + + +def file_response(content: bytes, media_type: str) -> responses.Response: + return responses.Response(content=content, media_type=media_type) diff --git a/src/huamenchuang/core/security.py b/src/huamenchuang/core/security.py new file mode 100644 index 0000000..1fa7a73 --- /dev/null +++ b/src/huamenchuang/core/security.py @@ -0,0 +1,28 @@ +"""JWT 签发与校验(HS256,默认 24h,对齐目标站鉴权方式)。""" +import datetime +import uuid +from typing import Any + +import jwt + +from .config import settings + + +def create_token(user_id: int) -> str: + """为用户签发 JWT,sub=用户id。""" + now = datetime.datetime.now(datetime.UTC) + payload: dict[str, Any] = { + "iss": "huamenchuang/api/login", + "iat": now, + "nbf": now, + "exp": now + datetime.timedelta(hours=settings.jwt_expire_hours), + "jti": uuid.uuid4().hex[:16], + "sub": str(user_id), # RFC 7519: sub 必须为字符串 + } + return jwt.encode(payload, settings.jwt_secret, algorithm=settings.jwt_algorithm) + + +def decode_token(token: str) -> int: + """校验 JWT 并返回用户 id;无效/过期抛 jwt 异常由调用方转换 401。""" + payload = jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]) + return int(payload["sub"]) diff --git a/src/huamenchuang/main.py b/src/huamenchuang/main.py new file mode 100644 index 0000000..5876ec8 --- /dev/null +++ b/src/huamenchuang/main.py @@ -0,0 +1,73 @@ +"""应用入口:FastAPI 装配、异常处理、静态资源挂载、开发服务器。""" +from pathlib import Path + +from fastapi import FastAPI, Request +from fastapi.exceptions import RequestValidationError +from fastapi.staticfiles import StaticFiles + +from huamenchuang.api.router import api_router +from huamenchuang.core.database import init_db +from huamenchuang.core.deps import ModuleLockedError, NotAuthedError +from huamenchuang.core.response import fail, ok, paywall + +app = FastAPI(title="华门窗云平台", version="0.1.0") + + +@app.on_event("startup") +async def startup() -> None: + await init_db() + + +@app.exception_handler(NotAuthedError) +async def auth_handler(request: Request, exc: NotAuthedError): + return _json({"code": 401, "message": str(exc), "data": "", "other_data": "", "other_data2": ""}) + + +@app.exception_handler(ModuleLockedError) +async def module_locked_handler(request: Request, exc: ModuleLockedError): + return _json(paywall(exc.module)) + + +@app.exception_handler(RequestValidationError) +async def validation_handler(request: Request, exc: RequestValidationError): + """参数校验失败 → 422 语义 envelope(列出字段,不泄漏内部细节)。""" + fields = [f"{'.'.join(str(x) for x in e['loc'][1:])}" for e in exc.errors()] + return _json(fail(f"参数错误: {', '.join(fields)}")) + + +@app.exception_handler(Exception) +async def unhandled_handler(request: Request, exc: Exception): + """兜底异常:统一 500 envelope(不回栈信息)。""" + return _json(fail("服务器内部错误,请稍后再试")) + + +def _json(payload: dict): + from fastapi.responses import JSONResponse + return JSONResponse(payload) + + +@api_router.get("/health", tags=["系统"]) +async def health(): + return ok(data={"status": "up"}, message="ok") + + +app.include_router(api_router) + +# 静态资源:样板平面图(data/windoor-images) +_imgdir = Path(__file__).resolve().parents[2] / "data" / "windoor-images" +if _imgdir.exists(): + app.mount("/static/windoor-images", StaticFiles(directory=str(_imgdir)), name="windoor-images") + +# 前端构建产物挂载(前后端规范:构建到后端目录) +_dist = Path(__file__).resolve().parents[2] / "frontend" / "dist" +if _dist.exists(): + app.mount("/", StaticFiles(directory=str(_dist), html=True), name="frontend") + + +def main() -> None: + import uvicorn + uvicorn.run("huamenchuang.main:app", host="0.0.0.0", port=8000, reload=True) + + +if __name__ == "__main__": + main() diff --git a/src/huamenchuang/models/__init__.py b/src/huamenchuang/models/__init__.py new file mode 100644 index 0000000..7875223 --- /dev/null +++ b/src/huamenchuang/models/__init__.py @@ -0,0 +1,17 @@ +"""ORM 模型统一导出。""" +from huamenchuang.models.contract import Contract, Customer +from huamenchuang.models.order import DEFAULT_PROCESSES, Order, OrderLog, OrderProcess +from huamenchuang.models.user import User +from huamenchuang.models.windoor_template import WindoorCatelog, WindoorTemplate + +__all__ = [ + "Contract", + "Customer", + "DEFAULT_PROCESSES", + "Order", + "OrderLog", + "OrderProcess", + "User", + "WindoorCatelog", + "WindoorTemplate", +] diff --git a/src/huamenchuang/models/contract.py b/src/huamenchuang/models/contract.py new file mode 100644 index 0000000..9b95cc7 --- /dev/null +++ b/src/huamenchuang/models/contract.py @@ -0,0 +1,55 @@ +"""客户、合同模型(订单的上游实体)。""" +import datetime + +from sqlalchemy import DateTime, ForeignKey, Numeric, String, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from huamenchuang.core.database import Base + + +class Customer(Base): + __tablename__ = "customers" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(128)) + phone: Mapped[str] = mapped_column(String(32), default="") + address: Mapped[str] = mapped_column(String(255), default="") + note: Mapped[str] = mapped_column(Text, default="") + company_id: Mapped[int] = mapped_column(default=1, index=True) + salesmen: Mapped[str | None] = mapped_column(String(64), nullable=True) + discount: Mapped[float | None] = mapped_column(Numeric(6, 3), nullable=True) + balance: Mapped[float] = mapped_column(Numeric(12, 2), default=0) + created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) + updated_at: Mapped[datetime.datetime] = mapped_column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) + + +class Contract(Base): + """合同/意向单:目标站契约 status="意向单" 起步,oids[] 记录关联订单。""" + + __tablename__ = "contracts" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + contract_no: Mapped[str] = mapped_column(String(32), unique=True) # 如 FG20260920001 + customer_id: Mapped[int | None] = mapped_column(ForeignKey("customers.id"), nullable=True, index=True) + customer_name: Mapped[str] = mapped_column(String(128), default="") + phone: Mapped[str] = mapped_column(String(32), default="") + address: Mapped[str] = mapped_column(String(255), default="") + area: Mapped[float] = mapped_column(default=0) # ㎡ + count: Mapped[int] = mapped_column(default=0) + money: Mapped[float] = mapped_column(Numeric(12, 2), default=0) + # 0=意向单 1=正式单(已签) 2=已完成 3=已作废 + status: Mapped[int] = mapped_column(default=0, index=True) + sign_status: Mapped[int] = mapped_column(default=0) + sign_picture_url: Mapped[str | None] = mapped_column(String(255), nullable=True) + discount: Mapped[float] = mapped_column(Numeric(6, 3), default=1) + note: Mapped[str] = mapped_column(Text, default="") + company_id: Mapped[int] = mapped_column(default=1, index=True) + user_id: Mapped[int] = mapped_column(default=0, index=True) + created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) + updated_at: Mapped[datetime.datetime] = mapped_column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) + + customer: Mapped[Customer | None] = relationship(lazy="joined") diff --git a/src/huamenchuang/models/order.py b/src/huamenchuang/models/order.py new file mode 100644 index 0000000..c386c42 --- /dev/null +++ b/src/huamenchuang/models/order.py @@ -0,0 +1,103 @@ +"""订单模块模型:订单本体、生产进度、操作日志。""" +import datetime +from typing import TYPE_CHECKING + +from sqlalchemy import DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from huamenchuang.core.database import Base + +if TYPE_CHECKING: + from huamenchuang.models.contract import Contract + + +class Order(Base): + """订单:由合同 + 画布 drawing_data 创建(对齐目标站创建契约)。""" + + __tablename__ = "orders" + + # 状态机(自研设计,语义对齐目标站"意向单/正式单"流转) + STATUS_INTENTION = 0 # 意向单 + STATUS_FORMAL = 1 # 正式单 + STATUS_PRODUCING = 2 # 生产中 + STATUS_FINISHED = 3 # 已完工 + STATUS_CANCELED = 4 # 已取消 + STATUS_LABELS = {0: "意向单", 1: "正式单", 2: "生产中", 3: "已完工", 4: "已取消"} + # 允许的状态迁移 + TRANSITIONS: dict[int, set[int]] = { + 0: {1, 4}, + 1: {2, 4}, + 2: {3}, + 3: set(), + 4: set(), + } + + __tablename__ = "orders" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + order_no: Mapped[str] = mapped_column(String(32), unique=True) # DO+YYYYMMDD+4位流水 + contract_id: Mapped[int | None] = mapped_column( + ForeignKey("contracts.id"), nullable=True, index=True + ) + drawing_data: Mapped[str] = mapped_column(Text, default="") # shapeManager 序列化 JSON + pic_url: Mapped[str] = mapped_column(String(255), default="") # 平面图/缩略图 + # 冗余客户信息(下单快照,客户资料变更不影响订单) + customer_id: Mapped[int | None] = mapped_column(ForeignKey("customers.id"), nullable=True) + customer_name: Mapped[str] = mapped_column(String(128), default="") + phone: Mapped[str] = mapped_column(String(32), default="") + address: Mapped[str] = mapped_column(String(255), default="") + area: Mapped[float] = mapped_column(Numeric(10, 3), default=0) + count: Mapped[int] = mapped_column(Integer, default=0) + money: Mapped[float] = mapped_column(Numeric(12, 2), default=0) + status: Mapped[int] = mapped_column(Integer, default=0, index=True) + # 拆单/切割状态(对齐目标站 cut_status) + cut_status: Mapped[int] = mapped_column(Integer, default=0) + sort: Mapped[int] = mapped_column(Integer, default=0) + note: Mapped[str] = mapped_column(Text, default="") + company_id: Mapped[int] = mapped_column(default=1, index=True) + user_id: Mapped[int] = mapped_column(default=0, index=True) + created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) + updated_at: Mapped[datetime.datetime] = mapped_column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) + + contract: Mapped["Contract | None"] = relationship("Contract", lazy="joined") + processes: Mapped[list["OrderProcess"]] = relationship( + back_populates="order", cascade="all, delete-orphan", order_by="OrderProcess.sort" + ) + + @property + def status_label(self) -> str: + return self.STATUS_LABELS.get(self.status, "未知") + + +class OrderProcess(Base): + """订单生产进度节点(下料→组装→玻璃→打胶→五金→质检→完工)。""" + + __tablename__ = "order_processes" + __table_args__ = (UniqueConstraint("order_id", "name", name="uq_order_process"),) + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"), index=True) + name: Mapped[str] = mapped_column(String(32)) + status: Mapped[int] = mapped_column(Integer, default=0) # 0未开始 1进行中 2完成 + finish_time: Mapped[datetime.datetime | None] = mapped_column(DateTime, nullable=True) + sort: Mapped[int] = mapped_column(Integer, default=0) + + order: Mapped[Order] = relationship(back_populates="processes") + + +DEFAULT_PROCESSES = ["下料", "组装", "玻璃", "打胶", "五金", "质检", "完工"] + + +class OrderLog(Base): + """订单操作日志(对齐目标站 order_up_logs)。""" + + __tablename__ = "order_logs" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"), index=True) + user_id: Mapped[int] = mapped_column(default=0) + action: Mapped[str] = mapped_column(String(32)) # create/update_status/delete_process... + detail: Mapped[str] = mapped_column(String(255), default="") + created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) diff --git a/src/huamenchuang/models/user.py b/src/huamenchuang/models/user.py new file mode 100644 index 0000000..fb4aef4 --- /dev/null +++ b/src/huamenchuang/models/user.py @@ -0,0 +1,26 @@ +"""用户模型。""" +import datetime + +from sqlalchemy import DateTime, String, Text +from sqlalchemy.orm import Mapped, mapped_column + +from huamenchuang.core.database import Base + + +class User(Base): + __tablename__ = "users" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + nickname: Mapped[str] = mapped_column(String(64), default="") + phone_number: Mapped[str] = mapped_column(String(20), unique=True) + password_hash: Mapped[str] = mapped_column(String(128), default="") + company_id: Mapped[int] = mapped_column(default=1, index=True) + email: Mapped[str | None] = mapped_column(String(128), nullable=True) + language: Mapped[str] = mapped_column(String(16), default="zh-CN") + # 套餐到期时间,空=试用 + try_expiration_time: Mapped[datetime.datetime | None] = mapped_column(DateTime, nullable=True) + note: Mapped[str] = mapped_column(Text, default="") + created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) + updated_at: Mapped[datetime.datetime] = mapped_column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) diff --git a/src/huamenchuang/models/windoor_template.py b/src/huamenchuang/models/windoor_template.py new file mode 100644 index 0000000..2780cd7 --- /dev/null +++ b/src/huamenchuang/models/windoor_template.py @@ -0,0 +1,44 @@ +"""样板库模型(drawing_data 与目标站 254 套样本同构)。""" +import datetime + +from sqlalchemy import DateTime, Integer, String, Text +from sqlalchemy.orm import Mapped, mapped_column + +from huamenchuang.core.database import Base + + +class WindoorTemplate(Base): + """官方/企业窗型样板。drawing_data 为 shapeManager 序列化 JSON。""" + + __tablename__ = "windoor_templates" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(128), default="") + drawing_data: Mapped[str] = mapped_column(Text) # 原样保留编辑器 JSON 字符串 + pic_url: Mapped[str] = mapped_column(String(255), default="") + catelog_id: Mapped[int] = mapped_column(Integer, default=0, index=True) + note: Mapped[str] = mapped_column(String(255), default="") + times: Mapped[int] = mapped_column(Integer, default=0) # 引用次数 + # 尺寸限制(毫米),用于编辑器校验 + low_width: Mapped[int] = mapped_column(Integer, default=0) + high_width: Mapped[int] = mapped_column(Integer, default=0) + low_height: Mapped[int] = mapped_column(Integer, default=0) + high_height: Mapped[int] = mapped_column(Integer, default=0) + is_default: Mapped[int] = mapped_column(Integer, default=1) # 1=官方样板 + company_id: Mapped[int] = mapped_column(default=1, index=True) + created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow) + updated_at: Mapped[datetime.datetime] = mapped_column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) + + +class WindoorCatelog(Base): + """样板分类树(推拉门{平开,推拉,复古门,肯德基门,中式门窗,转角窗})。""" + + __tablename__ = "windoor_catelogs" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + name: Mapped[str] = mapped_column(String(64)) + fid: Mapped[int] = mapped_column(Integer, default=0, index=True) + note: Mapped[str] = mapped_column(String(255), default="") + company_id: Mapped[int] = mapped_column(default=1, index=True) diff --git a/src/huamenchuang/repositories/__init__.py b/src/huamenchuang/repositories/__init__.py new file mode 100644 index 0000000..7bd17b1 --- /dev/null +++ b/src/huamenchuang/repositories/__init__.py @@ -0,0 +1 @@ +"""华门窗包。""" diff --git a/src/huamenchuang/repositories/order_repo.py b/src/huamenchuang/repositories/order_repo.py new file mode 100644 index 0000000..70e3b38 --- /dev/null +++ b/src/huamenchuang/repositories/order_repo.py @@ -0,0 +1,98 @@ +"""订单数据访问层。""" +import datetime + +from sqlalchemy import func, or_, select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload + +from huamenchuang.models.order import Order, OrderLog, OrderProcess + + +class OrderRepository: + def __init__(self, db: AsyncSession) -> None: + self.db = db + + async def next_order_no(self, company_id: int) -> str: + """生成订单号:DO+YYYYMMDD+4位流水(同日同企业递增)。""" + today = datetime.date.today().strftime("%Y%m%d") + prefix = f"DO{today}" + stmt = select(Order.order_no).where( + Order.company_id == company_id, Order.order_no.like(f"{prefix}%") + ).order_by(Order.order_no.desc()).limit(1) + last = (await self.db.execute(stmt)).scalar_one_or_none() + seq = int(last[-4:]) + 1 if last else 1 + return f"{prefix}{seq:04d}" + + async def create(self, order: Order) -> Order: + self.db.add(order) + await self.db.flush() + return order + + async def get(self, order_id: int, company_id: int) -> Order | None: + stmt = ( + select(Order) + .options(selectinload(Order.processes)) + .where(Order.id == order_id, Order.company_id == company_id) + ) + return (await self.db.execute(stmt)).scalar_one_or_none() + + async def search(self, company_id: int, **f) -> tuple[list[Order], int]: + """按条件分页搜索。f: page,page_size,keyword,status,contract_id,start_date,end_date,order_by""" + conds = [Order.company_id == company_id] + if f.get("status") is not None: + conds.append(Order.status == f["status"]) + if f.get("contract_id") is not None: + conds.append(Order.contract_id == f["contract_id"]) + if kw := (f.get("keyword") or "").strip(): + like = f"%{kw}%" + conds.append(or_( + Order.order_no.like(like), + Order.customer_name.like(like), + Order.phone.like(like), + )) + try: + if f.get("start_date"): + conds.append(Order.created_at >= datetime.datetime.fromisoformat(f["start_date"])) + if f.get("end_date"): + conds.append(Order.created_at <= datetime.datetime.fromisoformat(f["end_date"])) + except ValueError: + pass # 日期格式错误时忽略该条件,避免 500 + where = *conds, + total = (await self.db.execute(select(func.count()).select_from(Order).where(*where))).scalar_one() + order_map = { + "created_desc": Order.created_at.desc(), + "sort_asc": Order.sort.asc(), + "money_desc": Order.money.desc(), + } + stmt = ( + select(Order) + .where(*where) + .order_by(order_map.get(f.get("order_by") or "", Order.created_at.desc())) + .offset((f["page"] - 1) * f["page_size"]) + .limit(f["page_size"]) + ) + items = list((await self.db.execute(stmt)).scalars()) + return items, total + + async def add_log(self, order_id: int, user_id: int, action: str, detail: str = "") -> None: + self.db.add(OrderLog(order_id=order_id, user_id=user_id, action=action, detail=detail)) + + async def list_logs(self, order_id: int, limit: int = 50) -> list[OrderLog]: + stmt = select(OrderLog).where(OrderLog.order_id == order_id).order_by(OrderLog.id.desc()).limit(limit) + return list((await self.db.execute(stmt)).scalars()) + + async def upsert_process(self, order_id: int, name: str, status: int) -> OrderProcess | None: + stmt = select(OrderProcess).where(OrderProcess.order_id == order_id, OrderProcess.name == name) + proc = (await self.db.execute(stmt)).scalar_one_or_none() + if proc is None: + proc = OrderProcess(order_id=order_id, name=name, status=status) + self.db.add(proc) + else: + proc.status = status + if status == 2: + proc.finish_time = datetime.datetime.utcnow() + await self.db.flush() + return proc + + async def delete(self, order: Order) -> None: + await self.db.delete(order) diff --git a/src/huamenchuang/repositories/user_repo.py b/src/huamenchuang/repositories/user_repo.py new file mode 100644 index 0000000..3dea530 --- /dev/null +++ b/src/huamenchuang/repositories/user_repo.py @@ -0,0 +1,19 @@ +"""用户数据访问层。""" +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from huamenchuang.models.user import User + + +class UserRepository: + def __init__(self, db: AsyncSession) -> None: + self.db = db + + async def get_by_phone(self, phone_number: str) -> User | None: + stmt = select(User).where(User.phone_number == phone_number) + return (await self.db.execute(stmt)).scalar_one_or_none() + + async def create(self, user: User) -> User: + self.db.add(user) + await self.db.flush() + return user diff --git a/src/huamenchuang/schemas/__init__.py b/src/huamenchuang/schemas/__init__.py new file mode 100644 index 0000000..7bd17b1 --- /dev/null +++ b/src/huamenchuang/schemas/__init__.py @@ -0,0 +1 @@ +"""华门窗包。""" diff --git a/src/huamenchuang/schemas/auth.py b/src/huamenchuang/schemas/auth.py new file mode 100644 index 0000000..788c595 --- /dev/null +++ b/src/huamenchuang/schemas/auth.py @@ -0,0 +1,13 @@ +"""认证模块 schemas。""" +from pydantic import BaseModel, Field + + +class LoginRequest(BaseModel): + phone_number: str = Field(min_length=5, max_length=20) + password: str = Field(min_length=4, max_length=64) + + +class LoginResponse(BaseModel): + token: str + expires_hours: int + user: dict diff --git a/src/huamenchuang/schemas/order.py b/src/huamenchuang/schemas/order.py new file mode 100644 index 0000000..59e9b74 --- /dev/null +++ b/src/huamenchuang/schemas/order.py @@ -0,0 +1,86 @@ +"""订单模块 Pydantic 请求/响应模型。""" +import datetime +from typing import Any + +from pydantic import BaseModel, Field + +from huamenchuang.models.order import DEFAULT_PROCESSES + + +class OrderCreate(BaseModel): + """创建订单(对齐目标站契约:contract_id + drawing_data + pic_url)。""" + + contract_id: int | None = None + drawing_data: str = Field(default="", description="shapeManager 序列化 JSON 字符串") + pic_url: str = "" + customer_id: int | None = None + note: str = "" + money: float = 0 + count: int = 0 + area: float = 0 + status: int = Field(default=0, ge=0, le=4, description="0意向单 1正式单 2生产中 3完工 4取消") + + +class OrderUpdate(BaseModel): + """订单更新:基础字段 + 状态迁移(action=change_status)。""" + + sort: int | None = None + note: str | None = None + money: float | None = None + count: int | None = None + area: float | None = None + pic_url: str | None = None + drawing_data: str | None = None + cut_status: int | None = Field(default=None, ge=0, le=2) + action: str | None = Field(default=None, description="change_status=执行状态迁移") + target_status: int | None = Field(default=None, ge=0, le=4) + + +class OrderProcessUpdate(BaseModel): + name: str = Field(description=f"进度节点名,默认流程: {'/'.join(DEFAULT_PROCESSES)}") + status: int = Field(ge=0, le=2, description="0未开始 1进行中 2完成") + + +class OrderOut(BaseModel): + """订单列表项(不含大字段 drawing_data)。""" + + model_config = {"from_attributes": True} + + id: int + order_no: str + contract_id: int | None + pic_url: str + customer_id: int | None + customer_name: str + phone: str + address: str + area: float + count: int + money: float + status: int + status_label: str + cut_status: int + sort: int + note: str + created_at: datetime.datetime + updated_at: datetime.datetime + + +class OrderDetail(OrderOut): + """订单详情:附带 drawing_data 与生产进度。""" + + drawing_data: str = "" + processes: list[dict[str, Any]] = [] + + +class OrderSearch(BaseModel): + """订单搜索(对齐目标站 POST 搜索语义)。""" + + page: int = Field(default=1, ge=1) + page_size: int = Field(default=15, ge=1, le=100) + keyword: str = Field(default="", description="订单号/客户名/手机号 模糊") + status: int | None = Field(default=None, ge=0, le=4) + contract_id: int | None = None + start_date: str | None = "" + end_date: str | None = "" + order_by: str = Field(default="created_desc", description="created_desc/sort_asc/money_desc") diff --git a/src/huamenchuang/scripts/seed.py b/src/huamenchuang/scripts/seed.py new file mode 100644 index 0000000..4a5bf14 --- /dev/null +++ b/src/huamenchuang/scripts/seed.py @@ -0,0 +1,76 @@ +"""种子数据脚本: +1. 创建默认企业/管理员(admin/123456) +2. 导入采集到的 254 套官方样板(drawing_data + 分类树 + 平面图) +用法: uv run python -m huamenchuang.scripts.seed +""" +import asyncio +import json +from pathlib import Path + +DATA = Path(__file__).resolve().parents[3] / "data" + + +async def seed() -> None: + from huamenchuang.core.database import SessionLocal, init_db + from huamenchuang.models.user import User + from huamenchuang.models.windoor_template import WindoorCatelog, WindoorTemplate + from huamenchuang.services.auth_service import hash_password + + await init_db() + async with SessionLocal() as db: + # 1) 管理员 + if await db.get(User, 1) is None: + db.add(User(id=1, nickname="管理员", phone_number="13800000000", + password_hash=hash_password("123456"), company_id=1)) + print("已创建管理员: 13800000000 / 123456") + + # 2) 分类树(来自 default_windoor_catelog.json,取目标站原始 id 便于对照) + cat_file = DATA / "api-samples" / "default_windoor_catelog.json" + if cat_file.exists(): + cats = json.load(open(cat_file, encoding="utf-8"))["data"] + existing = {c.id for c in (await db.execute( + __import__("sqlalchemy").select(WindoorCatelog.id))).scalars()} + for top in cats: + if top["id"] not in existing: + db.add(WindoorCatelog(id=top["id"], name=top["name"], fid=0, company_id=1)) + for ch in top.get("children", []): + if ch["id"] not in existing: + db.add(WindoorCatelog(id=ch["id"], name=ch["name"], fid=top["id"], company_id=1)) + print("分类树导入完成") + + # 3) 官方样板 254 套 + templates = json.load(open(DATA / "api-samples" / "windoor-templates-clean.json", encoding="utf-8")) + drawings_dir = DATA / "windoor-drawings" + img_dir = DATA / "windoor-images" + from sqlalchemy import func, select + count = (await db.execute(select(func.count()).select_from(WindoorTemplate))).scalar_one() + if count == 0: + for t in templates: + dd_file = drawings_dir / f"{t['id']}.json" + if not dd_file.exists(): + continue + detail = json.load(open(dd_file, encoding="utf-8")) + img = img_dir / f"{t['id']}.png" + img2 = img_dir / f"{t['id']}.jpg" + pic = "/static/windoor-images/" + (img.name if img.exists() else img2.name) + db.add(WindoorTemplate( + name=detail.get("name") or t.get("name") or f"样板{t['id']}", + drawing_data=detail["drawing_data"], + pic_url=pic, + catelog_id=t["catelog_id"], + note=detail.get("note") or "", + times=detail.get("times") or 0, + low_width=detail.get("low_width") or 0, + high_width=detail.get("high_width") or 0, + low_height=detail.get("low_height") or 0, + high_height=detail.get("high_height") or 0, + )) + print(f"已导入样板 {len(templates)} 套") + else: + print(f"样板表已有 {count} 条, 跳过导入") + await db.commit() + print("种子数据完成") + + +if __name__ == "__main__": + asyncio.run(seed()) diff --git a/src/huamenchuang/services/__init__.py b/src/huamenchuang/services/__init__.py new file mode 100644 index 0000000..7bd17b1 --- /dev/null +++ b/src/huamenchuang/services/__init__.py @@ -0,0 +1 @@ +"""华门窗包。""" diff --git a/src/huamenchuang/services/auth_service.py b/src/huamenchuang/services/auth_service.py new file mode 100644 index 0000000..ea95f8d --- /dev/null +++ b/src/huamenchuang/services/auth_service.py @@ -0,0 +1,47 @@ +"""认证业务逻辑。""" +import hashlib +import hmac +import os + +from sqlalchemy.ext.asyncio import AsyncSession + +from huamenchuang.core.security import create_token +from huamenchuang.models.user import User +from huamenchuang.repositories.user_repo import UserRepository + + +def hash_password(password: str) -> str: + """PBKDF2-SHA256(标准库实现,无额外依赖)。""" + salt = os.urandom(16) + dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 100_000) + return salt.hex() + "$" + dk.hex() + + +def verify_password(password: str, stored: str) -> bool: + try: + salt_hex, dk_hex = stored.split("$", 1) + except ValueError: + return False + dk = hashlib.pbkdf2_hmac("sha256", password.encode(), bytes.fromhex(salt_hex), 100_000) + return hmac.compare_digest(dk.hex(), dk_hex) + + +class AuthService: + def __init__(self, db: AsyncSession) -> None: + self.repo = UserRepository(db) + + async def login(self, phone_number: str, password: str) -> tuple[str, int, User] | None: + user = await self.repo.get_by_phone(phone_number) + if user is None or not verify_password(password, user.password_hash): + return None + return create_token(user.id), 24, user + + async def register(self, phone_number: str, password: str, nickname: str = "") -> User: + if await self.repo.get_by_phone(phone_number): + raise ValueError("手机号已注册") + user = await self.repo.create(User( + phone_number=phone_number, + password_hash=hash_password(password), + nickname=nickname or f"用户{phone_number[-4:]}", + )) + return user diff --git a/src/huamenchuang/services/order_service.py b/src/huamenchuang/services/order_service.py new file mode 100644 index 0000000..c8d17c6 --- /dev/null +++ b/src/huamenchuang/services/order_service.py @@ -0,0 +1,90 @@ +"""订单业务逻辑层:状态机、进度初始化、审计日志。""" +from sqlalchemy.ext.asyncio import AsyncSession + +from huamenchuang.core.deps import ModuleLockedError, NotAuthedError +from huamenchuang.models.order import DEFAULT_PROCESSES, Order, OrderProcess +from huamenchuang.models.user import User +from huamenchuang.repositories.order_repo import OrderRepository +from huamenchuang.schemas.order import OrderCreate, OrderProcessUpdate, OrderSearch, OrderUpdate + + +class OrderService: + def __init__(self, db: AsyncSession) -> None: + self.db = db + self.repo = OrderRepository(db) + + async def create_order(self, user: User, payload: OrderCreate) -> Order: + """创建订单:对齐目标站 {contract_id, drawing_data, pic_url} 契约。""" + order = Order( + order_no=await self.repo.next_order_no(user.company_id), + contract_id=payload.contract_id, + drawing_data=payload.drawing_data, + pic_url=payload.pic_url, + customer_id=payload.customer_id, + note=payload.note, + money=payload.money, + count=payload.count, + area=payload.area, + status=payload.status, + company_id=user.company_id, + user_id=user.id, + ) + order = await self.repo.create(order) + # 初始化默认生产进度节点(直接 add,避免触发关系惰性加载) + for i, name in enumerate(DEFAULT_PROCESSES): + self.db.add(OrderProcess(order_id=order.id, name=name, status=0, sort=i)) + await self.repo.add_log(order.id, user.id, "create", f"创建订单 {order.order_no}") + return order + + async def search(self, user: User, q: OrderSearch) -> tuple[list[Order], int]: + return await self.repo.search( + user.company_id, page=q.page, page_size=q.page_size, keyword=q.keyword, + status=q.status, contract_id=q.contract_id, + start_date=q.start_date, end_date=q.end_date, order_by=q.order_by, + ) + + async def get_detail(self, user: User, order_id: int) -> Order: + order = await self.repo.get(order_id, user.company_id) + if order is None: + raise KeyError("订单不存在") + return order + + async def update_order(self, user: User, order_id: int, payload: OrderUpdate) -> Order: + order = await self.get_detail(user, order_id) + data = payload.model_dump(exclude_none=True, exclude={"action", "target_status"}) + for k, v in data.items(): + setattr(order, k, v) + if payload.action == "change_status": + self._transition(order, payload.target_status) + await self.repo.add_log(order.id, user.id, "change_status", + f"{order.STATUS_LABELS.get(order.status)}→{order.STATUS_LABELS.get(payload.target_status)}") + else: + await self.repo.add_log(order.id, user.id, "update", f"更新字段: {', '.join(data) or '-'}") + return order + + @staticmethod + def _transition(order: Order, target: int | None) -> None: + """状态机校验:非法迁移直接拒绝。""" + if target is None: + raise ValueError("缺少 target_status") + if target not in Order.TRANSITIONS.get(order.status, set()): + raise ValueError(f"不允许的状态迁移: {order.status_label}→{order.STATUS_LABELS.get(target)}") + order.status = target + + async def update_process(self, user: User, order_id: int, payload: OrderProcessUpdate) -> OrderProcess: + order = await self.get_detail(user, order_id) + proc = await self.repo.upsert_process(order.id, payload.name, payload.status) + await self.repo.add_log(order.id, user.id, "up-order-process", f"{payload.name}={payload.status}") + return proc # type: ignore[return-value] + + async def list_logs(self, user: User, order_id: int) -> list: + await self.get_detail(user, order_id) + return await self.repo.list_logs(order_id) + + async def delete_order(self, user: User, order_id: int) -> None: + order = await self.get_detail(user, order_id) + await self.repo.add_log(order.id, user.id, "delete", f"删除订单 {order.order_no}") + await self.repo.delete(order) + + +__all__ = ["OrderService", "ModuleLockedError", "NotAuthedError"] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..357730f --- /dev/null +++ b/uv.lock @@ -0,0 +1,525 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "aiosqlite" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "fastapi" +version = "0.141.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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 = "greenlet" +version = "3.5.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/6e/0091f175ccd02b02bc8811bbcbcc6ac2e980be116e3b2f7a736ca322bf84/greenlet-3.5.6.tar.gz", hash = "sha256:8e67c43bdfc88d5fee6db0d3e40175b362fc95fb85f0412d233b9b203c53a575", size = 207653, upload-time = "2026-09-14T15:42:51.806Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/c0/d254544ae2b8bdd311aef000fafc02828c2771b17d994b3075620ea7cc6e/greenlet-3.5.6-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8cddea1b8339451c2fb3388e138347b6126744f33b611bdb55b7357361cfef46", size = 295221, upload-time = "2026-09-14T14:25:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/18/18/eb54be16b9cc3971e09ca5b73334e1b8c804a4630d9addaaf218a4fe300f/greenlet-3.5.6-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c59acfa8eb73a1e0d484392dc002bdf001fd4ce73394e0132df3d1ab6093d7cb", size = 660992, upload-time = "2026-09-14T15:12:04.876Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b4/e193efe65671dcf294bc51fcc59efb52d154adf8612c4ea016da0d2c486c/greenlet-3.5.6-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3b4a01c6da07ef9f80d4fe8933b994bc99747bcea3eab0330a9c34d3c12655b", size = 673428, upload-time = "2026-09-14T15:20:45.756Z" }, + { url = "https://files.pythonhosted.org/packages/45/ac/28fa7a9e50f2859466214c4ac584d776db52c1604ad4dd158960a5af2a1f/greenlet-3.5.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a09d59bef1db94f384b5bcc2d523694d338f3df6b757aeeaf7baca5d0c0be88", size = 670773, upload-time = "2026-09-14T14:36:02.577Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cd/fb7d6cdd86ff3427c1494854f0e35437eba05142be91f530f6da75e09e19/greenlet-3.5.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8b7c73d1cef3d9ae963e9ff03f6222df43efbb9054ffd2f1969c935b7fc84c02", size = 1631900, upload-time = "2026-09-14T15:10:09.745Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/143bdbb20a516628cb15074ae52ed17d850b450292609c7a6fccac6dbece/greenlet-3.5.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8b27df301f56e3b3d2298095c8f7d6b68f2521f6b1693e901fa039bdbae34424", size = 1693740, upload-time = "2026-09-14T14:35:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9e/019642432e6ae283301df1361227d47610709d2dc69a38f95edef266d713/greenlet-3.5.6-cp314-cp314-win_amd64.whl", hash = "sha256:f8f0bd690e1a41294ac87905e8121c81a3761ec2583c768f13467428606c8c7a", size = 327473, upload-time = "2026-09-14T14:28:12.948Z" }, + { url = "https://files.pythonhosted.org/packages/e9/7f/8aafc7bf70c948786dba7221d0dc0838e5329bebc6d434ef2208b4f0e760/greenlet-3.5.6-cp314-cp314-win_arm64.whl", hash = "sha256:8cda13494d86a4f12429641117cb6ac4bbbc9c30a33f711f7d3a2e5fbe4b0b7e", size = 311095, upload-time = "2026-09-14T14:28:00.7Z" }, + { url = "https://files.pythonhosted.org/packages/14/7e/7a205688a5b3074933b18a906608d46d106e9a79d776bdab5a4abf4b4feb/greenlet-3.5.6-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:97c5a53e8c1754df58e73f047a99e287d4da1bdfe64b0072fb25c87000897951", size = 305352, upload-time = "2026-09-14T14:21:31.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/cb/9c4a57a9d9dd0256e20b8f7f4f06554c2c92badebf0ab73ce344321b78b9/greenlet-3.5.6-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fea4427d1ffdb3b523d7daa6712038428a4c16c450b9777bdd1221cfee0eab49", size = 672671, upload-time = "2026-09-14T15:12:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/97/52/c6729681ebbd298f4decd28746815acc8a0b0a0fde21d2df33776fd4d042/greenlet-3.5.6-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73a29b5ba642e35433166a03a3e02935e7238c4b3467fbd77523b99edea23e5b", size = 679489, upload-time = "2026-09-14T15:20:47.291Z" }, + { url = "https://files.pythonhosted.org/packages/58/c5/2b6c721ba8b8963da42d5a0f57f25b8aaeb1fe9bdd156875e57f3be648a2/greenlet-3.5.6-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:460e70b033aba8ed47e2ac9b5d0d2157b05a34fbfa30a241400aef4118902cdc", size = 676608, upload-time = "2026-09-14T14:36:03.959Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/0d018e0d05bcdde19a0fcb907834155f1fc853a9bedd3f3f5e6acadcae19/greenlet-3.5.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca80a49b53ed1d22f7282da7255f7bb2fd1935fd0f623d8613fda38745f18961", size = 1641479, upload-time = "2026-09-14T15:10:11.216Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/f02ef9073919158f6403fe3701d4ed4403d646720e7201dfc6e9d264bac3/greenlet-3.5.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:916f92f2a8db10508f739d0b5e00b83defe5d1115a997c54532a6d7cf8c95404", size = 1698758, upload-time = "2026-09-14T14:35:54.336Z" }, + { url = "https://files.pythonhosted.org/packages/08/a5/1f48fe647473a2dcccfd1839b2ff2c78eb57009be776b4da071e901c9bff/greenlet-3.5.6-cp314-cp314t-win_amd64.whl", hash = "sha256:886bcf1870af74c32bc310fd00a6b803445e17e51b7d5a107c7b35c0f362cc16", size = 331574, upload-time = "2026-09-14T14:27:18.451Z" }, + { url = "https://files.pythonhosted.org/packages/cd/72/3882855a75838faeb54a58aeef4fd77d20b2a86d4bad570c70d41b565dcf/greenlet-3.5.6-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:3ac3494c381dab876cad7d0b22f3a722f3e0c8deb3a65b9e7f35ad7f58b8fcb3", size = 295926, upload-time = "2026-09-14T14:27:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/10/1f/be4d957d8a9b90bcbe8db206548a42134d96222d43e5ed3fc4708fb6e24b/greenlet-3.5.6-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:602024dae6d77e161f4b89491b62ca1d4f19949d79d47b2db057e476d21179d6", size = 666910, upload-time = "2026-09-14T15:12:07.901Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/60d62571a7d6de961e4ce7625d6c2faf359345659fc782d2cdf517c34577/greenlet-3.5.6-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8e63209c3e1e828ee6a457529b4a6d8b05d050fe0ae03a7ae49e967c5d312e0", size = 677415, upload-time = "2026-09-14T15:20:48.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/16/ac9e547b611539aaed1870eb1d6ddc57abdd5924b3a99bb9b5f0b44176b8/greenlet-3.5.6-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccadce0130fd813ec86ebfe969a6c58b42acc1d0fe55a47525375b740e07b605", size = 675927, upload-time = "2026-09-14T14:36:05.34Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b1/b7ba08d6431121741f1d30be0d5d292e76873325179a63586cd9217b62f6/greenlet-3.5.6-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:9297fb9c39b9a2c039dbcd306c410bd6906b95244dec3bba4318d36c718c164c", size = 1637236, upload-time = "2026-09-14T15:10:12.442Z" }, + { url = "https://files.pythonhosted.org/packages/af/c5/3b1cbc68f0c082022fc8717f7fe4b8b13b8d583c52352be37f4e9f55bcd2/greenlet-3.5.6-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b374e79ffa7511afc11773aef40a4ccea6191fba1c856ea2f9c56738dca69d7a", size = 1698526, upload-time = "2026-09-14T14:35:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/12941ed2711400451c89d544e10f831800a2770f19dd55eac8f0f7f2003b/greenlet-3.5.6-cp315-cp315-win_amd64.whl", hash = "sha256:7969bffa322c097bd46ae595ada6a931cefda613f18ba64587e9cff4cb320756", size = 327739, upload-time = "2026-09-14T14:23:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3b/576b9ed5ac929252e340cf60b4bcb6a8515350dc20797064b1922dc4ea75/greenlet-3.5.6-cp315-cp315-win_arm64.whl", hash = "sha256:8dba0129b93e7091dfefaf4cf7000172741bff7f47bf6326fcf17f32fbb54d6b", size = 311715, upload-time = "2026-09-14T14:28:25.154Z" }, + { url = "https://files.pythonhosted.org/packages/16/c2/86cfc5555a98e12b86966ddbd24fd39af32f71f2f785c6595b7feb2db156/greenlet-3.5.6-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:de3de000d459402cda015068fd135aa50c0bf6f2477a80d4da1e646f123b4e78", size = 306244, upload-time = "2026-09-14T14:27:57.565Z" }, + { url = "https://files.pythonhosted.org/packages/14/6d/83ffc9d05a75a80ab3a7595dbb1d9604e5d4fc2996d73a8ae2dbd1284900/greenlet-3.5.6-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45663c01a4de48b9a64a2ee1509d92d1dfd3afb02b2ccfc9333029d11aef996a", size = 676578, upload-time = "2026-09-14T15:12:09.468Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d6/c2cf684810e5caded075970aaadea654ecb58b8382b9aecf1d231b936894/greenlet-3.5.6-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3deccbb57a481e3a408fe61cdfd5c13e0678fc0a30fdd09597917ca87b4be877", size = 684203, upload-time = "2026-09-14T15:20:50.261Z" }, + { url = "https://files.pythonhosted.org/packages/62/19/00e1bee5d2af890dc8f400b54d0b0f9b489965f92bc12b407ff72cc6f469/greenlet-3.5.6-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:311018b46472fb26ee85870847fb89eb64cc8aaddb617400789d87076f7cfeec", size = 681253, upload-time = "2026-09-14T14:36:06.742Z" }, + { url = "https://files.pythonhosted.org/packages/89/58/c9275fd0ca195d1d3402931bcce8cfcc74726ff76efb1883d229e6e1a3d7/greenlet-3.5.6-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:7f924a5a9d5890649566f2f6682e0d8ad8ca23028bacffbbac36dbd7fd680176", size = 1645988, upload-time = "2026-09-14T15:10:13.758Z" }, + { url = "https://files.pythonhosted.org/packages/e0/36/b35747582fa4f1a5453f8f3002405dbac788e450cec7674dc2d204b6ccb5/greenlet-3.5.6-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:de9923832f2d8c1a5ecd8d7260465a6ca5a86888a0d129e3bd5cf0406d2fc5bf", size = 1702383, upload-time = "2026-09-14T14:35:58.143Z" }, + { url = "https://files.pythonhosted.org/packages/ed/69/6ec22ac9351e474d2a134d0ff9400dc80362d1c20f0721088ffffdfc205b/greenlet-3.5.6-cp315-cp315t-win_amd64.whl", hash = "sha256:2ab5f42ac6c238eb71770715e6e909ad9a1a92b6c681ccb64cd5a0f07edb953f", size = 331845, upload-time = "2026-09-14T14:27:41.723Z" }, + { url = "https://files.pythonhosted.org/packages/30/cf/697c051fd534e223461fb8b523890e21a24eeca229cd50624cff6f02fabd/greenlet-3.5.6-cp315-cp315t-win_arm64.whl", hash = "sha256:f9fe868463ec7e1363733af77e38a5fda3e9b63940337048c945d69e0c80ff24", size = 315070, upload-time = "2026-09-14T14:22:21.476Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[package]] +name = "huamenchuang" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "aiosqlite" }, + { name = "fastapi" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt" }, + { name = "sqlalchemy" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.metadata] +requires-dist = [ + { name = "aiosqlite", specifier = ">=0.20" }, + { name = "fastapi", specifier = ">=0.115" }, + { name = "pydantic", specifier = ">=2.7" }, + { name = "pydantic-settings", specifier = ">=2.3" }, + { name = "pyjwt", specifier = ">=2.8" }, + { name = "sqlalchemy", specifier = ">=2.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.30" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" }, + { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" }, + { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" }, + { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" }, + { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" }, + { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" }, + { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.54" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/271aa905cf2964f841371a97f3e63ab692bf51b4423d0491e67bc7f64037/sqlalchemy-2.0.54.tar.gz", hash = "sha256:baa8521e8ee9f24e75dfc7aaabc08020e551ef0d48d7c3e3536f5cddf277586b", size = 9969559, upload-time = "2026-09-15T21:06:57.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/c0/4a6503c9d22d6d00a5631082ab1484222ecf7d573db791e0f53161bf7745/sqlalchemy-2.0.54-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:abd6b21bc58e91c1932eb5d6d7f1bd44a551dfec7b6a7f517c3638ccd67233a0", size = 2185899, upload-time = "2026-09-15T22:29:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/f4424f618bd1f373761a32a821d53ce2c257350e9894bae9b968cb03d8fd/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5417322b3c025dd82918725d3bf09ec105fac95efc195722b8b06e1d9c381139", size = 3394763, upload-time = "2026-09-15T22:29:38.131Z" }, + { url = "https://files.pythonhosted.org/packages/59/d2/7f0c77f8e042cb5f28275fea29c3080b4ac6fd4b3fdd7f59ff1ef3e28c11/sqlalchemy-2.0.54-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f84099e4b04a5c2d44500a2a8302eee5af4bc6fee63e8c6e9cf6786e747280e", size = 3402800, upload-time = "2026-09-15T22:40:36.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/32/3eaa930bcf71d17a72587081d2706a5fb97ab3f11a7e0fb838f581f7cff1/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a0956dc754d3884da7fe60097110ec7a8a105d26afa2f0844468f4b1598c6912", size = 3341469, upload-time = "2026-09-15T22:29:39.682Z" }, + { url = "https://files.pythonhosted.org/packages/eb/cc/cddb6cbd4408e5c55b3bf722be26b9d3b54d42509f901b7ecc15debd3d1f/sqlalchemy-2.0.54-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:87ba8834318b0d8dc94fc6f405d071b5c08be32a6c3fd68107fd6952ee949615", size = 3373232, upload-time = "2026-09-15T22:40:39.181Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/9c2aa5efc642b7f3b985d13565cd1a5e78856e079ef3022796fea5180498/sqlalchemy-2.0.54-cp314-cp314-win32.whl", hash = "sha256:842540e4382472f23c79589995752648d14696a8200d0807ed8c5c59c92ade44", size = 2142018, upload-time = "2026-09-15T22:42:55.118Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0b/3594f1f51769feb3022d686135dc5d8682a12345ed15ae61d0c0ca42cbee/sqlalchemy-2.0.54-cp314-cp314-win_amd64.whl", hash = "sha256:f4e8f955d13af83fb4e35c3472e5377ee22d3445eada1e5e48199588edb69835", size = 2169220, upload-time = "2026-09-15T22:42:56.727Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a5/c211a9a7af83222509519407e16a4db760c6df3d03be69ebc5414d465321/sqlalchemy-2.0.54-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ca05f4e7852cf48083b0cf157e4f9504b7068780422a50fa82f45353b8c5e14a", size = 2208458, upload-time = "2026-09-15T22:30:05.718Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2e/490ad7b3731116cb48ba170f7722eaa99a89707193e54389ca84b7ad55af/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a8b6417cbb7b735cf91c2b59453c2a554cefa0a8d7bd15aa35740739410d77", size = 3660585, upload-time = "2026-09-15T22:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/eb/25/15dfe6814847eeda773bd58ab6cf42a94b0176e5cf25a578fc1165777160/sqlalchemy-2.0.54-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e55a0b96a1577a1e108c91ccdeeb9cd92768f28ce206597311c3bf6d6423abd", size = 3624442, upload-time = "2026-09-15T22:36:38.377Z" }, + { url = "https://files.pythonhosted.org/packages/aa/19/724d0a6a2fb2a86ff2d6008e581c258f722d9b7d8e52adc7b79085febdd4/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:69cab115c40fd02c5a22c68e4ee630fa6ef9a1650f1de944419aab1f7096fc4f", size = 3562972, upload-time = "2026-09-15T22:36:08.581Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/9df1bd81c2f2d000cf5e7a1b1a9b331468a3aab939ad983355e701fa42b2/sqlalchemy-2.0.54-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e08397c6c42f53b2488acde9108b8bfefd52d7afd1bf2f03d2ffcab7a204aceb", size = 3576479, upload-time = "2026-09-15T22:36:40.272Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/b5775465d3b89061d7c46057c31c56ff8fb6c509550b2b0c6570ffc248b3/sqlalchemy-2.0.54-cp314-cp314t-win32.whl", hash = "sha256:b9086b8ad48280ef6a7ba68262d5e44f7db1c4cb1973e8cdae8a9f467ae66f51", size = 2174794, upload-time = "2026-09-15T22:31:45.925Z" }, + { url = "https://files.pythonhosted.org/packages/77/f8/296c2e46b4ccd3f29b00b954ef2f195dde32f98e352ed21de1d292cedc0d/sqlalchemy-2.0.54-cp314-cp314t-win_amd64.whl", hash = "sha256:b67c1744e453af833667fc1b84de07adb4a64f3536ef52a8ec5ac2b941d43970", size = 2211942, upload-time = "2026-09-15T22:31:47.368Z" }, + { url = "https://files.pythonhosted.org/packages/24/a1/bd5e3e99bc9c8863b51ac5b9b03008a7f2da8c6b59695992f5c654e1265b/sqlalchemy-2.0.54-py3-none-any.whl", hash = "sha256:7e33a631ab1474f8fe6b910bd1a07b7b8009c4c78cdd3fb18001b03e3bc2e1d2", size = 1958015, upload-time = "2026-09-15T22:24:22.95Z" }, +] + +[[package]] +name = "starlette" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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 = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.53.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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.optional-dependencies] +standard = [ + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, +] + +[[package]] +name = "websockets" +version = "17.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/72/fba934cb3dff7a85d811820efffcd141ddd52b5a2a01637f64551373ff4d/websockets-17.1.tar.gz", hash = "sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a", size = 187520, upload-time = "2026-08-26T17:25:33.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/0d/500cf5daea09d4669dff3a7d67159094a0bd6c4ef130381404f6edd3eb5f/websockets-17.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c9982938980e086da59f70d05f9418cd143401a601a0faac10fa48f7bb1cd3e", size = 217048, upload-time = "2026-08-26T14:56:36.03Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/5b12c6168aa269cffbfd24d177cd492b130120403a418c7e89462e27b4ac/websockets-17.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:57b39dc8541cf7ed3f639da82bf7451060483967f9e733da1f8173e4095f0642", size = 214737, upload-time = "2026-08-26T14:56:37.43Z" }, + { url = "https://files.pythonhosted.org/packages/0c/36/e453e5106e4e2416f008ac222837c2f1637a063b08008afcd1088889b631/websockets-17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:96abdecbaae746851b87c3a36cb4a661df93ca3d92f114270f79228bf1d00de6", size = 214955, upload-time = "2026-08-26T14:56:38.71Z" }, + { url = "https://files.pythonhosted.org/packages/dd/30/0204bb86176db02cdfc678ce65ed808a66fab87d250ce61a8790800a60b0/websockets-17.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9fc873e239c5abeb150bc24dbd1a7af23a9254526383ce0a077f5e20adbeb19", size = 224331, upload-time = "2026-08-26T14:56:39.924Z" }, + { url = "https://files.pythonhosted.org/packages/46/c8/d8372256e00c4e3cab1115c45075d1eeedb642a3f2b42bd70c4deae03f06/websockets-17.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f42912fa9eb4cb7c7ec9fde9b3332ba339eb8a8811981043d4029599f3d950b", size = 224685, upload-time = "2026-08-26T14:56:41.169Z" }, + { url = "https://files.pythonhosted.org/packages/12/7d/650355b8f67f908ff99603351d4458d1a0b787d627950a47c38db7e25308/websockets-17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f98bf378d7a5be047a044a1a27c987a8f355e10e3b5754617dbe756248cbc5ce", size = 225927, upload-time = "2026-08-26T14:56:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/34/6c/a9ffa5b903579eed76017870f055d75ecc73988d9d0c9b65a92ba0bf2a27/websockets-17.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d334d11398086bb5559606cb42d51c013ea7c061c7db701521392373d3c087f5", size = 227300, upload-time = "2026-08-26T14:56:43.538Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/4551c2269066af7481ee44605a0813770961615b5b5da3e87a8f5cb859ea/websockets-17.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c27336b1a0ac56569493e858497870347854372395f50483725f8cdacc5a45c", size = 226533, upload-time = "2026-08-26T14:56:44.669Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/237a99233e5c445759a613831b3a92e91905afc064dc3bd0ad33c35fd1e2/websockets-17.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67258b00302a5aaf0b267771c7014b13429abd7ea17eebc4c55bd935ff101555", size = 225280, upload-time = "2026-08-26T14:56:45.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b5/e9407a91613d1d1cd932414143a1012096b26674a782fc55a0bd23217ee4/websockets-17.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:455ffeea0879d313205df1e745e5883e1feb7f31ecd26be882f5f0babd3db04f", size = 222540, upload-time = "2026-08-26T14:56:47.053Z" }, + { url = "https://files.pythonhosted.org/packages/db/d2/db76628db0577b783205d9779f64d8e373416b04c62d1546be4b75dc8540/websockets-17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f7233eaf441a345a5943a929fd4b5ea3278f11aed35a9ed0f3106b8cb3ca846a", size = 225354, upload-time = "2026-08-26T14:56:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4c/2174181c067b89a74ae18e2650c2ac29959f4b796afe876ab3f4d30d642c/websockets-17.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c65da239a5ad553619804c1f9d65c1a0b3005381c6158ee14da2c7444cbd0c78", size = 223867, upload-time = "2026-08-26T14:56:49.579Z" }, + { url = "https://files.pythonhosted.org/packages/df/75/274decb9a8253561b5be3261e02a6676fc8ecdf31e95b722e53d5bfb8fd2/websockets-17.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fa1ffa08c81a4f809cdab6129f8e55bee4650b9d6d3461019dda73aacd146b6", size = 224652, upload-time = "2026-08-26T14:56:50.885Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e6/49824f1fb4db7656d2f7492b1d8be16147b759d909490e32f4776843ee64/websockets-17.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:406b8107943a43ef4649b1e0cb0cdc052bbf08fe1c8905a623c4af9586e5cebb", size = 225822, upload-time = "2026-08-26T14:56:52.356Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6a/5dc43838c0b02a95f42c47a0de33c5ddd7767a9feeb4d0d8777ac1cfefe4/websockets-17.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:4e8ffcb486c8490a34a4cef5e4409d8da5a1cb1681e5bf7d786ce5e84aa8540d", size = 223379, upload-time = "2026-08-26T14:56:53.699Z" }, + { url = "https://files.pythonhosted.org/packages/c2/62/585637cf06d6b321232f79c55dc14d65518d12cf87c94c44f5864068810e/websockets-17.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fb88076df585b69c5761c387c0081aa87d7b9eb1b205a6535ca4777e25650d81", size = 224330, upload-time = "2026-08-26T14:56:55.184Z" }, + { url = "https://files.pythonhosted.org/packages/de/68/c3b234a6a1366b6ab5bbfaa4434a1b946e1dc4e8ddd6824bfd93a8835b7f/websockets-17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5d4724255fb8398acd9e583b97eb2279cec20e0bd0f9a94bf75f6056ef9f13da", size = 224622, upload-time = "2026-08-26T14:56:56.393Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d4/84cf3d1376f5d8207f55f43c1c818babd6b89447f5dcd01f18a6d5526796/websockets-17.1-cp314-cp314-win32.whl", hash = "sha256:be3f0129c5654517b2abf07dcb75bb1d9479759a4ccfb569e8293579e9fc029a", size = 217036, upload-time = "2026-08-26T14:56:57.652Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0f/9e7ac63c5d7cb642952200814f584318e65146df008b7d375d5d9c6b2c97/websockets-17.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a4dc6ef83f4559e0d05f313a375cb38f63c986096a9da99fe94fdd779d313e5", size = 217382, upload-time = "2026-08-26T14:56:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/1ae6b91f7f3ac05f5c9f14a72dc2181c115ff370bcd8a7f10f02c174adfd/websockets-17.1-cp314-cp314-win_arm64.whl", hash = "sha256:46c0331c9eaaf73a559f3a9e388466be0df96eb83d40f06f1ca6ab6613b35c82", size = 217268, upload-time = "2026-08-26T14:57:00.654Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f0/f65644d0e0b2b90918a8c41503841cc4072a58f2bf76c09bc36e751fc0dd/websockets-17.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d411ea5ca18ac1b12c0c94be88b60c18ca641ac43bcdfdf1c9f79d46cdbe1603", size = 217379, upload-time = "2026-08-26T14:57:02.181Z" }, + { url = "https://files.pythonhosted.org/packages/ff/35/4c46d1f620ac1a30f92b6eae78ee40a772a93f568647ca7ccdc5ea283cf8/websockets-17.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:07fa3e7c30e2c577928d359b56bf872a3e0cbcc15553eaa0907c1ee86344b56f", size = 214911, upload-time = "2026-08-26T14:57:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/4587e8406d7c1188e97b9cf466c081e93399380d447f885bfce81626cd37/websockets-17.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de9acef07e3a78e9567fcd26c29011a4da8f050b13004bbf880a0fd82a6eea5", size = 215115, upload-time = "2026-08-26T14:57:04.692Z" }, + { url = "https://files.pythonhosted.org/packages/ec/06/1381c8fff525041025909eb80ace32489194a00ba22a0a8d428030afcc84/websockets-17.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ea0ed9373b880115911d9d39634bccc95b8ce590c9c42e8589f5cacc3ef3cee2", size = 224696, upload-time = "2026-08-26T14:57:05.899Z" }, + { url = "https://files.pythonhosted.org/packages/36/9d/9034e867dc85340be058619751742b895f722326e83100d110063461ca07/websockets-17.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50903d335bfda026c2fa11dd9aed09d8cbee0c451e3a85122a9acb041b7dc69b", size = 224975, upload-time = "2026-08-26T14:57:07.262Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/ed03aa3cae748ebf6397e5d44028f433f746bad09dc568ff754fda3a3c9b/websockets-17.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a74531ce81af587f906ab42f194032388fcff8fc7938402e5917c9147a39441", size = 226151, upload-time = "2026-08-26T14:57:08.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c9/cc1964a096d16f3b73cb1ee5f14f277f5a3bcac07c6e8f9a1dcded99f4c8/websockets-17.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8fbf28e639544503b7d1c96452a5e5e043e4108d89b1f3fa02910603622d19db", size = 228292, upload-time = "2026-08-26T14:57:09.846Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/46da6dd0363c2db2e4876fd59a40fd40c1943a82d7018d0a33afbce47d52/websockets-17.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f612dc57f00c07cf4aa2673f7cbceabd654ad2457b7e639f061b794d6e11f9fd", size = 226722, upload-time = "2026-08-26T14:57:11.118Z" }, + { url = "https://files.pythonhosted.org/packages/78/98/ecd8f5e1c5d0e54c08ebc5c66852271112166db68107cb0e17ca1bf25009/websockets-17.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c7ac77401227212dc6e849182feee50d57cf456ec6329ffd6979c94bb136c5c", size = 225451, upload-time = "2026-08-26T14:57:12.601Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/da8d2760db53e17aae763738b6ba834b1fcf16813d3632f3edb6951e1ec8/websockets-17.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32a2a68d989d6e5b74a9d5095415c51189ebae29fceb7cf2b64a1c0318a81256", size = 223003, upload-time = "2026-08-26T14:57:13.875Z" }, + { url = "https://files.pythonhosted.org/packages/a4/40/ea401c141a79c5b1d0021a0dab9d0df2051c108f1620fbb39a6e7c714c3b/websockets-17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:aec00f018d34c67500ff0438dc314b40277be4a1b983cbacbf53ccf7db63e257", size = 225704, upload-time = "2026-08-26T14:57:15.091Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8e/07ab3f44215d89840d5385fdcaaab1fed8caeffa67c6899e15062957c12c/websockets-17.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0014eaff8ad5b3b43feda2279f9d34bf2eaae040720b9fbbb55944b10f40b14d", size = 224192, upload-time = "2026-08-26T14:57:16.3Z" }, + { url = "https://files.pythonhosted.org/packages/58/93/ccf1af0a23e5748d4e22292a377d78d15cf294d7e707bbb11a8990ae6bd5/websockets-17.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:db9d7ee47f3ba531e278be539af39e2c7c7d28fb94897b6cd1120d63b0ef5922", size = 225082, upload-time = "2026-08-26T14:57:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/e32200f99ce282e728d2929f2c429db353cf3282db7d0eba99eb32c9fec1/websockets-17.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:ff3e2ba7a9f0a110b0555452e9b5a03a34e11662544e01beea15f144b48ba7b7", size = 226101, upload-time = "2026-08-26T14:57:18.802Z" }, + { url = "https://files.pythonhosted.org/packages/28/3d/e7a6e9777b29433620167c98f3caaff0d6b08b1239a273ef7f7fd1393349/websockets-17.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6da17fc94bd270f5987b10bee113461ac36a36a98b0481ddcc98056e5a90001a", size = 223794, upload-time = "2026-08-26T14:57:20.313Z" }, + { url = "https://files.pythonhosted.org/packages/48/05/ac569090726dedd6656f3ee28b0c02dfb1ba76e898dceaccc2987a237cef/websockets-17.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:e8dc3fa6d6b7ead3f9de57895f41b116a28787548e066365d9d90f7356bcaad2", size = 224567, upload-time = "2026-08-26T14:57:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/14/50/4ef62941111db6b31193f4fabbb65f845a5177579040cb8fe0d774d25034/websockets-17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b65d5fe48219dc2d5e158de9e6514e75600f379cc7e37108d35f31764c155566", size = 224993, upload-time = "2026-08-26T14:57:22.86Z" }, + { url = "https://files.pythonhosted.org/packages/28/42/2b95ada4ea19bf3a2072b68669ce4f4afb212690b727d31640576287fd68/websockets-17.1-cp314-cp314t-win32.whl", hash = "sha256:2cce251f3e2469b99b6802b55435bcdd07123b41870f54c87b336183af9d7e68", size = 217168, upload-time = "2026-08-26T14:57:24.466Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/67d5ee08dd8060a37d612fd40a625b5376ad19ae48fe1c8ad428c278b817/websockets-17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8f6c38cdcaf98a911d7acc25577f2f9e710f3a2fc2bde1563556784320196b51", size = 217508, upload-time = "2026-08-26T14:57:25.983Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/822005d0c674451d2411027b878cdc128a2b7ea5a30d337d9e279da22eba/websockets-17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:d1e2f5fa2b6d01f0d85b4f223fea7ed1d504be282a02a81bd2be4817ef7a2f03", size = 217425, upload-time = "2026-08-26T14:57:27.324Z" }, + { url = "https://files.pythonhosted.org/packages/de/d5/99a6c6a1eb5d5ae9f45f59a3c97f4e3b21f310eb404a547fb3e7d2fc054c/websockets-17.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:88381602e379165b66244b2ebc29f9b23ea0851fbe63ae157f91ca324f072d6f", size = 216970, upload-time = "2026-08-26T14:57:28.575Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0e/1e7f6e833728193958d3ed3d67b5d57c3c7cfa948abf94d4bc553257c954/websockets-17.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:88bc5138e53903a85c354e59df7ba73ce306f7b09724cef74dba121e60a88ce2", size = 214699, upload-time = "2026-08-26T14:57:29.862Z" }, + { url = "https://files.pythonhosted.org/packages/07/00/95d39549f86e34425a0412bcbe61708dd1fc46af654e2134a6c4389102ad/websockets-17.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3546ef55b3a074494106508bc6505c73825970d2d9505f7bf53882b3e88b0d1e", size = 214927, upload-time = "2026-08-26T14:57:31.148Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/b442415fc4f7f9943b0fc8e8eebaa13923ca73361e167c439ba634eecbd9/websockets-17.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9ae55d24241fc055f22aea3ac924559069848bd0ad4ea065fdd72d2194685fe8", size = 224373, upload-time = "2026-08-26T14:57:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/a8/dd/b83537aae4cf61615b9d8b2dbb235c0030ba85457a6d934798273814600f/websockets-17.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7b349265fad6244013eecd99df8d83c12bf3013943e431f4fadd5bffc37db42", size = 224801, upload-time = "2026-08-26T14:57:34.041Z" }, + { url = "https://files.pythonhosted.org/packages/76/83/5ab0abed58454909e8dbab45086ac68ee4556d7a8ada26735addc909b903/websockets-17.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc5789e5ea182b77a38881383ada5347202a6c66f4857d054e075290e80b604b", size = 225967, upload-time = "2026-08-26T14:57:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/4b/26/e2412f2b998a8c1dfc00c0709ff6ee0c634dd0b0b4f92bdfe9667876b71c/websockets-17.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce13c7d233239e739600a57d4a73c1192ad8259e655a4d55aa1a454242bc809d", size = 227664, upload-time = "2026-08-26T14:57:36.493Z" }, + { url = "https://files.pythonhosted.org/packages/ec/25/0dd4495df3c0e02f6db705312ba85ab9b2dd42257dc23eb0da10066e4844/websockets-17.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1036189bd34b0bc1b10a4679321e2c7968af317efe6e8e4c1c5141c4254fb5bb", size = 226447, upload-time = "2026-08-26T14:57:37.781Z" }, + { url = "https://files.pythonhosted.org/packages/be/67/6df3f63ffc48f08126ed0cd2fd2a41092967c3e364f8ec100deae90b6d77/websockets-17.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e78fd4b7b2c5086a38671c9c882c1e643385eccea360b5b1fda4a105e590087e", size = 225343, upload-time = "2026-08-26T14:57:39.133Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8d/a8479bbb09ff054907d141123d8f52fb6ae5ac39c6dbe39e6a02a8408309/websockets-17.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46e7a10bf04318c7b0c0273791925ae5e1cbe4a11e34aa934d2ef27862058a80", size = 222748, upload-time = "2026-08-26T14:57:40.478Z" }, + { url = "https://files.pythonhosted.org/packages/40/fb/4c3d2a3269cde3f3087916de9c3d9fc5d7196b46846d8c3a9ae59ad0a884/websockets-17.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:33e45c7ea38428e740a7f233555d71df0b875cef7fc080acebc9654475e35335", size = 225453, upload-time = "2026-08-26T14:57:41.859Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1c/6467b401d19408f34e1c7389c222c2c7e1dfdf08c551190269b5eabc726c/websockets-17.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:6e63c01803be425ff062b7f7fc201a74def1d49fc94a2410dd17375df75936e9", size = 224112, upload-time = "2026-08-26T14:57:43.136Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5f/744e032ac80e11039a7447657ebabb46e9b5c2dbcec83be571335212932f/websockets-17.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:722ec21717eec6477bce582147a28acdfe034e604239466a6a95daedb863e774", size = 224646, upload-time = "2026-08-26T14:57:44.871Z" }, + { url = "https://files.pythonhosted.org/packages/9f/47/bcb9128d9afc4d0934d9192e2a24897ca2f7a63df2654904915349c6c46d/websockets-17.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:e74e41f0ad12ff1e8983e349daef79d37cc8280c743ce9d134d6c74c18dab5d6", size = 225797, upload-time = "2026-08-26T14:57:46.338Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e0/b058047b7cf565e1105b10ef6b6b24a6ebe3575678c7dc75a645334705a7/websockets-17.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:12fe8984a32dbfd084e0603f1a8d740c0180cb85b3174585c54a80d2455a8394", size = 223605, upload-time = "2026-08-26T14:57:48.175Z" }, + { url = "https://files.pythonhosted.org/packages/b9/69/fc1555bff884de363f1bf9eebf2836dbeb29fa7e4f957debb7bbcf43abba/websockets-17.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:01dcb47deebc40b38fd4a493b9b9f4d0a704b7bec6f35e4d34085b329abce71a", size = 224508, upload-time = "2026-08-26T14:57:49.407Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/648d4e68621688b19093b06f7b497d520952e68cdea1c1b54371fe9491de/websockets-17.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f4c45ee2512d3757b5e6c67c5a34e435143f2ecb7df3324f9fd888688c45c0f4", size = 224767, upload-time = "2026-08-26T14:57:50.799Z" }, + { url = "https://files.pythonhosted.org/packages/58/93/f8342b55864f71df13eb8e9ef7dce691b87a87f04f75bb8a1385b3336e7c/websockets-17.1-cp315-cp315-win32.whl", hash = "sha256:0f4f50dfe2cc810fc4e2de979b35e83bf8bb4bccdc6fe472d93762ea7b1d5927", size = 217003, upload-time = "2026-08-26T14:57:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f0/7b5fdb774c245e0b6217009e2a24d2105c1a64923949f33be41aa7959302/websockets-17.1-cp315-cp315-win_amd64.whl", hash = "sha256:4af784f3e436f65b355c117c6497320f2b5cf6a559295cb1c4c7338e335d45cc", size = 217300, upload-time = "2026-08-26T14:57:53.492Z" }, + { url = "https://files.pythonhosted.org/packages/76/33/1fe6ed1b5087516115ca451b2c240314b010647071f8fc3bd78a21e4dddb/websockets-17.1-cp315-cp315-win_arm64.whl", hash = "sha256:d58159af7835fde09c462394293c0d7aaf8fb4557d8f8e5699f5e722ccae013d", size = 217214, upload-time = "2026-08-26T14:57:54.88Z" }, + { url = "https://files.pythonhosted.org/packages/94/ca/ed02e75996a266d76c5fcb5dd9b930db4cf2b388ca5fa3d2a72086f81568/websockets-17.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1a5cf4e7bbe3ca499e6a289206cb4fcb7444b09919e129bd517f57d5fa192c13", size = 217282, upload-time = "2026-08-26T14:57:56.108Z" }, + { url = "https://files.pythonhosted.org/packages/bd/7d/d536f5bc89ea5b52fd1c1727c59fabafee6bc41f5ce92c3bd2f83047908c/websockets-17.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:416b4bc8789a1865a3ff643ec4ee073a5f52402d0dbeafd27b1798d5dd6b6a51", size = 214863, upload-time = "2026-08-26T14:57:57.355Z" }, + { url = "https://files.pythonhosted.org/packages/37/37/944cf17bad668e9be1247e6314f88a48b9faf7c250e383410db8b38af0b9/websockets-17.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:259f45358c76d3b18489e3e80636cdbe807e05ecf1b10fdf1a779106d23d0c8e", size = 215073, upload-time = "2026-08-26T14:57:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/74/bf/3267966cc1bbc2b8fa62fd329651b0af502df1f5d1c0eed027ff339d6aa8/websockets-17.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d9d01e8ede41fea4f5a847dad9d628355f74905f437a5b6856d67aa66d193800", size = 225229, upload-time = "2026-08-26T14:58:00.235Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d8/85ea722f483510abb39fc71aafb4465d17cf9051a275ab036874ff3c300c/websockets-17.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7b35181a14cbfcae163b4de545d22abfd07d06c2c41ca69cfcd99251d6888ab", size = 225500, upload-time = "2026-08-26T14:58:01.994Z" }, + { url = "https://files.pythonhosted.org/packages/50/ce/64c7d00005bd0d15ecb5c5fcb7fb2597b6b92ddd16c4fa6bbc3d2835ad63/websockets-17.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a8e768a048c2220697477ce2e67e4345dc9f693d0ee6af53945b5e30227c6a7", size = 226829, upload-time = "2026-08-26T14:58:03.327Z" }, + { url = "https://files.pythonhosted.org/packages/b4/dc/096c67940fb957e667ca3c542818150434eb0388c6fdc90b3a502f3c3e96/websockets-17.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:880069d21cc33a558dcf180924a546d1ecf8ada5be3e4e70acee87019d706a24", size = 228457, upload-time = "2026-08-26T14:58:04.78Z" }, + { url = "https://files.pythonhosted.org/packages/51/fe/f2331b6b7ccc67589891da354fa46a5cb79e95f83b9fd0e734d77f1f2140/websockets-17.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cec1bb8f22abccc8d20f8ca63df9be41600c26c190f4b97ee86c675fd4a863a6", size = 227265, upload-time = "2026-08-26T14:58:06.102Z" }, + { url = "https://files.pythonhosted.org/packages/47/a5/fb1642302f8ec77ca922203074f155a9831a5128ad75e725059a476d1227/websockets-17.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3a1d577e081667dda7f8e5b4796e6e32f9713c93e2a3d930669519840a3c623", size = 226143, upload-time = "2026-08-26T14:58:07.464Z" }, + { url = "https://files.pythonhosted.org/packages/d7/41/7133fcfb63f5562750b269d6a845c689dde6a2c6407286da395beea19ddd/websockets-17.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc053f9e95a76213c5eb7ed95779f7daf0d2bf0e4e03073629ebfa43a033f151", size = 223501, upload-time = "2026-08-26T14:58:08.766Z" }, + { url = "https://files.pythonhosted.org/packages/64/b1/82b36bfabc79ff2d383a1fc043cee6a13f794ef4f6bf1b4810ad6988cf6f/websockets-17.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:bb0efe019480a1c93e168ce96479273aaebd672fc8c350d5eed1e507ababb1b8", size = 226330, upload-time = "2026-08-26T14:58:09.987Z" }, + { url = "https://files.pythonhosted.org/packages/41/7d/5b511b9bf6e9ad331e6ff902fcbcc71c3794d10ef3b5efe80ccb8f0a7861/websockets-17.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:615746b12b26a3fd4077bc6fbeb277a1c192a45dd57b531d07ad9ed5c52a9a7a", size = 224980, upload-time = "2026-08-26T14:58:11.303Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/aed08f25301f8eef23be903ff9319fcf35630ca2bdec9d226f7d804dd5b3/websockets-17.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:1a20136d61f9ca3a31493732762661fafc2c20e8861930214e21afc6a8a692a2", size = 225478, upload-time = "2026-08-26T14:58:12.543Z" }, + { url = "https://files.pythonhosted.org/packages/3e/47/0d63d4168536b4682c9d19b7399443b1176f25dbb68878374fa716670230/websockets-17.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:2786cbd273ab69c22612db8a41229ddf2c158060b17b5928884bf388d07887f3", size = 226588, upload-time = "2026-08-26T14:58:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/844bd0b6386fc81ed6a55f4b6dd26f01c6987eda205afa10175ea12b2164/websockets-17.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c323fc3be1dc3f87f6c59458cb7d9e13dcbbf971d6c3f3e2bbaf58d3bfcdfe", size = 224336, upload-time = "2026-08-26T14:58:15.778Z" }, + { url = "https://files.pythonhosted.org/packages/96/18/03709c84bc88ec4dcea68d4be4ccd07d611073dec111203a5bf45af8809d/websockets-17.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:12c8e2b25df59755954a04dfa09c990b96691025aaf7eafd19ed6da24b09c18d", size = 225197, upload-time = "2026-08-26T14:58:17.141Z" }, + { url = "https://files.pythonhosted.org/packages/27/cf/0d1c694b6466c89e875b85b32b51312c472cf6708eee91914866f5087dde/websockets-17.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f58f58b4b29bbea2a3635e2c56eff4a3adab011fe383802a9e542e31b97085fc", size = 225493, upload-time = "2026-08-26T14:58:18.521Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f5/99857c3dd9676749f33e3668665a34ad6099505fb8d75eb084f49f7807a9/websockets-17.1-cp315-cp315t-win32.whl", hash = "sha256:f78a3ffb1994304db2c0c4588e4d1a518079b557054fa3bb985a6f5e50ff49a3", size = 217130, upload-time = "2026-08-26T14:58:20.037Z" }, + { url = "https://files.pythonhosted.org/packages/2c/84/77599922ab441bfe61508f97dab2c71f8e114d31793993ea54011db16199/websockets-17.1-cp315-cp315t-win_amd64.whl", hash = "sha256:ad68c28a27246fed109a4409393d677b7e1388345cbbd2f5aee5c182d8506110", size = 217448, upload-time = "2026-08-26T14:58:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3c/8b9a225b523f06a9389be81f1b0ab07c49bec6014742e6aa359c1f920f1f/websockets-17.1-cp315-cp315t-win_arm64.whl", hash = "sha256:e552e0037230ac16e5f568de7012041344d1b18c9feed30ec2891b8eba55af81", size = 217372, upload-time = "2026-08-26T14:58:22.807Z" }, + { url = "https://files.pythonhosted.org/packages/41/63/23572870e01836a98346075b9e17a8bc24a6ddd9800a3204ceee58677f3c/websockets-17.1-py3-none-any.whl", hash = "sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23", size = 211134, upload-time = "2026-08-26T17:25:31.397Z" }, +]