Author SHA1 Message Date
yeuimu 5f4e1ffa1f feat: 快递100上门取件集成(SDS运单自动生成+面单回传)
- api/kuaidi100: 上门取件API客户端(bOrder/price/cancel/detail,MD5签名封装)
- api/sds: 变体克重(children接口,24h缓存)/面单PDF上传/挂包裹/carriageNoStatus筛选
- flows/sds/waybill_autogen: 每10分钟遍历运单待生成订单(factoryIds+carriageNoStatus+orderStatusCode)
  自动下极兔(returnType=20+tempid出面单),面单回调转PDF回传SDS;
  幂等跳过已有运单;面单超时不自动取消(记人工跟进);schedule 默认 active:false
- scripts: 手动E2E测试脚本 + 回调接收服务(验签MD5(param+salt)+JSONL落盘,配frpc 18090穿透)
- 依赖 +pillow(面单PNG转PDF)
2026-11-03 08:02:39 +00:00
inkreach d60a99ab4e Merge pull request 'feat: RIIN 商品拉取改用 goods/list 批量接口(200/批串行)替换逐单 order/goods' (#14) from fix/riin-goods-batch-list into main
Reviewed-on: #14
2026-10-31 10:23:48 +00:00
yeuimu 4a7c44a354 feat: RIIN 商品拉取改用 goods/list 批量接口(200/批串行)替换逐单 order/goods
CI / test (pull_request) Successful in 1m11s
- api/riin/client.py: 新增 get_order_goods_batch(distribution/order/goods/list)
- flows/shared/clients.py: fetch_goods_concurrent → fetch_goods_for_orders,
  批量 200/批、串行、批间 2s,批级重试;移除逐单全局限速(340~500ms)机制
- flows/riin/_pipeline.py: 适配新批量拉取
- flows/shared/db.py: 存在性预查 unnest cast timestamp[] → timestamptz[]
  (入参为带时区偏移的 tz-aware datetime,timestamptz 才能稳定匹配)

背景:旧逐单接口高频被软限流(成功返回空 data),10-28 起日均缺口 13~33%;
批量接口调用量从日均 ~1 万次降到 ~50 次,已实测回补 7689 单/9717 条商品全量成功。
2026-10-31 10:01:50 +00:00
inkreach 40288c2cd6 Merge pull request 'fix: EVRI 面单 OCR 单号入库前去掉 '-'' (#13) from develop into main
Reviewed-on: #13
2026-10-30 01:53:01 +00:00
yeuimu 740bd803a9 fix: EVRI 面单 OCR 单号入库前去掉 '-'
CI / test (pull_request) Successful in 36s
2026-10-29 09:41:53 +00:00
inkreach a7fd330622 Merge pull request 'fix: order/goods 限速收紧至峰值<3次/秒(340ms+0~160ms随机间隔)' (#12) from inkreach/order-goods-throttle into main
Reviewed-on: #12
2026-10-23 11:14:11 +00:00
yeuimu f87662ba4b fix: order/goods 限速收紧至峰值<3次/秒(340ms+0~160ms随机间隔)
CI / test (pull_request) Successful in 1m8s
2026-10-23 11:06:49 +00:00
yeuimu eb97ee1647 fix: RIIN order/goods 全局限速(并发20→5+150ms间隔,峰值≤6.7QPS);暂停 recent-sync/verify 调度(分销侧反馈调用量过大) 2026-10-23 10:57:46 +00:00
rom 0baac665da Merge pull request 'Develop' (#11) from develop into main
Reviewed-on: #11
2026-10-21 09:50:24 +00:00
rom 1a13075926 fix(ci): 锁定 Python 3.12,避免 uv 选 3.15 触发全量源码编译
CI / test (pull_request) Successful in 3m40s
2026-10-21 17:45:56 +08:00
rom 019cbadd1e feat: 新增 shop-dim-sync flow 并注册每 5 分钟调度
CI / test (pull_request) Canceled after 5m33s
2026-10-21 16:55:31 +08:00
inkreach e6fdda0b3f Merge pull request 'feat: OCR 新增支持波兰(PL,版式 pables_dpd/pables_inpost)' (#10) from inkreach/ocr-pl into main
Reviewed-on: #10
2026-10-15 07:00:30 +00:00
inkreach e4885803df Merge pull request 'fix: 写库重试 3→5 次、退避拉长至 1/3/7/15/30s' (#9) from inkreach/harden-db-write-retry into main
Reviewed-on: #9
2026-10-15 07:00:22 +00:00
yeuimu 2321e8ae9f Merge branch 'inkreach/ocr-pl': OCR 新增支持波兰(PL) 2026-10-15 03:14:40 +00:00
yeuimu fdcf107993 feat: OCR 新增支持波兰(PL,版式 pables_dpd/pables_inpost)
CI / test (pull_request) Successful in 2m49s
2026-10-15 03:12:21 +00:00
yeuimu e5175a0504 fix: 写库重试 3→5 次、退避拉长至 1/3/7/15/30s
CI / test (pull_request) Successful in 4m30s
sceptical-finch(riin-recent-sync 10-10 16:04)失败复盘:17track 物流回写
(宿主机本地进程,不走 advisory 锁)与 sync 写库在 panel 触发器链
(panel.orders/dims/dims_daily)上交叉持锁,冲突窗口持续约 3 分钟,
原 3 次重试(1/3/7s)全部耗尽。加深重试使其可跨过分钟级外部冲突窗口。
2026-10-10 09:08:01 +00:00
inkreach 489dacca49 Merge pull request 'Develop' (#8) from inkreach/develop into main
Reviewed-on: #8
2026-09-28 07:29:10 +00:00
yeuimu 5eacfefb6c fix: RIIN 商品状态补 CLOSE=已取消(重拉残留时告警发现的新枚举;UNKNOWN 保留 NULL)
CI / test (pull_request) Successful in 2m8s
2026-09-28 03:19:29 +00:00
inkreach 63d30d8f25 Merge pull request 'fix: RIIN 商品状态补 COMPLETE/PAUSE 映射,修复 Goods.status 大面积 NULL' (#7) from inkreach/fix/riin-goods-status-map into develop
CI / test (pull_request) Successful in 48s
Reviewed-on: #7
2026-09-28 03:04:39 +00:00
yeuimu c030eca178 fix: RIIN 商品状态补 COMPLETE/PAUSE 映射,修复 Goods.status 大面积 NULL
CI / test (pull_request) Successful in 1m35s
问题:RIIN 商品级 goodsStatus 复用平台状态枚举,COMPLETE/PAUSE 无映射,
.get() 返回 None 落库为 NULL,累计 206 万行(占 RIIN 商品 33.75%),
终态订单不再被 refresh_pending 刷新而长期滞留。该缺口继承自
inkreach-bi-auth-mng TS 版,且 TS 的 warnUnknown 告警在移植时被省略,
导致从未暴露。

修复:
- RIIN_GOODS_STATUS_MAP 补 COMPLETE=已完成、PAUSE=暂停
- 对齐 TS 版 warnUnknown:四个状态映射函数对未知值告警(同值只报一次)
- docs/sql:Goods.status 约束新增允许"暂停"(原约束拒绝,已在库执行)
- scripts/backfill_riin_goods_status.py:存量 NULL 按"已发货"订单终态
  近似回填(幂等、分批);"已关闭"与非终态订单走 by-orders 重拉真值
- tests/test_mappers.py:9 个单测全过;实单 GSU1S555400MFLL e2e 验证
  4 行 NULL → 已完成/暂停
2026-09-28 03:02:05 +00:00
inkreach fff5b707ac Merge pull request 'feat: OCR 新增支持西班牙/意大利/加拿大(ES/IT/CA)' (#6) from inkreach/ocr-es-it-ca into main
Reviewed-on: #6
2026-09-24 03:56:05 +00:00
yeuimu c6be5bacf6 ci: 检出兼容 AGit PR 的 refs/pull/N/head(clone --branch 只认分支名,改为 fetch 后 checkout)
CI / test (pull_request) Successful in 1m6s
2026-09-24 03:53:34 +00:00
yeuimu 2259bce7c4 feat: OCR 新增支持西班牙/意大利/加拿大(ES/IT/CA)
CI / test (pull_request) Failing after 1s
- api/ocr/models.py: SUPPORTED_COUNTRIES/_COUNTRY_MAP 补 CA/ES/IT 的 ISO 码与中文国名
- flows/label_ocr.py: label-ocr 默认国家列表扩为 8 国,docstring 同步
2026-09-24 03:49:46 +00:00
rom d07c67327e Merge pull request 'Develop' (#5) from develop into main
Reviewed-on: #5
2026-09-23 03:46:14 +00:00
rom 10d3c5f278 feat: 新增 platform-order-upper-sync flow 并注册部署
CI / test (pull_request) Successful in 1m2s
2026-09-23 11:41:10 +08:00
rom 6007b2c34b 增加商户号
CI / test (pull_request) Successful in 1m7s
2026-09-21 10:52:45 +08:00
rom d8e4adb0b5 增加商户号 2026-09-21 10:43:49 +08:00
rom 9c76772969 Merge pull request 'Develop' (#4) from develop into main
Reviewed-on: #4
2026-09-20 09:04:49 +00:00
rom f37ef24cf0 增加商户号
CI / test (pull_request) Successful in 45s
2026-09-20 16:53:49 +08:00
yeuimu 1393b2a86b fix: platform_order 本地直跑改为直接执行,不再 serve 同名 deployment(会覆盖正式部署导致 Not Ready) 2026-09-19 02:57:01 +00:00
yeuimu 5aaddbbf7c fix: worker 加 init 回收僵尸进程;label-ocr 批量请求重试且失败不落 ERROR
- docker-compose: prefect-worker 加 init: true。pull 步骤的 git 子进程被杀后
  无人 wait 变僵尸,9/17 宿主机重启后爆发 3.7 万僵尸占满容器 cgroup PID 配额,
  导致所有 flow run "Failed to start flow run process"(全量 CRASH 约 40h)。
- label_ocr: OCR 编排服务(lm-server-canary)偶发秒级重启,原逻辑对批量请求
  失败零重试并整批标永久 ERROR,9/1、9/5、9/9 三波共 ~2500 单误标。
  现改为请求级重试 3 次,仍失败则保持未处理,留给下个小时 run 自动重试。
2026-09-18 10:09:24 +00:00
inkreach a83b89b08a Merge pull request 'Develop' (#3) from develop into main
Reviewed-on: #3
2026-09-17 09:31:45 +00:00
yeuimu fbba147413 merge: 同步远端 develop
CI / test (pull_request) Successful in 35s
2026-09-17 09:11:54 +00:00
yeuimu 6800384f9a ci: 检出代码改为直接 clone Gitea, 摆脱对 github.com 的依赖(action 下载超时) 2026-09-17 09:10:55 +00:00
rom 000a0a934e chore: 补充 flows/platform_order 包 __init__.py
CI / test (pull_request) Failing after 2m2s
2026-09-17 14:55:56 +08:00
rom 1dead528fc 增加sm数据拉取数据 2026-09-17 14:32:39 +08:00
rom 5be6f33982 增加sm数据拉取数据 2026-09-17 14:29:25 +08:00
yeuimu b513667b69 merge: 同步 main( Incorporate PR #1) 2026-09-11 01:41:09 +00:00
yeuimu 387044a26c ci: 新增 PR 轻量检查工作流, AGENTS.md 补充 CI/CD 自动部署说明 2026-09-11 01:40:23 +00:00
inkreach 2fda92eec4 Merge pull request 'docs: AGENTS.md 精简依赖管理, 运行环境与部署模型合并为部署流程' (#1) from develop into main
Reviewed-on: #1
2026-09-10 07:44:23 +00:00
yeuimu 3336e3a20c docs: AGENTS.md 精简依赖管理, 运行环境与部署模型合并为部署流程 2026-09-10 06:43:04 +00:00
32 changed files with 3337 additions and 84 deletions
+24
View File
@@ -0,0 +1,24 @@
name: CI
on:
pull_request:
branches: [develop, main]
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
# 不用 actions/checkout: 它需从 github.com 拉取, 本网络不稳, 改为直接 clone Gitea
- name: 检出代码
run: |
REF="${{ github.event.pull_request.head.ref || github.ref_name }}"
git clone --depth 1 "${{ github.server_url }}/${{ github.repository }}.git" .
# PR head 可能是完整 ref(AGit 建的 PR 为 refs/pull/N/head),clone --branch 只认分支名,改 fetch 后 checkout
git fetch --depth 1 origin "$REF"
git checkout --detach FETCH_HEAD
- name: 安装 uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: 安装依赖
run: ~/.local/bin/uv sync --frozen
- name: 语法与导入检查
run: ~/.local/bin/uv run python -m compileall -q flows api
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+1
View File
@@ -0,0 +1 @@
3.12
+10 -30
View File
@@ -12,26 +12,11 @@ ETL 工作流项目,使用 [Prefect](https://docs.prefect.org.cn/v3/get-started)
- 新增系统是加法式:加一个 `api/<系统>/` + 一个 flow,不碰旧代码。
- 详细架构说明见 [`docs/dev/systematic-design.md`](docs/dev/systematic-design.md)。
## 依赖管理:使用 uv(不要用 requirements.txt)
## 依赖管理
本项目用 [uv](https://docs.astral.sh/uv/) 管理依赖,锁定文件为 `uv.lock`。
**没有 requirements.txt,也不要新建。**
用 [uv](https://docs.astral.sh/uv/) 管理依赖(锁定文件 `uv.lock`,Python `>=3.12`):`uv sync` 装依赖,`uv add/remove` 增删包,一切命令用 `uv run ...` 前缀,不要直接用系统 `python` / `pip`。提交时一并提交 `pyproject.toml` 和 `uv.lock`;不要新建 requirements.txt,不要提交 `.venv/`。
```bash
uv sync # 按 uv.lock 安装依赖到 .venv(首次/拉取代码后执行)
uv add <package> # 添加依赖(自动写入 pyproject.toml + 更新 uv.lock)
uv add --dev <package> # 添加开发依赖
uv remove <package> # 移除依赖
uv lock --upgrade # 升级所有依赖的锁定版本
uv run python <file> # 在项目环境中运行(无需手动激活 venv)
uv run prefect <cmd> # 运行 prefect CLI
```
- Python 版本固定为 `>=3.12`(见 `pyproject.toml`)。
- 所有命令都用 `uv run ...` 前缀,**不要**直接用系统 `python` / `pip`。
- 提交代码时一并提交 `pyproject.toml` 和 `uv.lock`;**不要提交 `.venv/`**。
## 运行环境
## 部署流程
- **Prefect server + worker**:自托管,通过 Docker Compose 运行(server + worker 容器),数据持久化。
```bash
@@ -40,18 +25,13 @@ uv run prefect <cmd> # 运行 prefect CLI
docker compose logs -f prefect-worker # 看 worker 日志
docker compose down # 停止
```
- **API 地址**:`http://localhost:4200/api`(本机)/ `http://192.168.124.92:4200`(局域网)。
- 客户端已配置:`uv run prefect config set PREFECT_API_URL="http://localhost:4200/api"`。
## 部署模型:Work Pool + Git 拉取(推送即生效)
- 代码托管在自托管 Gitea:`ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-prefect.git`。分支:`main`(主干,worker 拉取)、`develop`(开发)、`deploy`(部署)。
- worker 是 **Docker 容器**(`prefect-worker` 服务,基于 `Dockerfile`),镜像只装依赖(git + uv 按锁文件装),**不烤业务代码**。
- 每个 flow run 执行前,pull 步骤从 Gitea **clone 最新代码**(token 存 `gitea-token` Secret Block,运行时解析)。
- **改 flow 代码 → `git push` → 下次 run 自动用新代码**(worker 不重启、不 redeploy)。
- 改 deployment 配置(cron/参数)才需重跑:`uv run prefect deploy --all`(读取 `prefect.yaml`)。
- **日常开发流程**:本地开发机 clone Gitea 仓库 → 改代码 → `git push` → 自动生效。
- 旧远程 Gitee(`git@gitee.com:inkreachZLL/inkreach-prefect.git`)已退役,保留作历史快照,不再推送。
- **API 地址**:`http://localhost:4200/api`(本机)/ `http://192.168.124.92:4200`(局域网);客户端已配置 `PREFECT_API_URL`。
- 代码托管在自托管 Gitea:`ssh://git@gitea.inkreach.cc:2222/inkreachhub/inkreach-prefect.git`。分支:`main`(主干,worker 拉取)、`develop`(开发)、`deploy`(部署);旧远程 Gitee(`git@gitee.com:inkreachZLL/inkreach-prefect.git`)已退役保留。
- worker 是 **Docker 容器**(`prefect-worker` 服务,基于 `Dockerfile`),镜像只装依赖(git + uv 按锁文件装),**不烤业务代码**;每个 flow run 执行前,pull 步骤从 Gitea **clone 最新代码**(token 存 `gitea-token` Secret Block,运行时解析)。
- **改 flow 代码 → `git push` → 下次 run 自动用新代码**(worker 不重启、不 redeploy);改 deployment 配置(cron/参数)才需重跑 `uv run prefect deploy --all`(读取 `prefect.yaml`)。
- **日常开发流程**:clone Gitea 仓库 → 改代码 → `git push` → 自动生效。
- **CI/CD 自动部署**:develop 合并到 main(push)→ Gitea webhook → 本机 9000 的 `inkreach-prefect-webhook` 服务(systemd,deploy 账号)→ 自动 `git pull + uv sync + prefect deploy --all` → 企微群通知。CI 检查:`.gitea/workflows/ci.yml`,PR 到 develop/main 时跑轻量语法检查(runner 标签 ubuntu-latest,本机 gitea-act-runner)。
- 部署脚本与密钥在 `/opt/inkreach-prefect-deploy/`(deploy.sh / rollback.sh / status.sh / webhook.secret / wecom_webhook.txt,均 deploy 账号所有);代码检出在 `/opt/inkreach-prefect`(deploy 账号所有,deploy.sh 自动 reset 到 origin/main)。
## 密钥管理(标准做法)
+8
View File
@@ -0,0 +1,8 @@
"""快递100 上门取件API(线上支付) 客户端。
文档: https://api.kuaidi100.com/document/603cb649a62a19500e19866b
"""
from api.kuaidi100.client import Kuaidi100ApiError, Kuaidi100Client
__all__ = ["Kuaidi100Client", "Kuaidi100ApiError"]
+160
View File
@@ -0,0 +1,160 @@
"""快递100「上门取件API(线上支付)」REST 客户端(httpx + pydantic 思路,纯客户端层,禁止 import prefect)。
文档: https://api.kuaidi100.com/document/603cb649a62a19500e19866b
鉴权与签名:
- 公共 body 参数 method/key/sign/t/param(Content-Type: application/x-www-form-urlencoded);
- sign = MD5(param + t + key + secret) 转 32 位大写,secret 在企业管理后台获取;
- 凭据(key/secret/customer/tempid)由调用方(flow 层)从 Secret Block 读取后注入,本层不落盘。
实测注意(勿踩):
- taskId/orderId 回传取消接口时必须原样保持类型(orderId 是字符串数字);
- cancelMsg 含"测试"字样会被后端判"订单信息错误",用固定文案"暂时不寄件了";
- returnType=20 出面单图片短链,经 callBackUrl 回调推送(status=200),tempid 必填。
"""
from __future__ import annotations
import hashlib
import json
import time
from typing import Any
import httpx
PROD_BASE_URL = "https://poll.kuaidi100.com/order/borderapi.do"
TEST_BASE_URL = "http://e-test.kuaidilab.com/api/order/borderapi.do"
CANCEL_MSG = "暂时不寄件了"
class Kuaidi100ApiError(RuntimeError):
"""快递100 业务失败(result != true 或 returnCode != 200)。"""
class Kuaidi100Client:
"""寄件下单(bOrder)/查价(price)/取消(cancel)/订单详情(detail)。"""
def __init__(
self,
key: str,
secret: str,
*,
base_url: str = PROD_BASE_URL,
customer: str | None = None,
timeout: float = 30.0,
) -> None:
self._key = key
self._secret = secret
self._customer = customer
self._base_url = base_url
self._http = httpx.Client(timeout=timeout)
# ----- 公共调用 -----
@staticmethod
def make_sign(param: str, t: str, key: str, secret: str) -> str:
return hashlib.md5(f"{param}{t}{key}{secret}".encode("utf-8")).hexdigest().upper()
def call(self, method: str, param: dict[str, Any]) -> dict[str, Any]:
"""POST 公共表单,业务失败抛 Kuaidi100ApiError,成功返回整个响应 JSON。"""
param_str = json.dumps(param, ensure_ascii=False, separators=(",", ":"))
t = str(int(time.time() * 1000))
body = {
"method": method,
"key": self._key,
"sign": self.make_sign(param_str, t, self._key, self._secret),
"t": t,
"param": param_str,
}
resp = self._http.post(self._base_url, data=body)
resp.raise_for_status()
data = resp.json()
if data.get("result") is not True or str(data.get("returnCode")) != "200":
raise Kuaidi100ApiError(f"[{method}] {json.dumps(data, ensure_ascii=False)[:500]}")
return data
# ----- 业务接口 -----
def price(
self,
kuaidi_com: str,
send_addr: str,
rec_addr: str,
weight: str = "1",
service_type: str | None = None,
) -> dict[str, Any]:
"""查价(只读)。返回 data:defPrice/defFirstPrice/price/overPricePerKg/serviceType 等。"""
param: dict[str, Any] = {
"kuaidiCom": kuaidi_com,
"sendManPrintAddr": send_addr,
"recManPrintAddr": rec_addr,
"weight": weight,
}
if service_type:
param["serviceType"] = service_type
return self.call("price", param).get("data") or {}
def border(
self,
*,
kuaidicom: str,
rec_man_name: str,
rec_man_mobile: str,
rec_man_print_addr: str,
send_man_name: str,
send_man_mobile: str,
send_man_print_addr: str,
callback_url: str,
cargo: str | None = None,
weight: str | None = None,
payment: str = "SHIPPER",
remark: str | None = None,
salt: str | None = None,
return_type: str | None = None,
tempid: str | None = None,
print_callback_url: str | None = None,
extra: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""寄件下单。returnType=20 + tempid 时面单短链经回调(status=200)推送。
返回 data:taskId/orderId/kuaidinum/pollToken/appointGotStartTime 等,
taskId/orderId 后续回传必须保持原类型。
"""
param: dict[str, Any] = {
"kuaidicom": kuaidicom,
"recManName": rec_man_name,
"recManMobile": rec_man_mobile,
"recManPrintAddr": rec_man_print_addr,
"sendManName": send_man_name,
"sendManMobile": send_man_mobile,
"sendManPrintAddr": send_man_print_addr,
"callBackUrl": callback_url,
"payment": payment,
}
for key, value in (
("cargo", cargo), ("weight", weight), ("remark", remark), ("salt", salt),
("returnType", return_type), ("tempid", tempid), ("printCallBackUrl", print_callback_url),
):
if value is not None:
param[key] = value
if extra:
param.update(extra)
return self.call("bOrder", param).get("data") or {}
def cancel(self, task_id: Any, order_id: Any, cancel_msg: str = CANCEL_MSG) -> None:
"""取消订单(取件前)。taskId/orderId 原样回传下单返回值。"""
self.call("cancel", {"taskId": task_id, "orderId": order_id, "cancelMsg": cancel_msg})
def detail(self, task_id: Any) -> dict[str, Any]:
"""订单详情(只读):status/kuaidiNum/payStatus/preWeight 等。"""
return self.call("detail", {"taskId": task_id}).get("data") or {}
def close(self) -> None:
self._http.close()
def __enter__(self) -> "Kuaidi100Client":
return self
def __exit__(self, *exc: Any) -> None:
self.close()
+3 -1
View File
@@ -37,14 +37,16 @@ class OcrResult(BaseModel):
# OCR 服务已有分类器模型的国家(版面见 api.md §4)
SUPPORTED_COUNTRIES = {"US", "UK", "DE", "MX", "SA"}
SUPPORTED_COUNTRIES = {"US", "UK", "DE", "MX", "SA", "CA", "ES", "IT", "PL"}
# ISO 国家代码 / 中文国名 → OCR 国家代码
_COUNTRY_MAP: dict[str, str] = {
# 代码
"GB": "UK", "UK": "UK", "US": "US", "DE": "DE", "MX": "MX", "SA": "SA",
"CA": "CA", "ES": "ES", "IT": "IT", "PL": "PL",
# 中文名
"英国": "UK", "美国": "US", "德国": "DE", "墨西哥": "MX", "沙特": "SA",
"加拿大": "CA", "西班牙": "ES", "意大利": "IT", "波兰": "PL",
}
+8
View File
@@ -246,6 +246,14 @@ class RiinDisClient(TokenHttpClient):
data = self.post("distribution/order/goods", body={"orderId": order_id})
return OrderGoodsResponse.model_validate(data)
def get_order_goods_batch(self, ids: list[str]) -> OrderGoodsResponse:
"""批量获取订单商品(distribution/order/goods/list)。
上游要求:单批 ≤200 个 id 且串行调用;高频/并发会被软限流(成功但空 data)。
"""
data = self.post("distribution/order/goods/list", body={"ids": ids})
return OrderGoodsResponse.model_validate(data)
def get_order_count(
self, params: OrderPageRequest | None = None
) -> OrderListCountResponse:
+97
View File
@@ -23,6 +23,7 @@ from api.sds.models import (
OrderQueryParams,
OrderStatistics,
OrderStatus,
ProductChildrenResponse,
ProductListRequest,
ProductListResponse,
)
@@ -30,6 +31,7 @@ from api.sds.models import (
BASE_URL = "https://pod-api.sdspod.com"
COMPANY_NO = "印美达"
KEYWORDS_BATCH_SIZE = 200
PRODUCT_CACHE_TTL_SECONDS = 24 * 3600 # 产品变体(克重等)是主数据,长缓存
FILTERS_FIELDS = (
"factoryList,merchantList,logisticsList,earlyWarningList,"
"issuingBayAreaList,logisticsSourceList,extendStatusList"
@@ -64,6 +66,7 @@ class SdsMngClient(TokenHttpClient):
self._username = username
self._password = password
self._cache: TTLCache[Any] = TTLCache(cache_ttl_seconds)
self._product_cache: TTLCache[Any] = TTLCache(PRODUCT_CACHE_TTL_SECONDS)
self._concurrency = concurrency
# ----- 认证 -----
@@ -129,6 +132,8 @@ class SdsMngClient(TokenHttpClient):
query["earlyWarningType"] = params.early_warning_type
if params.item_num_type is not None:
query["itemNumType"] = params.item_num_type
if params.carriage_no_status is not None:
query["carriageNoStatus"] = params.carriage_no_status
return query
@staticmethod
@@ -297,6 +302,98 @@ class SdsMngClient(TokenHttpClient):
merged.extend(r.records)
return ProductListResponse(total_count=first.total_count, records=merged)
# ----- 产品变体/克重(主数据,24h 缓存) -----
def get_product_children(self, product_id: int) -> ProductChildrenResponse:
"""产品变体列表(含每尺码克重 weight 克/件、包装盒尺寸)。
GET /pod/product/{id}/children;变体克重是主数据,缓存 PRODUCT_CACHE_TTL_SECONDS。
"""
key = f"children:{product_id}"
cached = self._product_cache.get(key)
if cached is not None:
return cached
data = self.get(f"/pod/product/{product_id}/children", params={"t": int(time.time() * 1000)})
result = ProductChildrenResponse.model_validate(data)
self._product_cache.set(key, result)
return result
def get_variant_weight_map(self, product_id: int) -> dict[int, float]:
"""{变体 id: 克重}。订单行的 product_id 即变体 id,可直接查。"""
return {
c.id: c.weight
for c in self.get_product_children(product_id).records
if c.weight is not None
}
def resolve_product_id(self, code: str) -> int | None:
"""产品编码(父 sku,如 RB9327005) -> 产品 id(keyword 精确搜索,长缓存)。"""
key = f"pid:{code}"
cached = self._product_cache.get(key)
if cached is not None:
return cached or None
resp = self.get_products(ProductListRequest(keyword=code, size=1))
pid = resp.records[0].id if resp.records else 0
self._product_cache.set(key, pid)
return pid or None
def get_order_weight_grams(self, order: Any) -> tuple[float | None, list[str]]:
"""按变体克重汇总订单克重。返回 (总克重, 未匹配行描述);None 时调用方需回退。"""
total, missed, maps = 0.0, [], {}
for it in order.items or []:
supply = getattr(it, "productSupply", None) or {}
parent_id = supply.get("productParentId")
if parent_id is None or it.product_id is None:
missed.append(f"行{it.no or '?'} 缺 productParentId/product_id")
continue
if parent_id not in maps:
maps[parent_id] = self.get_variant_weight_map(parent_id)
weight = maps[parent_id].get(it.product_id)
if weight is None:
missed.append(f"行{it.no or it.product_id} 变体无克重字段")
continue
total += weight * (it.num or 1)
return (total or None), missed
# ----- 面单文件/包裹(运单回传) -----
def _send_with_token(
self,
method: str,
url: str,
*,
files: dict[str, Any] | None = None,
json_body: dict[str, Any] | None = None,
params: dict[str, Any] | None = None,
) -> Any:
"""基类 _send 未覆盖的请求形态(multipart 上传 / PUT),带 token + 401 重登一次。"""
for attempt in (1, 2):
headers = {self._token_header: self._ensure_token()}
resp = self._http.request(method, url, files=files, json=json_body, params=params, headers=headers)
if resp.status_code == 401 and attempt == 1:
self._clear_token()
continue
resp.raise_for_status()
return resp.json() if resp.content else {}
return {}
def upload_waybill_pdf(self, filename: str, pdf_bytes: bytes) -> dict[str, Any]:
"""上传面单 PDF,返回含 CDN url 的文件信息。POST /files/pdf/logisticsWaybill。"""
files = {"file": (filename, pdf_bytes, "application/pdf")}
return self._send_with_token("POST", "/files/pdf/logisticsWaybill", files=files)
def attach_parcel_label(self, order_id: Any, parcel_id: Any, order_no: str, label_pdf_url: str) -> None:
"""把面单挂到订单包裹(覆盖语义)。PUT /orders/{orderId}/logistic/parcel/{parcelId}。
orderNo 传该包裹运单号(原样保留或回写新单号);labelPdf 为 upload_waybill_pdf 的 url。
"""
self._send_with_token(
"PUT",
f"/orders/{order_id}/logistic/parcel/{parcel_id}",
json_body={"orderNo": order_no, "labelPdf": label_pdf_url},
params={"t": int(time.time() * 1000)},
)
# ----- 物流 -----
def get_logistics_areas(self) -> IssuingBayAreaResponse:
+21
View File
@@ -219,6 +219,26 @@ class ProductListResponse(_SdsModel):
records: list[Product] = Field(default=[], alias="list")
class ProductVariant(_SdsModel):
"""产品变体(/pod/product/{id}/children):颜色/尺码/克重/包装盒尺寸。"""
id: int
parent_id: int | None = Field(default=None, alias="parentId")
sku: str | None = None
size: str | None = None
color_name: str | None = Field(default=None, alias="colorName")
color_code: str | None = Field(default=None, alias="colorCode")
status: int | None = None
weight: float | None = None # 克/件
box_length: float | None = Field(default=None, alias="boxLength")
box_width: float | None = Field(default=None, alias="boxWidth")
box_height: float | None = Field(default=None, alias="boxHeight")
class ProductChildrenResponse(_SdsModel):
records: list[ProductVariant] = Field(default=[], alias="list")
# ----- 物流域(混用 snake_case,显式 alias)-----
@@ -318,6 +338,7 @@ class OrderPageRequest(BaseModel):
extend_status: str | None = None
early_warning_type: str | None = None
item_num_type: str | None = None
carriage_no_status: str | None = None # 运单号状态筛选,如 generate=运单待生成
limit: int | None = None
all: bool | None = None
+3
View File
@@ -15,6 +15,9 @@ services:
prefect-worker:
build: .
container_name: prefect-worker
# init 进程(tini)回收孤儿/僵尸:pull 步骤的 git 子进程被杀后没人 wait 会变僵尸,
# 堆满 cgroup PID 配额后容器内无法再 fork(2026-09-17 事故根因)
init: true
environment:
PREFECT_API_URL: http://prefect-server:4200/api
TZ: Asia/Shanghai
@@ -0,0 +1,13 @@
-- 2026-09-28 Goods.status 约束新增允许 '暂停'
-- 背景:RIIN 商品级 goodsStatus 复用平台状态枚举,存在 PAUSE(暂停),
-- 原约束未包含该值,导致映射层只能落 NULL(chk_goods_status 拒绝其余新值)。
-- 配套:mappers.py 的 RIIN_GOODS_STATUS_MAP 补 COMPLETE/PAUSE。
-- 执行方式(两步,避免阻塞 ETL 写入;VALIDATE 只锁 SHARE UPDATE EXCLUSIVE):
ALTER TABLE "Goods" DROP CONSTRAINT chk_goods_status;
ALTER TABLE "Goods" ADD CONSTRAINT chk_goods_status CHECK (
status IS NULL OR status = ANY (ARRAY[
'备货中', '已删除', '已取消', '已完成', '待付款', '待确认',
'搁置中', '待发货', '已发货', '退款中', '暂停'
])
) NOT VALID;
ALTER TABLE "Goods" VALIDATE CONSTRAINT chk_goods_status;
+36 -12
View File
@@ -7,6 +7,7 @@
from __future__ import annotations
import os
import time
import httpx
from prefect import flow, task
@@ -16,6 +17,8 @@ from api.ocr import OcrClient, country_to_ocr_code
from flows.shared import SyncDB, build_stats_rows, emit_sync_summary
BATCH_SIZE = 200
BATCH_RETRIES = 3 # 批量请求级重试(OCR 服务偶发秒级重启)
BATCH_RETRY_BACKOFF = [5, 15] # 重试间隔(秒)
_OCR_FIELD_MAP = [
("layout", "ocrLayout"), ("courier", "ocrCourier"), ("tracking", "ocrTracking"),
@@ -26,13 +29,20 @@ _OCR_FIELD_MAP = [
]
# 单号不需要 "-" 的承运商(Evri 单号两段式带 "-",下游查询用无 "-" 形式)
_DASH_STRIP_COURIERS = {"EVRI"}
def _result_to_fields(r) -> dict[str, str | None]:
if not r.ok:
err = (r.error or "").lower()
if "reject" in err or "not a recognized layout" in err:
return {"ocrLayout": "UNSUPPORTED"}
return {"ocrLayout": "ERROR"}
return {col: (getattr(r, attr) or "") for attr, col in _OCR_FIELD_MAP}
fields = {col: (getattr(r, attr) or "") for attr, col in _OCR_FIELD_MAP}
if (r.courier or "").strip().upper() in _DASH_STRIP_COURIERS:
fields["ocrTracking"] = fields["ocrTracking"].replace("-", "")
return fields
def _has_running_peer() -> bool:
@@ -63,7 +73,7 @@ def _has_running_peer() -> bool:
def process_all(pending: list) -> dict[str, int]:
db = SyncDB()
ocr = OcrClient()
stats = {"success": 0, "error": 0, "unsupported": 0, "batches": 0}
stats = {"success": 0, "error": 0, "unsupported": 0, "batches": 0, "deferred": 0}
try:
# 1. 按国家分组;不支持的国家直接标 UNSUPPORTED
by_country: dict[str, list[tuple[str, object, str]]] = {}
@@ -89,12 +99,24 @@ def process_all(pending: list) -> dict[str, int]:
urls = [{"url": url, "filename": no} for no, _, url in batch]
batch_no = i // BATCH_SIZE + 1
total_batches = -(-total // BATCH_SIZE)
try:
results = ocr.recognize_batch(urls, country_code)
except Exception as err: # noqa: BLE001
print(f"[OCR] {country_code} batch {batch_no}/{total_batches} 请求失败: {str(err)[:100]}")
db.update_ocr_batch([(no, created_map[no], {"ocrLayout": "ERROR"}) for no in created_map])
stats["error"] += len(batch)
results = None
for attempt in range(BATCH_RETRIES):
try:
results = ocr.recognize_batch(urls, country_code)
break
except Exception as err: # noqa: BLE001
if attempt < BATCH_RETRIES - 1:
wait = BATCH_RETRY_BACKOFF[min(attempt, len(BATCH_RETRY_BACKOFF) - 1)]
print(f"[OCR] {country_code} batch {batch_no}/{total_batches} 请求失败"
f"({str(err)[:80]}),{wait}s 后重试({attempt + 2}/{BATCH_RETRIES})")
time.sleep(wait)
else:
# OCR 服务端短暂不可用(容器秒级重启类故障):订单保持未处理,
# 留给下个小时的 run 自动重试,不落 ERROR(那会是永久终态)。
print(f"[OCR] {country_code} batch {batch_no}/{total_batches} "
f"重试 {BATCH_RETRIES} 次仍失败,留待下次 run: {str(err)[:100]}")
if results is None:
stats["deferred"] += len(batch)
continue
updates = []
@@ -122,12 +144,12 @@ def label_ocr(
courier_source: str | None = None,
days: int = 7,
) -> dict:
"""面单 OCR enrichment。默认处理英国+美国,近 7 天下单的订单。
"""面单 OCR enrichment。默认处理全部已支持国家(见 api.ocr.models.SUPPORTED_COUNTRIES),近 7 天下单的订单。
手动跑其他国家: label_ocr(country_regions=['德国'])
手动跑指定国家: label_ocr(country_regions=['德国'])
"""
if country_regions is None:
country_regions = ['英国', '美国', '墨西哥', '德国', '沙特']
country_regions = ['英国', '美国', '墨西哥', '德国', '沙特', '加拿大', '西班牙', '意大利', '波兰']
if _has_running_peer():
print("已有 label-ocr 在运行,跳过本次")
return {"skipped": True}
@@ -152,11 +174,13 @@ def label_ocr(
after = db.count_ocr_status()
total_processed = stats["success"] + stats["error"] + stats["unsupported"]
print(f"完成{scope}: 处理 {total_processed} 单 / {stats['batches']} 批 | "
f"成功 {stats['success']} 失败 {stats['error']} 不支持 {stats['unsupported']}")
f"成功 {stats['success']} 失败 {stats['error']} 不支持 {stats['unsupported']} "
f"遗留待重试 {stats['deferred']}")
print(f"累计: {after}")
emit_sync_summary(f"面单 OCR{scope}", build_stats_rows("ALL", {}, [
("本批处理", total_processed), ("成功", stats["success"]),
("失败", stats["error"]), ("不支持", stats["unsupported"]),
("遗留待重试", stats["deferred"]),
("累计已完成", after.get("done", 0)), ("累计待处理", after.get("pending", 0)),
]))
return {"processed": total_processed, **stats, **after}
+1
View File
@@ -0,0 +1 @@
"""平台订单提取 flows:riin / rouhui / sdspod(写入 platform_order_data 表)。"""
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""平台订单 platform 字段大写同步:把 platform_order_data.platform 统一转为大写。
优化自 platform_order_upper_sync.py,对应 platform-order-upper-sync。
"""
import time
import psycopg2
from prefect import flow
# 数据库连接参数
conn_params = {
"host": "192.168.124.92",
"port": 5432,
"user": "postgres",
"password": "Inkreach123456.",
"database": "smt",
}
def log_msg(msg, level="info"):
ts = time.strftime('%H:%M:%S')
tag = {"success": "[OK]", "error": "[ERR]"}.get(level, "")
print(f"[{ts}] {tag} {msg}")
def update_platform_upper() -> int:
conn = None
cur = None
try:
conn = psycopg2.connect(**conn_params)
cur = conn.cursor()
# 仅更新需要变更的行,避免每次都全表写入
update_sql = """
UPDATE platform_order_data
SET platform = UPPER(platform)
WHERE platform IS NOT NULL
AND platform <> UPPER(platform);
"""
cur.execute(update_sql)
affected_rows = cur.rowcount
conn.commit()
log_msg(f"platform 大写转换完成,共 {affected_rows} 行受影响", "success")
return affected_rows
except Exception as e:
log_msg(f"platform 大写转换失败:{e}", "error")
if conn:
conn.rollback()
raise
finally:
if cur:
cur.close()
if conn:
conn.close()
@flow(name="platform-order-upper-sync", log_prints=True)
def platform_order_upper_sync() -> int:
return update_platform_upper()
if __name__ == "__main__":
platform_order_upper_sync.serve(name="platform-order-upper-sync", cron="*/2 * * * *")
+212
View File
@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
"""平台订单提取(RIIN/银河):写入 platform_order_data 表。
最小包装自 riin_extract_v2.py,对应 platform-order-riin。
"""
import time
import json
import hashlib
import asyncio
import aiohttp
import urllib3
from collections import defaultdict
from datetime import date, timedelta, datetime
from prefect import flow
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ===================== 银河(RIIN)接口配置 =====================
RIIN_LOGIN_URL = "http://tshirt.riin.com/auth/api/auth/login"
RIIN_ORDER_URL = "https://tshirt.riin.com/distribution/order/page"
USERNAME_RIIN = "预上网"
PASSWORD_RIIN = "Aa123456."
SUB_ID_LIST = ["10368", "10283", "16905", "11502", "13615", "11531","12279"]
MAX_RETRY = 3; REQUEST_TIMEOUT = 30; PAGE_SIZE_RIIN = 200
LOOKBACK_DAYS = 6 # 回查14天避免漏单
COUNTRY_MAP = {
"United States":"美国","America":"美国","USA":"美国","Mexico":"墨西哥","Canada":"加拿大",
"United Kingdom":"英国","UK":"英国","Germany":"德国","France":"法国","Italy":"意大利",
"Spain":"西班牙","Portugal":"葡萄牙","Netherlands":"荷兰","Belgium":"比利时","Poland":"波兰",
"Japan":"日本","South Korea":"韩国","Saudi Arabia":"沙特阿拉伯","Australia":"澳大利亚",
"Brazil":"巴西","Sweden":"瑞典","Denmark":"丹麦","Austria":"奥地利","Czech Republic":"捷克",
"Ireland":"爱尔兰","Switzerland":"瑞士","Norway":"挪威","Finland":"芬兰",
"Hungary":"匈牙利","Romania":"罗马尼亚","Greece":"希腊","Bulgaria":"保加利亚",
"Croatia":"克罗地亚","Slovakia":"斯洛伐克","Slovenia":"斯洛文尼亚",
"Lithuania":"立陶宛","Latvia":"拉脱维亚","Estonia":"爱沙尼亚",
"Luxembourg":"卢森堡","Malta":"马耳他","Cyprus":"塞浦路斯","Iceland":"冰岛",
"Liechtenstein":"列支敦士登","Monaco":"摩纳哥","Andorra":"安道尔",
"New Zealand":"新西兰","Argentina":"阿根廷","Chile":"智利","Colombia":"哥伦比亚",
"Peru":"秘鲁","India":"印度","Russia":"俄罗斯","Turkey":"土耳其","Israel":"以色列",
"UAE":"阿联酋","United Arab Emirates":"阿联酋","Singapore":"新加坡",
"Malaysia":"马来西亚","Indonesia":"印度尼西亚","Thailand":"泰国",
"Philippines":"菲律宾","Vietnam":"越南",
}
EUROPEAN_COUNTRIES = {
"德国","法国","意大利","西班牙","葡萄牙","荷兰","比利时","波兰","瑞典","丹麦","奥地利","捷克","爱尔兰",
"瑞士","挪威","芬兰","匈牙利","罗马尼亚","希腊","保加利亚","克罗地亚","斯洛伐克","斯洛文尼亚",
"立陶宛","拉脱维亚","爱沙尼亚","卢森堡","马耳他","塞浦路斯","冰岛","列支敦士登","摩纳哥","安道尔",
"Germany","France","Italy","Spain","Portugal","Netherlands","Belgium","Switzerland","Sweden",
"Norway","Denmark","Finland","Poland","Czech Republic","Austria","Hungary","Romania","Greece",
"Ireland","Bulgaria","Croatia","Slovakia","Slovenia","Lithuania","Latvia","Estonia",
"Luxembourg","Malta","Cyprus","Iceland","Liechtenstein","Monaco","Andorra"
}
def to_chinese_country(name):
if not name: return ""
return COUNTRY_MAP.get(name.strip(), name.strip())
def log_msg(msg, level="info"):
ts = time.strftime('%H:%M:%S')
tag = {"success":"[OK]","error":"[ERR]"}.get(level,"")
print(f"[{ts}] {tag} {msg}")
def _platform_name(record):
if record.get("temuHalf"): return "Temu"
if record.get("temuFull"): return "TEMU全托管"
if record.get("sheinOrder"): return "SHEIN"
if record.get("tiktokOrder"): return "TikTok"
if record.get("shopeeOrder"): return "Shopee"
code = record.get("platformShopCode","")
return code.split("_")[0] if code else ""
# ===================== 银河(RIIN)登录与拉取 =====================
async def riin_login(session, username, password):
payload = {"clientId":"WEB","username":username,"password":hashlib.md5(password.encode("utf-8")).hexdigest()}
headers = {"content-type":"application/json;charset=UTF-8"}
for retry in range(MAX_RETRY):
try:
async with session.post(RIIN_LOGIN_URL, json=payload, headers=headers, timeout=30) as resp:
data = await resp.json(content_type=None)
if not data.get("successful"):
log_msg(f"银河登录失败: {data.get('message')}", "error"); return ""
log_msg("银河登录成功", "success")
return data["data"]["token"]
except Exception as e:
log_msg(f"银河登录重试{retry+1}: {e}", "error"); await asyncio.sleep(1)
return ""
def _riin_headers(token):
return {"Accept":"application/json","Authorization":token,"Content-Type":"application/json",
"Cookie":f"authorized-token-TRADE={token}","Origin":"http://tshirt.riin.com",
"Referer":"http://tshirt.riin.com/","X-Time-Zone":"UTC+8"}
async def riin_fetch_page(session, token, payload):
for retry in range(MAX_RETRY):
try:
async with session.post(RIIN_ORDER_URL, json=payload, headers=_riin_headers(token), timeout=REQUEST_TIMEOUT) as resp:
result = await resp.json(content_type=None)
if not result.get("successful"):
log_msg(f"银河API错误: {result.get('message')}", "error"); await asyncio.sleep(1); continue
return result["data"]
except Exception as e:
log_msg(f"银河请求异常: {e}", "error"); await asyncio.sleep(1)
return {}
async def riin_fetch_all(token, start_date, end_date):
base = {"timeType":3,"startTime":f"{start_date} 00:00:00","endTime":f"{end_date} 23:59:59",
"dayRangeGroup":None,"hasRefund":False,"orderStatus":None,"shopCodeList":[],"subIdList":SUB_ID_LIST}
all_records = []; page = 1
async with aiohttp.ClientSession() as session:
while True:
payload = {**base, "pageIndex":page, "pageSize":PAGE_SIZE_RIIN}
data = await riin_fetch_page(session, token, payload)
records = data.get("records",[]) or []
all_records.extend(records)
total = data.get("total",0)
if len(records) < PAGE_SIZE_RIIN or len(all_records) >= total: break
page += 1
return all_records
def riin_format(raw_list):
rows = []
for r in raw_list:
if str(r.get("orderStatus","")) == "13": # 过滤status=13
continue
cn = to_chinese_country(r.get("receiverCountry",""))
rows.append([cn, cn, _platform_name(r), r.get("platformShopName",""),
r.get("platformOid",""), r.get("goodsTotalQty",0),
# str(r.get("orderTime",""))[:10], "RIIN",
str(r.get("orderTime","")), "RIIN",
r.get("orderStatus","")])
return rows
# ===================== 主流程 =====================
@flow(name="platform-order-riin", log_prints=True)
async def riin_platform_sync():
TODAY = date.today()
START_DATE = (TODAY - timedelta(days=LOOKBACK_DAYS)).isoformat()
END_DATE = TODAY.isoformat()
# END_DATE = (TODAY - timedelta(days=LOOKBACK_DAYS-60)).isoformat()
log_msg(f"===== 银河(RIIN) v2.0 =====")
log_msg(f"查询: {START_DATE} ~ {END_DATE} ({LOOKBACK_DAYS}天回查)")
merged_detail = []; riin_total_order = 0; riin_total_qty = 0
try:
async with aiohttp.ClientSession() as session:
riin_token = await riin_login(session, USERNAME_RIIN, PASSWORD_RIIN)
if not riin_token: return
riin_raw = await riin_fetch_all(riin_token, START_DATE, END_DATE)
merged_detail = riin_format(riin_raw)
riin_total_order = len(merged_detail)
riin_total_qty = sum(r[5] for r in merged_detail)
log_msg(f"银河API: {riin_total_order}单, {riin_total_qty}件", "success")
except Exception as e:
log_msg(f"银河异常: {e}", "error")
# 欧区
for row in merged_detail:
if row[1] in EUROPEAN_COUNTRIES: row[0]="欧区"; row[1]="欧区"
tiktok_rows = [r for r in merged_detail if "tiktok" in r[2].lower()]
non_tiktok_rows = [r for r in merged_detail if "tiktok" not in r[2].lower()]
# 统计
nt_plat_stat = defaultdict(lambda:[0,0]); nt_country_stat = defaultdict(lambda:[0,0])
for r in non_tiktok_rows:
nt_plat_stat[r[2]][0]+=1; nt_plat_stat[r[2]][1]+=r[5]
nt_country_stat[r[1]][0]+=1; nt_country_stat[r[1]][1]+=r[5]
tk_plat_stat = defaultdict(lambda:[0,0]); tk_country_stat = defaultdict(lambda:[0,0])
for r in tiktok_rows:
tk_plat_stat[r[2]][0]+=1; tk_plat_stat[r[2]][1]+=r[5]
tk_country_stat[r[1]][0]+=1; tk_country_stat[r[1]][1]+=r[5]
total_qty = riin_total_qty
log_msg(f"\n非Tiktok: {sum(v[1] for v in nt_plat_stat.values())}件")
for p in sorted(nt_plat_stat): log_msg(f" {p}: {nt_plat_stat[p][1]}件")
# 导入PostgreSQL (ON CONFLICT DO UPDATE)
try:
import psycopg2
conn=psycopg2.connect(host="192.168.124.92",port=5432,user="postgres",password="Inkreach123456.",database="smt")
cur=conn.cursor()
cur.execute("""CREATE TABLE IF NOT EXISTS platform_order_data(
id SERIAL PRIMARY KEY, site VARCHAR(50), country VARCHAR(50), platform VARCHAR(50),
store_name VARCHAR(200), order_no VARCHAR(100), quantity INTEGER, order_date DATE,
order_system VARCHAR(50), order_status VARCHAR(100), update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(order_no, order_system))""")
conn.commit()
now_str=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
rows=[(r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],now_str) for r in merged_detail]
sql="""INSERT INTO platform_order_data(site,country,platform,store_name,order_no,quantity,order_date,order_system,order_status,update_time)
VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT(order_no,order_system) DO UPDATE SET quantity=EXCLUDED.quantity, order_date=EXCLUDED.order_date, order_status=EXCLUDED.order_status, update_time=EXCLUDED.update_time"""
for i in range(0,len(rows),500): cur.executemany(sql,rows[i:i+500]); conn.commit()
cur.execute("SELECT COUNT(*) FROM platform_order_data"); cnt=cur.fetchone()[0]
cur.close(); conn.close()
log_msg(f"已同步到DB, 总{cnt}条","success")
except Exception as e:
log_msg(f"DB异常: {e}","error")
log_msg("===== 完成 =====")
if __name__ == "__main__":
# 直跑一次用于本地调试;勿改回 serve():同名 upsert 会覆盖 server 上挂 work pool 的正式 deployment
riin_platform_sync()
+277
View File
@@ -0,0 +1,277 @@
# -*- coding: utf-8 -*-
"""平台订单提取(柔汇/子元素):写入 platform_order_data 表。
最小包装自 rouhui_extract_v3.py,对应 platform-order-rouhui。
"""
import time
import json
import hmac
import hashlib
import random
import requests
import urllib3
from urllib.parse import urlparse, parse_qs, quote
from collections import defaultdict
from datetime import date, timedelta, datetime
from prefect import flow
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ===================== 柔汇openapi配置 =====================
host_rouhui = "https://openapi.rouhuipod.com"
app_id_rouhui = "smt01zi783662727"
app_key_rouhui = "fd1bec5b190e5c52b6a15d9fe1086fe9"
redirect_uri_rouhui = "https://demo-callback.virtual-api.com/oauth/callback"
login_url_rouhui = f"{host_rouhui}/openapi/v2/oauth2/login"
token_url_rouhui = f"{host_rouhui}/openapi/v2/oauth2/token"
order_list_url_rouhui = "/openapi/v2/order"
PLATFORM_MAP = {
2: "Temu-半托管", 3: "亚马逊", 4: "Tiktok-自运营",
9: "SHEIN-半托管", 10: "Temu-全托管", 12: "Temu-本土店", 13: "Shein-自运营"
}
PLATFORM_ID_LIST = [2, 3, 4, 9, 10, 12, 13]
SHORT_PLATFORM_MAP = {
"Temu-半托管": "Temu", "亚马逊": "亚马逊", "Tiktok-自运营": "tiktok",
"SHEIN-半托管": "shein", "Temu-全托管": "Temu", "Temu-本土店": "Temu", "Shein-自运营": "shein"
}
rouhui_login_form = {
"_token": "merchant", "merchant": "smt01",
"username": "smt01", "password": "Sanmu111.",
"app_id": app_id_rouhui, "redirect_uri": redirect_uri_rouhui
}
LOOKBACK_DAYS = 30 # 回查天数
HISTORY_COMPLEMENT_MODE = False # True=历史补数模式;False=日常增量模式
def log_msg(msg, level="info"):
ts = time.strftime('%H:%M:%S')
tag = {"success":"[OK]","error":"[ERR]"}.get(level,"")
print(f"[{ts}] {tag} {msg}")
def format_date_only(date_str):
if not date_str: return ""
return str(date_str).split(" ")[0]
# ===================== 柔汇签名与登录 =====================
def get_rand(): return str(random.randint(100000, 999999))
def get_ts(): return str(int(time.time()))
def calc_sign(params):
clean = {}
for k, v in params.items():
if v not in (None, ""): clean[k] = v
buf = []
for k in sorted(clean.keys()):
v = clean[k]
if isinstance(v, (dict, list)):
v = json.dumps(v, ensure_ascii=False, separators=(',', ':'))
buf.append(f"{k}={v}")
return hmac.new(app_key_rouhui.encode("utf-8"), "&".join(buf).encode("utf-8"), hashlib.sha256).hexdigest()
def rouhui_get_token():
"""登录获取access_token"""
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": f"{host_rouhui}/openapi/v2/oauth2?app_id={app_id_rouhui}&redirect_uri={quote(redirect_uri_rouhui)}",
"Origin": host_rouhui
}
try:
resp = requests.post(login_url_rouhui, data=rouhui_login_form, headers=headers, allow_redirects=False, verify=False, timeout=15)
redirect_url = resp.headers.get("Location")
if not redirect_url:
log_msg("柔汇登录无302跳转", "error"); return ""
code = parse_qs(urlparse(redirect_url).query)["code"][0]
log_msg("柔汇登录成功", "success")
except Exception as e:
log_msg(f"柔汇登录失败: {e}", "error"); return ""
params = {"code": code, "app_id": app_id_rouhui, "rand_str": get_rand(), "timestamp": get_ts()}
params["signature"] = calc_sign(params)
try:
resp = requests.post(token_url_rouhui, json=params, headers={"Content-Type": "application/json"}, timeout=10, verify=False)
data = resp.json()
if data.get("code") == 1:
log_msg("柔汇token获取成功", "success")
return data["data"]["access_token"]
log_msg(f"柔汇token失败: {data.get('message')}", "error")
except Exception as e:
log_msg(f"柔汇token网络异常: {e}", "error")
return ""
# ✅增加入参 begin_date,end_date,不再读取外部变量
def rouhui_fetch_platform_order(access_token, platform_id, begin_date, end_date):
"""拉取指定平台订单"""
bd = begin_date
ed = end_date
all_orders = []
page = 1
page_size = 200
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {access_token}"}
full_name = PLATFORM_MAP[platform_id]
while True:
query = {"page": page, "page_size": page_size, "platform_id": platform_id,
"status": 3, "create_time": [bd, ed]}
common = {"app_id": app_id_rouhui, "rand_str": get_rand(), "timestamp": get_ts()}
all_params = {**query, **common}
all_params["signature"] = calc_sign(all_params)
try:
resp = requests.post(f"{host_rouhui}{order_list_url_rouhui}", json=all_params, headers=headers, timeout=15, verify=False)
data = resp.json()
if data.get("code") != 1:
log_msg(f"{full_name} 查询失败: {data.get('message')}", "error")
break
lst = data["data"].get("list", [])
if not lst: break
all_orders.extend(lst)
if len(lst) < page_size: break
page += 1
time.sleep(0.2)
except Exception as e:
log_msg(f"{full_name} page{page}异常: {e}", "error")
time.sleep(0.5)
return all_orders
# ===================== 主流程 =====================
@flow(name="platform-order-rouhui", log_prints=True)
def rouhui_platform_sync():
TODAY = date.today()
if HISTORY_COMPLEMENT_MODE:
# 历史补数模式: [T‑2*N , T‑N]
start_dt = TODAY - timedelta(days=LOOKBACK_DAYS * 2)
end_dt = TODAY - timedelta(days=LOOKBACK_DAYS)
else:
# 日常增量模式: [T‑N , T]
start_dt = TODAY - timedelta(days=LOOKBACK_DAYS)
end_dt = TODAY
START_DATE = start_dt.isoformat()
END_DATE = end_dt.isoformat()
log_msg(f"===== 子元素(柔汇)订单提取 v2.0 =====")
log_msg(f"查询时间: {START_DATE} ~ {END_DATE} (回查{LOOKBACK_DAYS}天)")
merged_detail = []
rouhui_stat = {}
rouhui_total_order = 0
rouhui_total_qty = 0
try:
rh_token = rouhui_get_token()
if not rh_token:
log_msg("柔汇登录失败,终止", "error")
return
for pid in PLATFORM_ID_LIST:
full_name = PLATFORM_MAP[pid]
short_name = SHORT_PLATFORM_MAP[full_name]
log_msg(f"\n拉取柔汇【{full_name}】(pid={pid})")
# ✅调用时把main内部日期传进去
order_list = rouhui_fetch_platform_order(rh_token, pid, START_DATE, END_DATE)
p_order_cnt = len(order_list)
p_qty = 0
rouhui_stat[short_name] = [p_order_cnt, 0]
if not order_list:
log_msg(f" {full_name}: 无订单")
continue
for order in order_list:
order_no = order.get("order_no", "")
store = order.get("store_name", "未知")
create_t = order.get("create_time", "")
create_date_only = format_date_only(create_t)
items = order.get("order_items", [])
total_qty = sum(i.get("third_qty", 1) for i in items)
p_qty += total_qty
merged_detail.append(["墨西哥", "墨西哥", short_name, store, order_no, total_qty, create_date_only, "子元素", "已发货"])
rouhui_stat[short_name][1] = p_qty
rouhui_total_order += p_order_cnt
rouhui_total_qty += p_qty
log_msg(f" {full_name}: {p_order_cnt}单, {p_qty}件")
except Exception as e:
log_msg(f"柔汇API异常: {e}", "error")
# 统一平台名称
for row in merged_detail:
if row[2].upper() == "TEMU": row[2] = "Temu"
# 拆分Tiktok/非Tiktok
tiktok_rows = [row for row in merged_detail if "tiktok" in row[2].lower()]
non_tiktok_rows = [row for row in merged_detail if "tiktok" not in row[2].lower()]
# 统计
nt_plat_stat = defaultdict(lambda: [0, 0])
nt_country_stat = defaultdict(lambda: [0, 0])
for row in non_tiktok_rows:
nt_plat_stat[row[2]][0] += 1; nt_plat_stat[row[2]][1] += row[5]
nt_country_stat[row[1]][0] += 1; nt_country_stat[row[1]][1] += row[5]
tk_plat_stat = defaultdict(lambda: [0, 0])
tk_country_stat = defaultdict(lambda: [0, 0])
for row in tiktok_rows:
tk_plat_stat[row[2]][0] += 1; tk_plat_stat[row[2]][1] += row[5]
tk_country_stat[row[1]][0] += 1; tk_country_stat[row[1]][1] += row[5]
total_qty = rouhui_total_qty
log_msg(f"\n========== 统计汇总 ==========")
log_msg(f"非Tiktok总件数:{sum(v[1] for v in nt_plat_stat.values())} 件")
log_msg(f"Tiktok总件数:{sum(v[1] for v in tk_plat_stat.values())} 件")
log_msg(f"全部商品总件数:{total_qty} 件")
# ===== 导入PostgreSQL =====
try:
import psycopg2
conn = psycopg2.connect(host="192.168.124.92", port=5432, user="postgres", password="Inkreach123456.", database="smt")
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS platform_order_data (
id SERIAL PRIMARY KEY, site VARCHAR(50), country VARCHAR(50),
platform VARCHAR(50), store_name VARCHAR(200), order_no VARCHAR(100),
quantity INTEGER, order_date DATE, order_system VARCHAR(50),
order_status VARCHAR(100),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(order_no, order_system)
)
""")
conn.commit()
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db_rows = [(r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], now_str) for r in merged_detail]
sql = """
INSERT INTO platform_order_data (site, country, platform, store_name, order_no, quantity, order_date, order_system, order_status, update_time)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (order_no, order_system) DO UPDATE SET
quantity = EXCLUDED.quantity,
order_date = EXCLUDED.order_date,
order_status = EXCLUDED.order_status,
update_time = EXCLUDED.update_time
"""
batch_size = 500
for i in range(0, len(db_rows), batch_size):
cur.executemany(sql, db_rows[i:i + batch_size])
conn.commit()
cur.execute("SELECT COUNT(*) FROM platform_order_data")
cnt = cur.fetchone()[0]
cur.close(); conn.close()
log_msg(f"已同步到PostgreSQL, 总{cnt}条", "success")
except Exception as e:
log_msg(f"PostgreSQL异常: {e}", "error")
log_msg("===== 提取完成 =====")
if __name__ == "__main__":
# 直跑一次用于本地调试;勿改回 serve():同名 upsert 会覆盖 server 上挂 work pool 的正式 deployment
rouhui_platform_sync()
+483
View File
@@ -0,0 +1,483 @@
# -*- coding: utf-8 -*-
"""平台订单提取(SDSPOD/印美达):写入 platform_order_data 表。
最小包装自 sdspod_extract_v5.py,对应 platform-order-sdspod。
"""
import time
import json
import asyncio
import aiohttp
import math
import requests
import urllib3
from collections import defaultdict
from datetime import date, timedelta, datetime
from prefect import flow
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ===================== 共享配置 =====================
COMPANY_NO = "印美达"
EXTRA_INFO = "eyJpcCI6IjM4LjcxLjEyNy4xODkiLCJsb2NhdGlvbiI6eyJsYXQiOjM4Ljg4MzMsImxuZyI6LTc3fSwiYWRfaW5mbyI6eyJuYXRpb24iOiLnvo7lm70iLCJwcm92aW5jZSI6IiIsImNpdHkiOiIiLCJkaXN0cmljdCI6IiIsImFkY29kZSI6LTEsIm5hdGlvbl9jb2RlIjo4NDB9LCJmaW5nZXJwcmludElkIjoiN2M3YzllMDUxYWU2NDkzOTEzMGY5OWI0NjhiY2FhYTUiLCJ1c2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW4sgeDY0KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdl2tvKSBDaHJvbWUvMTM4LjAuMC4wIFNhZmFyaS81MzcuMzyiLCJocmVmIjoiaHR0cHM6Ly9tYW5hZ2Uuc2RzcG9kLmNvbS91c2VyL2xvZ2luIn0="
USERNAME_SD = "叶富华"
PASSWORD_SD = "Aa123456."
MAX_RETRY = 3
REQUEST_TIMEOUT = 30
# ===================== 国家映射(共用) =====================
COUNTRY_MAP = {
"United States": "美国", "America": "美国", "USA": "美国",
"Mexico": "墨西哥", "Canada": "加拿大",
"United Kingdom": "英国", "UK": "英国",
"Germany": "德国", "France": "法国", "Italy": "意大利",
"Spain": "西班牙", "Portugal": "葡萄牙",
"Netherlands": "荷兰", "Belgium": "比利时",
"Switzerland": "瑞士", "Sweden": "瑞典", "Norway": "挪威",
"Denmark": "丹麦", "Finland": "芬兰", "Poland": "波兰",
"Czech Republic": "捷克", "Czech": "捷克",
"Austria": "奥地利", "Hungary": "匈牙利",
"Romania": "罗马尼亚", "Greece": "希腊",
"Ireland": "爱尔兰", "Bulgaria": "保加利亚",
"Croatia": "克罗地亚", "Slovakia": "斯洛伐克",
"Slovenia": "斯洛文尼亚", "Lithuania": "立陶宛",
"Latvia": "拉脱维亚", "Estonia": "爱沙尼亚",
"Luxembourg": "卢森堡", "Malta": "马耳他",
"Cyprus": "塞浦路斯", "Iceland": "冰岛",
"Liechtenstein": "列支敦士登", "Monaco": "摩纳哥", "Andorra": "安道尔",
"Australia": "澳大利亚", "New Zealand": "新西兰",
"Japan": "日本", "South Korea": "韩国",
"Brazil": "巴西", "Argentina": "阿根廷",
"Chile": "智利", "Colombia": "哥伦比亚", "Peru": "秘鲁",
"India": "印度", "Russia": "俄罗斯", "Turkey": "土耳其",
"Israel": "以色列",
"UAE": "阿联酋", "United Arab Emirates": "阿联酋",
"Saudi Arabia": "沙特阿拉伯",
"Singapore": "新加坡", "Malaysia": "马来西亚",
"Indonesia": "印度尼西亚", "Thailand": "泰国",
"Philippines": "菲律宾", "Vietnam": "越南",
}
EUROPEAN_COUNTRIES = {
"德国", "法国", "意大利", "西班牙", "葡萄牙", "荷兰", "比利时",
"瑞士", "瑞典", "挪威", "丹麦", "芬兰", "波兰", "捷克", "奥地利",
"匈牙利", "罗马尼亚", "希腊", "爱尔兰", "保加利亚", "克罗地亚", "斯洛伐克",
"斯洛文尼亚", "立陶宛", "拉脱维亚", "爱沙尼亚", "卢森堡", "马耳他", "塞浦路斯",
"冰岛", "列支敦士登", "摩纳哥", "安道尔",
"Germany", "France", "Italy", "Spain", "Portugal",
"Netherlands", "Belgium", "Switzerland", "Sweden", "Norway", "Denmark",
"Finland", "Poland", "Czech Republic", "Austria", "Hungary", "Romania",
"Greece", "Ireland", "Bulgaria", "Croatia", "Slovakia", "Slovenia",
"Lithuania", "Latvia", "Estonia", "Luxembourg", "Malta", "Cyprus",
"Iceland", "Liechtenstein", "Monaco", "Andorra"
}
def to_chinese_country(name):
if not name: return ""
return COUNTRY_MAP.get(name.strip(), name.strip())
def log_msg(msg, level="info"):
ts = time.strftime('%H:%M:%S')
tag = {"success":"[OK]","error":"[ERR]"}.get(level,"")
print(f"[{ts}] {tag} {msg}")
# ===================== 共享登录 =====================
def sd_login_sync():
ts = int(time.time() * 1000)
url = f"https://pod-api.sdspod.com/pod/auth/login?t={ts}"
headers = {"content-type": "application/json;charset=UTF-8"}
payload = {"no": COMPANY_NO, "password": PASSWORD_SD, "username": USERNAME_SD, "extraInfo": EXTRA_INFO}
for i in range(MAX_RETRY):
try:
resp = requests.post(url, headers=headers, json=payload, timeout=REQUEST_TIMEOUT)
data = resp.json()
if data.get("ret") == 0:
token = data.get("data", {}).get("accessToken")
if token:
log_msg("SDSPOD登录成功", "success")
return token
log_msg(f"SD登录失败: {data.get('msg')}", "error")
return ""
except Exception as e:
if i < MAX_RETRY - 1:
time.sleep(1)
return ""
# ===================== 数据库通用插入函数 =====================
def insert_orders_to_db(order_rows, order_system):
"""将统一格式的订单列表插入数据库,order_system 用于区分来源"""
try:
import psycopg2
conn = psycopg2.connect(
host="192.168.124.92", port=5432,
user="postgres", password="Inkreach123456.",
database="smt"
)
cur = conn.cursor()
# 确保表存在
cur.execute("""
CREATE TABLE IF NOT EXISTS platform_order_data (
id SERIAL PRIMARY KEY,
site VARCHAR(50),
country VARCHAR(50),
platform VARCHAR(50),
store_name VARCHAR(200),
order_no VARCHAR(100),
quantity INTEGER,
order_date DATE,
order_system VARCHAR(50),
order_status VARCHAR(100),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(order_no, order_system)
)
""")
conn.commit()
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db_rows = []
for r in order_rows:
# r = [站点, 国家, 平台, 店铺, 订单号, 数量, 日期, 订单系统, 状态]
order_date = r[6] if r[6] else None
db_rows.append((r[0], r[1], r[2], r[3], r[4], r[5], order_date, order_system, r[8], now_str))
sql = """
INSERT INTO platform_order_data (site, country, platform, store_name, order_no, quantity, order_date, order_system, order_status, update_time)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (order_no, order_system) DO UPDATE SET
quantity = EXCLUDED.quantity,
order_date = EXCLUDED.order_date,
order_status = EXCLUDED.order_status,
update_time = EXCLUDED.update_time
"""
batch_size = 500
for i in range(0, len(db_rows), batch_size):
batch = db_rows[i:i + batch_size]
cur.executemany(sql, batch)
conn.commit()
cur.execute("SELECT COUNT(*) FROM platform_order_data")
cnt = cur.fetchone()[0]
cur.close()
conn.close()
log_msg(f"已同步到PostgreSQL, 总{cnt}条 (来源: {order_system})", "success")
return True
except Exception as e:
log_msg(f"PostgreSQL导入异常 ({order_system}): {str(e)}", "error")
return False
# ======================================================================
# 脚本1:按支付时间拉取最近30天订单(同步,productionType=1,仅一个商户)
# ======================================================================
def fetch_one_page_pay(token, page, pay_begin, pay_end):
url = "https://pod-api.sdspod.com/pod/orders/page"
headers = {"access-token": token}
params = {
"merchantIds": "35876",
"productionType": 1,
"page": page,
"size": 100,
"originType": "normal",
"payDateBegin": pay_begin,
"payDateEnd": pay_end,
"t": int(time.time() * 1000)
}
for attempt in range(MAX_RETRY):
try:
resp = requests.get(url, headers=headers, params=params, timeout=REQUEST_TIMEOUT)
return resp.json()
except Exception as e:
log_msg(f"支付单第{page}页请求异常:{str(e)},重试 {attempt+1}/{MAX_RETRY}")
time.sleep(0.8)
return None
def fetch_all_orders_by_pay(token):
now = datetime.now()
pay_end = now.strftime("%Y-%m-%d 23:59:59")
pay_begin = (now - timedelta(days=30)).strftime("%Y-%m-%d 00:00:00")
# now = datetime.now()
# pay_end = (now - timedelta(days=120)).strftime("%Y-%m-%d 23:59:59")
# pay_begin = (now - timedelta(days=280)).strftime("%Y-%m-%d 00:00:00")
log_msg(f"[脚本1] 筛选支付时间:{pay_begin} ~ {pay_end}")
all_list = []
first_resp = fetch_one_page_pay(token, page=1, pay_begin=pay_begin, pay_end=pay_end)
if not first_resp:
log_msg("[脚本1] 第一页获取失败,终止拉取", "error")
return all_list
total_count = first_resp.get("totalCount", 0)
if total_count <= 0:
log_msg("[脚本1] 该时间段没有订单数据")
return all_list
total_pages = math.ceil(total_count / 100)
log_msg(f"[脚本1] 总订单:{total_count} 条,共{total_pages}页")
all_list.extend(first_resp.get("list", []))
for pg in range(2, total_pages + 1):
page_resp = fetch_one_page_pay(token, pg, pay_begin=pay_begin, pay_end=pay_end)
if page_resp and "list" in page_resp:
page_data = page_resp.get("list", [])
all_list.extend(page_data)
time.sleep(0.3)
log_msg(f"[脚本1] 拉取完成,合计 {len(all_list)} 条订单")
return all_list
def parse_order_item_pay(item):
"""
解析为统一格式:
[站点, 国家, 平台, 店铺名称, 订单号, 商品数量, 下单日期, 订单系统, 订单状态]
"""
order_no = item.get("no", "")
country_raw = item.get("address", {}).get("countryName", "")
country = to_chinese_country(country_raw)
store_name = item.get("merchantStore", {}).get("name", "")
platform = item.get("merchantStore", {}).get("platformName", "") # 可能不存在
gmt_created = item.get("gmtCreated", "")
order_date = gmt_created.split(" ")[0] if gmt_created else ""
total_qty = 0
for parcel in item.get("parcelList", []):
total_qty += parcel.get("totalQty", 0)
if country_raw in EUROPEAN_COUNTRIES or country in EUROPEAN_COUNTRIES:
country = "欧区"
site = country # 站点暂用国家
else:
site = country
status = str(item.get("status", ""))
return [site, country, platform, store_name, order_no, total_qty, order_date, "SDSPOD", status]
def main_script1():
log_msg("===== 脚本1:按支付时间拉取最近30天订单 =====")
token = sd_login_sync()
if not token:
log_msg("[脚本1] 登录失败,终止", "error")
return
orders = fetch_all_orders_by_pay(token)
if not orders:
log_msg("[脚本1] 没有订单数据")
return
parsed_rows = []
for item in orders:
if item.get("status") == 98:
continue
row = parse_order_item_pay(item)
parsed_rows.append(row)
log_msg(f"[脚本1] 有效订单: {len(parsed_rows)} 单")
if parsed_rows:
insert_orders_to_db(parsed_rows, "SDSPOD")
log_msg("===== 脚本1 完成 =====")
# ======================================================================
# 脚本2:按创建时间回查19天订单(异步,productionType=2,多商户)
# ======================================================================
MERCHANT_IDS = "35876,45637,52649,52699,35956,60756"
PRODUCTION_TYPE = 2
PAGE_SIZE_SD = 100
MAX_CONCURRENT = 5
LOOKBACK_DAYS = 7
def format_date_only(date_str):
if not date_str: return ""
return str(date_str).split(" ")[0]
async def sd_fetch_page(session, sem, params, pg, total):
async with sem:
for i in range(MAX_RETRY):
try:
timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
async with session.get("https://pod-api.sdspod.com/pod/orders/page", params=params, timeout=timeout) as resp:
data = await resp.json()
return data.get("list", [])
except:
if i < MAX_RETRY - 1:
await asyncio.sleep(0.5)
log_msg(f"SD第{pg}/{total}页拉取失败", "error")
return []
async def sd_fetch_batch(token, begin_str, end_str):
headers = {"access-token": token}
begin = f"{begin_str} 00:00:00"
end = f"{end_str} 23:59:59"
first_params = {
"page": 1, "size": PAGE_SIZE_SD, "orderTimeRangeType": "",
"createdDateBegin": begin, "createdDateEnd": end,
"merchantIds": MERCHANT_IDS, "productionType": PRODUCTION_TYPE,
"t": int(time.time() * 1000)
}
connector = aiohttp.TCPConnector(limit=MAX_CONCURRENT * 2, ttl_dns_cache=300)
async with aiohttp.ClientSession(headers=headers, connector=connector) as session:
timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
async with session.get("https://pod-api.sdspod.com/pod/orders/page", params=first_params, timeout=timeout) as resp:
first_data = await resp.json()
if first_data.get("ret") == 20001:
log_msg("Token过期", "error")
return []
total = first_data.get("totalCount", 0)
if total == 0:
return []
total_pages = math.ceil(total / PAGE_SIZE_SD)
log_msg(f" SD {begin_str}~{end_str}: {total}单/{total_pages}页")
first_list = first_data.get("list", [])
if total_pages == 1:
return first_list
sem = asyncio.Semaphore(MAX_CONCURRENT)
tasks = []
for pg in range(2, total_pages + 1):
pg_params = {
"page": pg, "size": PAGE_SIZE_SD, "orderTimeRangeType": "",
"createdDateBegin": begin, "createdDateEnd": end,
"merchantIds": MERCHANT_IDS, "productionType": PRODUCTION_TYPE,
"t": int(time.time() * 1000)
}
tasks.append(sd_fetch_page(session, sem, pg_params, pg, total_pages))
results = await asyncio.gather(*tasks)
all_data = first_list
for lst in results:
all_data.extend(lst)
return all_data
def sd_format_item(item):
address = item.get("address", {})
country = address.get("countryName", "")
store = item.get("merchantStore", {})
platform = store.get("platformName", "")
create_time = item.get("gmtCreated", "")
parcel_list = item.get("parcelList", [])
if parcel_list and isinstance(parcel_list, list):
def safe_int(val):
try:
return int(val)
except (ValueError, TypeError):
return 0
qty = sum(safe_int(p.get("totalQty", 0)) for p in parcel_list)
else:
qty = 0
return [
to_chinese_country(country),
to_chinese_country(country),
platform,
store.get("name", "未知"),
item.get("no", ""),
qty,
format_date_only(create_time),
"SDSPOD",
str(item.get("status", ""))
]
async def main_script2():
log_msg("===== 脚本2:SDSPOD订单提取工具 v2.0 =====")
TODAY = date.today()
START_DATE = (TODAY - timedelta(days=LOOKBACK_DAYS)).isoformat()
END_DATE = TODAY.isoformat()
# TODAY = date.today()
# START_DATE = (TODAY - timedelta(days=180)).isoformat()
# END_DATE = (TODAY - timedelta(days=60)).isoformat()
log_msg(f"查询时间: {START_DATE} ~ {END_DATE} (回查{LOOKBACK_DAYS}天)")
sd_raw = []
start_d = datetime.strptime(START_DATE, "%Y-%m-%d").date()
end_d = datetime.strptime(END_DATE, "%Y-%m-%d").date()
d = start_d
batches = []
while d <= end_d:
batch_end = min(d + timedelta(days=2), end_d)
batches.append((d.isoformat(), batch_end.isoformat()))
d = batch_end + timedelta(days=1)
log_msg(f"共{len(batches)}批次拉取")
sd_token = sd_login_sync()
if not sd_token:
log_msg("登录失败,终止", "error")
return
for bi, (b_start, b_end) in enumerate(batches):
raw = await sd_fetch_batch(sd_token, b_start, b_end)
sd_raw.extend(raw)
if bi > 0 and bi % 5 == 0:
sd_token = sd_login_sync()
if not sd_token:
break
log_msg(f"API拉取总数: {len(sd_raw)} 条", "success")
sd_detail = []
for item in sd_raw:
if item.get("status") == 98:
continue
row = sd_format_item(item)
cn = row[1]
if cn in EUROPEAN_COUNTRIES:
row[0] = "欧区"
row[1] = "欧区"
sd_detail.append(row)
sd_total_order = len(sd_detail)
sd_total_qty = sum(r[5] for r in sd_detail)
log_msg(f"有效订单: {sd_total_order}单, {sd_total_qty}件", "success")
tiktok_rows = [r for r in sd_detail if "tiktok" in r[2].lower()]
non_tiktok_rows = [r for r in sd_detail if "tiktok" not in r[2].lower()]
nt_plat_stat = defaultdict(lambda: [0, 0])
nt_country_stat = defaultdict(lambda: [0, 0])
for row in non_tiktok_rows:
nt_plat_stat[row[2]][0] += 1
nt_plat_stat[row[2]][1] += row[5]
nt_country_stat[row[1]][0] += 1
nt_country_stat[row[1]][1] += row[5]
tk_plat_stat = defaultdict(lambda: [0, 0])
tk_country_stat = defaultdict(lambda: [0, 0])
for row in tiktok_rows:
tk_plat_stat[row[2]][0] += 1
tk_plat_stat[row[2]][1] += row[5]
tk_country_stat[row[1]][0] += 1
tk_country_stat[row[1]][1] += row[5]
total_qty = sd_total_qty
log_msg(f"\n========== 统计汇总 ==========")
log_msg(f"非Tiktok总件数:{sum(v[1] for v in nt_plat_stat.values())} 件")
log_msg(f"Tiktok总件数:{sum(v[1] for v in tk_plat_stat.values())} 件")
log_msg(f"全部商品总件数:{total_qty} 件")
if sd_detail:
insert_orders_to_db(sd_detail, "SDSPOD")
log_msg("===== 脚本2 完成 =====")
# ======================================================================
# 主入口:先执行脚本1(支付订单),再执行脚本2(创建订单)
# ======================================================================
@flow(name="platform-order-sdspod", log_prints=True)
async def sdspod_platform_sync():
await asyncio.to_thread(main_script1)
await main_script2()
if __name__ == "__main__":
# 直跑一次用于本地调试;勿改回 serve():同名 upsert 会覆盖 server 上挂 work pool 的正式 deployment
sdspod_platform_sync()
+144
View File
@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
"""shop_dim 同步 flow:teable(源) -> 本地 smt 库(目标)。
最小包装自 sync_shop_dim.py,对应 shop-dim-sync,每 5 分钟调度一次。
源: PostgreSQL @ table.inkreach.cc:10432 db=teable schema=bsehWCxpH6HoHUdkjUd
物理表 tblUF7OrBxeRKUyWBc2 (teable 里名为 "shop_dim" 的表)
目标: PostgreSQL @ 192.168.124.92:5432 db=smt public.shop_dim
本地直接跑: python flows/platform_order/sync_shop_dim.py
"""
import time
from typing import Any
import psycopg2
from prefect import flow
SRC = dict(host="table.inkreach.cc", port=10432, dbname="teable",
user="read_only_role_bsehWCxpH6HoHUdkjUd",
password="ygBuh8Au5J-PjuNQASos0", connect_timeout=25)
SRC_TABLE = '"bsehWCxpH6HoHUdkjUd"."tblUF7OrBxeRKUyWBc2"'
DST = dict(host="192.168.124.92", port=5432, dbname="smt",
user="postgres", password="Inkreach123456.", connect_timeout=15)
COL_MAP = [
("Guo_Jia", "country"),
("Dian_Pu_Ming_Cheng", "shop_name"),
("DBDian_Pu_Ming", "db_store_name"),
("Ping_Tai", "platform"),
("Lei_Xing", "shop_type"),
("Deng_Ji", "tier"),
("Fu_Ze_Ren", "owner"),
("Pin_Lei", "category"),
("Xia_Dian_Shi_Jian", "opened_at"),
("Mu_Biao_Jian_Shu", "sep_target_qty"),
]
def log_msg(msg):
print(msg)
def connect(cfg, tries=5):
last = None
for i in range(tries):
try:
return psycopg2.connect(**cfg)
except Exception as e:
last = e
log_msg(f" 连接重试 {i+1}/{tries}: {e}")
time.sleep(3)
raise ConnectionError(f"连接失败: {last}")
def norm(v, col):
if v is None:
return None
if col == "opened_at":
return v.date() if hasattr(v, "date") else v
if col == "sep_target_qty":
try:
return int(round(float(v)))
except Exception:
return None
s = str(v).strip()
return s if s != "" else None
@flow(name="shop-dim-sync", log_prints=True)
def shop_dim_sync(dry_run: bool = False, prune: bool = True) -> dict[str, Any]:
log_msg(f"[1/4] 读取源表 {SRC_TABLE} ...")
sc = connect(SRC)
scur = sc.cursor()
sel = ", ".join('"%s"' % c for c, _ in COL_MAP)
scur.execute("SELECT %s FROM %s" % (sel, SRC_TABLE))
raw = scur.fetchall()
scur.close(); sc.close()
src_rows = [
dict(zip([d for _, d in COL_MAP], [norm(v, c) for v, (_, c) in zip(row, COL_MAP)]))
for row in raw
]
log_msg(f" 源记录: {len(src_rows)} 行")
log_msg("[2/4] 连接目标 smt.shop_dim ...")
dc = connect(DST)
dcur = dc.cursor()
dcur.execute("SELECT country, db_store_name FROM shop_dim WHERE db_store_name IS NOT NULL")
existing = {(r[0], r[1]) for r in dcur.fetchall()}
log_msg(f" 目标现有: {len(existing)} 个 (country, db_store_name)")
src_keys = {(r["country"], r["db_store_name"]) for r in src_rows}
to_update = src_keys & existing
to_insert = src_keys - existing
to_delete = existing - src_keys
log_msg(f"[3/4] 计划: 更新 {len(to_update)} | 新增 {len(to_insert)} | 待删 {len(to_delete)}")
if dry_run:
log_msg("[dry-run] 不执行写入。")
if to_delete:
log_msg(" 待删示例: " + str(list(to_delete)[:10]))
dcur.close(); dc.close()
return {"sourceRows": len(src_rows), "dryRun": True}
cols = [d for _, d in COL_MAP]
upd = 0
ins = 0
for r in src_rows:
vals = [r[c] for c in cols]
if (r["country"], r["db_store_name"]) in existing:
set_clause = ", ".join(f"{c}=%s" for c in cols)
dcur.execute(
f"UPDATE shop_dim SET {set_clause} WHERE country=%s AND db_store_name=%s",
vals + [r["country"], r["db_store_name"]])
upd += dcur.rowcount
else:
ph = ", ".join(["%s"] * len(cols))
dcur.execute(f"INSERT INTO shop_dim ({', '.join(cols)}) VALUES ({ph})", vals)
ins += dcur.rowcount
dele = 0
if prune and to_delete:
for country, dbn in to_delete:
dcur.execute("DELETE FROM shop_dim WHERE country=%s AND db_store_name=%s", (country, dbn))
dele += dcur.rowcount
dc.commit()
log_msg(f"[4/4] 完成: 更新 {upd} | 新增 {ins} | 删除 {dele}")
dcur.execute("SELECT COUNT(*) FROM shop_dim WHERE db_store_name IS NOT NULL")
total = dcur.fetchone()[0]
log_msg(f" 目标现有总数: {total}")
dcur.close(); dc.close()
return {
"sourceRows": len(src_rows),
"updated": upd,
"inserted": ins,
"deleted": dele,
"total": total,
}
if __name__ == "__main__":
shop_dim_sync.serve(name="shop-dim-sync", cron="*/5 * * * *")
+3 -3
View File
@@ -1,10 +1,10 @@
"""RIIN 同步公共管道:映射订单 → 并发拉商品 → upsert 到 PG。"""
"""RIIN 同步公共管道:映射订单 → 批量拉商品 → upsert 到 PG。"""
from __future__ import annotations
from typing import Any
from flows.shared import fetch_goods_concurrent
from flows.shared import fetch_goods_for_orders
from flows.shared.db import SyncDB
from flows.shared.mappers import map_riin_goods, map_riin_order
from api import RiinDisClient
@@ -31,7 +31,7 @@ def sync_riin_records(raw_orders: list[Any], client: RiinDisClient, db: SyncDB)
goods_inputs: list[Any] = []
if order_meta:
goods_map = fetch_goods_concurrent(client, list(order_meta.keys()))
goods_map = fetch_goods_for_orders(client, list(order_meta.keys()))
for oid, (order_no, created_at) in order_meta.items():
items = goods_map.get(oid, [])
if not items:
+241
View File
@@ -0,0 +1,241 @@
"""SDS 国内工厂运单自动生成:遍历「运单待生成」订单 → 自动下极兔 → 面单回传 SDS。
筛选参数: carriageNoStatus=generate(运单待生成) + factoryIds=1161(印美达国内工厂)
+ orderStatusCode=2(订单状态)。
链路: SDS 订单 → 变体克重(children 接口,客户端 24h 缓存)
→ 快递100 bOrder(极兔,returnType=20+tempid 出面单,回调走 frp 公网地址)
→ 回调服务 /list 轮询取面单短链(status=200)
→ PNG 转 PDF → SDS /files/pdf/logisticsWaybill 上传 → PUT 挂包裹(orderNo=新运单号)。
幂等与安全:
- 已有运单号的包裹跳过(10 分钟轮询天然去重);
- 面单等待超时只记录待人工跟进,不自动取消(快递员已接单时取消会扣其绩效);
- max_orders 限制单轮下单量(首轮积压保护);
- 改动必须先在测试环境/本地模拟验证,再真实下单(运营约定);
- ⚠️ 真实下单 flow:prefect.yaml 中本 deployment 的 schedule 默认 active:false,
上线前人工核对后改 true 并 `uv run prefect deploy --all` 才会开始调度。
对应 sds-waybill-autogen。
"""
from __future__ import annotations
import io
import json
import time
from typing import Any
import httpx
from PIL import Image
from prefect import flow
from prefect.blocks.system import Secret
from api.kuaidi100 import Kuaidi100Client
from api.sds.models import OrderPageRequest
from flows.shared import create_sds_client
# 寄件人固定为广州发货仓联系人
SEND_MAN_NAME = "张文风"
SEND_MAN_MOBILE = "18320690872"
SEND_MAN_ADDR = "广东省广州市番禺区南村镇新基村新盈科技园3栋2楼"
CALLBACK_BASE = "http://8.138.172.239:18090/kuaidi100" # 回调服务(frp 公网): /order 收推送, /list 查历史
CALLBACK_SALT = "inkreach-test" # 须与回调服务 kuaidi100-callback.service 的 --salt 一致
LABEL_POLL_INTERVAL_SECONDS = 5
CARGO_DEFAULT = "日用品"
def _load_secret(name: str) -> dict[str, str]:
value = Secret.load(name).get()
return json.loads(value) if isinstance(value, str) else value
def _join_addr(province: str | None, city: str | None, detail: str | None) -> str:
detail = detail or ""
prefix = ""
for part in (province or "", city or ""):
if part and part not in detail and part not in prefix:
prefix += part
return prefix + detail
def _parcel_info(order: Any) -> tuple[Any, str]:
"""取首个包裹 (parcel_id, 当前运单号,可能为空)。多包裹订单只处理包裹1并提示。"""
parcels = order.parcel_list or []
if len(parcels) > 1:
print(f"[autogen] 订单 {order.no} 有 {len(parcels)} 个包裹,仅处理包裹1")
if not parcels:
return None, ""
parcel = parcels[0] or {}
carriage = (parcel.get("logistics") or {}).get("carriage") or {}
return parcel.get("id"), str(carriage.get("no") or "")
def _wait_label_url(order_id: Any, callback_base: str, timeout_seconds: int) -> str | None:
"""轮询回调服务 /list,取该订单 status=200 回调里的面单短链。"""
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
try:
items = httpx.get(f"{callback_base}/list", params={"n": 50}, timeout=10).json().get("items") or []
for item in items:
param = item.get("param")
data = (param.get("data") or {}) if isinstance(param, dict) else {}
if str(data.get("orderId")) == str(order_id) and data.get("status") == 200 and data.get("label"):
return str(data["label"])
except Exception as err: # noqa: BLE001
print(f"[autogen] 回调服务查询失败(继续等待): {err}")
time.sleep(LABEL_POLL_INTERVAL_SECONDS)
return None
def _label_to_pdf(label_url: str) -> bytes:
resp = httpx.get(label_url, timeout=60, follow_redirects=True)
resp.raise_for_status()
img = Image.open(io.BytesIO(resp.content)).convert("RGB")
buf = io.BytesIO()
img.save(buf, "PDF", resolution=150.0)
return buf.getvalue()
def _process_one(
sds: Any,
kd: Kuaidi100Client,
order: Any,
*,
courier: str,
tempid: str | None,
callback_base: str,
label_wait_seconds: int,
) -> dict[str, Any]:
"""单订单全链路:下单 → 等面单 → 回传。失败尽力取消快递单,返回结果记录。"""
order_no = order.no or order.id
record: dict[str, Any] = {"orderNo": order_no, "result": "fail"}
parcel_id, current_no = _parcel_info(order)
if not parcel_id:
record["error"] = "订单无包裹"
return record
if current_no:
record.update(result="skip", waybill=current_no, reason="已有运单号")
return record
addr = order.address
if not addr or not addr.consignee or not addr.mobile_phone:
record["error"] = "订单缺收件人信息"
return record
grams, missed = sds.get_order_weight_grams(order)
weight = f"{grams / 1000:.2f}" if grams else "1"
if missed:
print(f"[autogen] 订单 {order_no} 克重未完全匹配({missed}),申报重量 {weight}kg")
data = kd.border(
kuaidicom=courier,
rec_man_name=addr.consignee,
rec_man_mobile=addr.mobile_phone,
rec_man_print_addr=_join_addr(addr.province, addr.city, addr.detail),
send_man_name=SEND_MAN_NAME,
send_man_mobile=SEND_MAN_MOBILE,
send_man_print_addr=SEND_MAN_ADDR,
callback_url=f"{callback_base}/order",
print_callback_url=f"{callback_base}/order",
cargo=(order.items[0].product_name if order.items else None) or CARGO_DEFAULT,
weight=weight,
remark=f"SDS-{order_no} autogen",
salt=CALLBACK_SALT,
return_type="20",
tempid=tempid,
extra={"op": 0, "resultv2": "0"},
)
waybill = str(data.get("kuaidinum") or "")
record.update(kuaidiOrderId=data.get("orderId"), waybill=waybill)
print(f"[autogen] 订单 {order_no} 下单成功 {waybill},等待面单回调…")
label_url = _wait_label_url(data.get("orderId"), callback_base, label_wait_seconds)
if not label_url:
# 不自动取消:快递员可能已接单,取消会扣其绩效;记录待人工跟进(后台补打面单或人工取消)。
record["error"] = f"面单回调等待超时({label_wait_seconds}s),需人工跟进"
record["manualFollowUp"] = True
print(f"[autogen] 订单 {order_no} 面单超时,保留快递单 {waybill} 待人工处理(不自动取消)")
return record
pdf_bytes = _label_to_pdf(label_url)
upload = sds.upload_waybill_pdf(f"{order_no}.pdf", pdf_bytes)
sds.attach_parcel_label(order.id, parcel_id, waybill, upload["url"])
record.update(result="ok", labelPdf=upload["url"], labelImage=label_url, weightKg=weight)
print(f"[autogen] 订单 {order_no} 完成: {waybill} → {upload['url']}")
return record
@flow(name="sds-waybill-autogen", log_prints=True)
def waybill_autogen(
enabled: bool = True,
courier: str = "jtexpress",
factory_ids: list[int] | None = None,
order_status_code: str = "2",
max_orders: int = 20,
label_wait_seconds: int = 120,
kuaidi100_block: str = "kuaidi100-prod",
callback_base: str = CALLBACK_BASE,
) -> dict[str, Any]:
"""运单待生成订单 → 极兔下单 + 面单回传。prefect.yaml 默认 schedule active:false。"""
factory_ids = factory_ids or [1161]
if not enabled:
print("[autogen] enabled=false,本轮跳过(不查询不下单)")
return {"enabled": False, "processed": 0}
cred = _load_secret(kuaidi100_block)
if not cred.get("tempid"):
raise ValueError(f"Secret Block {kuaidi100_block} 缺 tempid(面单模板编码),无法出面单")
sds = create_sds_client()
kd = Kuaidi100Client(cred["key"], cred["secret"], customer=cred.get("customer"))
try:
resp = sds.get_orders(
OrderPageRequest(
factory_ids=factory_ids,
carriage_no_status="generate",
order_status_code=order_status_code,
size=min(max_orders, 200),
page=1,
)
)
print(
f"[autogen] 待处理订单 {resp.total_count} 条"
f"(本轮上限 {max_orders},快递={courier},orderStatusCode={order_status_code})"
)
records = []
for order in resp.records[:max_orders]:
try:
records.append(
_process_one(
sds, kd, order,
courier=courier, tempid=cred["tempid"],
callback_base=callback_base, label_wait_seconds=label_wait_seconds,
)
)
except Exception as err: # noqa: BLE001
print(f"[autogen] 订单 {order.no} 处理异常: {err}")
records.append({"orderNo": order.no, "result": "fail", "error": str(err)[:300]})
finally:
kd.close()
sds.close()
summary = {
"enabled": True,
"pendingTotal": resp.total_count,
"processed": len(records),
"ok": sum(1 for r in records if r["result"] == "ok"),
"skip": sum(1 for r in records if r["result"] == "skip"),
"fail": sum(1 for r in records if r["result"] == "fail"),
"records": records,
}
print(
f"[autogen] 本轮完成: 成功 {summary['ok']} / 跳过 {summary['skip']} / 失败 {summary['fail']}"
f"(待生成共 {resp.total_count})"
)
return summary
if __name__ == "__main__":
# 本地直接运行默认不下单;要真实执行请带 enabled=True 显式运行或部署后由调度触发。
waybill_autogen(enabled=False)
+2 -2
View File
@@ -5,7 +5,7 @@ from flows.shared.artifacts import build_stats_rows, emit_sync_summary, emit_ver
from flows.shared.clients import (
create_riin_client,
create_sds_client,
fetch_goods_concurrent,
fetch_goods_for_orders,
fetch_riin_all_pages,
fetch_sds_all_pages,
floor_to_hour,
@@ -23,7 +23,7 @@ __all__ = [
"clients", "db", "mappers", "SyncDB",
"OrderUpsertInput", "GoodsUpsertInput",
"create_riin_client", "create_sds_client",
"fetch_riin_all_pages", "fetch_sds_all_pages", "fetch_goods_concurrent",
"fetch_riin_all_pages", "fetch_sds_all_pages", "fetch_goods_for_orders",
"get_channel_type_map", "refresh_channel_type_map", "resolve_courier_source",
"format_date", "start_of_day", "floor_to_hour",
"build_stats_rows", "emit_sync_summary", "emit_verify_report",
+42 -24
View File
@@ -21,7 +21,6 @@ from flows.shared.mappers import map_sds_channel_type
T = TypeVar("T")
GOODS_CONCURRENCY = 20
PAGE_DELAY_MS = 500
MAX_RETRIES = 3
RETRY_DELAY_MS = 3000
@@ -144,39 +143,58 @@ def fetch_sds_all_pages(client: SdsMngClient, params: dict[str, Any]) -> list[An
# ---------- RIIN 商品 ----------
def fetch_goods_for_order(client: RiinDisClient, order_id: str) -> list[Any]:
try:
resp = client.get_order_goods(order_id)
return list(resp.data)
except Exception as err: # noqa: BLE001
print(f"[RIIN sync] 订单 {order_id} 商品拉取失败: {err}")
return []
# 批量接口 distribution/order/goods/list:单批 ≤200 个 id,且要求串行调用(分销侧约定)。
# 相比旧逐单接口(软限流:高频返回成功但空 data),调用量从日均 ~1 万次降到 ~50 次。
GOODS_BATCH_SIZE = 200
GOODS_BATCH_INTERVAL_MS = 2000 # 相邻两批的串行间隔
def fetch_goods_concurrent(client: RiinDisClient, order_ids: list[str]) -> dict[str, list[Any]]:
"""以 GOODS_CONCURRENCY 并发拉取多订单商品,返回 {order_id: [goods...]}。"""
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
def fetch_goods_for_orders(client: RiinDisClient, order_ids: list[str]) -> dict[str, list[Any]]:
"""按 goods/list 批量接口串行拉取多订单商品,返回 {order_id: [goods...]}。
results: dict[str, list[Any]] = {}
单批失败(网络/业务错误)整批重试最多 MAX_RETRIES 次,仍失败则该批订单记空列表,
由调用方按"无商品订单"计数(与旧逐单路径的容错语义一致)。
"""
results: dict[str, list[Any]] = {oid: [] for oid in order_ids}
if not order_ids:
return results
concurrency = min(GOODS_CONCURRENCY, len(order_ids))
print(f"[RIIN sync] 商品拉取开始: {len(order_ids)} 个订单 (并发={concurrency})")
def make(oid: str) -> Callable[[], None]:
def run() -> None:
results[oid] = fetch_goods_for_order(client, oid)
return run
with ThreadPoolExecutor(max_workers=concurrency) as pool:
list(pool.map(lambda fn: fn(), [make(oid) for oid in order_ids]))
total_batches = -(-len(order_ids) // GOODS_BATCH_SIZE)
print(f"[RIIN sync] 商品批量拉取开始: {len(order_ids)} 个订单 / {total_batches} 批"
f" (goods/list 串行, 批间 {GOODS_BATCH_INTERVAL_MS}ms)")
for bi in range(total_batches):
chunk = order_ids[bi * GOODS_BATCH_SIZE : (bi + 1) * GOODS_BATCH_SIZE]
items = _fetch_goods_batch_with_retry(client, chunk, bi + 1)
for g in items:
if g.order_id in results:
results[g.order_id].append(g)
if bi < total_batches - 1:
time.sleep(GOODS_BATCH_INTERVAL_MS / 1000)
total_goods = sum(len(v) for v in results.values())
empty_orders = sum(1 for v in results.values() if not v)
print(f"[RIIN sync] 商品拉取完成: 共 {total_goods} 条商品, {empty_orders} 个订单无商品")
print(f"[RIIN sync] 商品批量拉取完成: 共 {total_goods} 条商品, {empty_orders} 个订单无商品")
return results
def _fetch_goods_batch_with_retry(client: RiinDisClient, chunk: list[str], batch_no: int) -> list[Any]:
last_err: Exception | None = None
for attempt in range(1, MAX_RETRIES + 1):
try:
resp = fetch_with_retry(
lambda: client.get_order_goods_batch(chunk), f"RIIN goods 批次 {batch_no}"
)
if resp.successful is False:
raise RuntimeError(f"goods/list 业务失败: {resp.message}")
return list(resp.data)
except Exception as err: # noqa: BLE001
last_err = err
print(f"[RIIN sync] 商品批次 {batch_no} 第 {attempt}/{MAX_RETRIES} 次失败: {str(err)[:120]}")
if attempt < MAX_RETRIES:
time.sleep(RETRY_DELAY_MS * attempt / 1000)
assert last_err is not None
print(f"[RIIN sync] 商品批次 {batch_no} 放弃, {len(chunk)} 个订单本轮无商品")
return []
# ---------- SDS 物流渠道字典 ----------
_channel_cache: dict[str, Any] = {}
+7 -4
View File
@@ -25,8 +25,9 @@ LOOKUP_BATCH = 10000
# 单锁无环,写事务之间从结构上不可能死锁。1768851058 = "inkr" 的 ASCII 整数表示。
DB_WRITE_LOCK_KEY = 1768851058
LOCK_TIMEOUT = "15s" # 等锁超时 → LockNotAvailable → 交给重试兜底
WRITE_RETRIES = 3 # 死锁/等锁超时的整事务重试次数
WRITE_RETRY_BACKOFF = [1, 3, 7] # 重试退避(秒)
WRITE_RETRIES = 5 # 死锁/等锁超时的整事务重试次数(外部写者如 17track
# 物流回写不走 advisory 锁,冲突窗口可达分钟级,重试需够深)
WRITE_RETRY_BACKOFF = [1, 3, 7, 15, 30] # 重试退避(秒;末位预留,最后一次失败直接抛出)
WRITE_RETRYABLE_ERRORS = (pg_errors.DeadlockDetected, pg_errors.LockNotAvailable)
@@ -459,9 +460,11 @@ class SyncDB:
stats = {"ordersCreated": 0, "ordersUpdated": 0, "goodsCreated": 0, "goodsUpdated": 0, "goodsRemoved": 0}
with conn.cursor() as cur:
# 统计已存在 → created/updated
# 注:必须 cast 成 timestamptz——入参是带时区偏移的 tz-aware datetime,
# cast 成 timestamp 会丢弃偏移,在会话时区非 +08 时匹配不到既有行,导致 created/updated 统计全错。
pairs = [(o.orderNo, o.createdAt) for o in orders]
cur.execute(
"SELECT t.order_no, t.created_at FROM unnest(%s::text[], %s::timestamp[]) AS t(order_no,created_at) "
"SELECT t.order_no, t.created_at FROM unnest(%s::text[], %s::timestamptz[]) AS t(order_no,created_at) "
"JOIN \"Orders\" o ON o.\"orderNo\"=t.order_no AND o.\"createdAt\"=t.created_at",
_unnest_pairs(pairs),
)
@@ -622,6 +625,6 @@ class SyncDB:
def _unnest_pairs(pairs: list[tuple]) -> tuple[list, list]:
"""构造 psycopg2 unnest 双数组参数:返回 (col_a_list, col_b_list)。
配合 SQL:unnest(%s::text[], %s::timestamp[]) 或 unnest(%s::text[], %s::text[])。
配合 SQL:unnest(%s::text[], %s::timestamptz[]) 或 unnest(%s::text[], %s::text[])。
"""
return [p[0] for p in pairs], [p[1] for p in pairs]
+51 -8
View File
@@ -33,6 +33,10 @@ RIIN_EXPRESS_SOURCE_MAP: dict[int, str] = {
RIIN_GOODS_STATUS_MAP: dict[str, str] = {
"NOT_SHIPPED": "待发货", "SHIPPED": "已发货", "CANCEL": "已取消",
"CANCELLED": "已取消", "REFUNDING": "退款中",
# 商品级状态复用平台状态枚举,COMPLETE/PAUSE 缺失曾导致 1/3 商品行 status 为 NULL
"COMPLETE": "已完成", "PAUSE": "暂停",
# CLOSE 同为商品终态(Goods 约束无"已关闭",取"已取消");UNKNOWN 保留 NULL 并由告警暴露
"CLOSE": "已取消",
}
RIIN_PLATFORM_STATUS_MAP: dict[str, str] = {
@@ -55,6 +59,17 @@ SDS_CHANNEL_TYPE_LABEL_MAP: dict[str, str] = {
# ---------- 日期解析 ----------
# 未知枚举值告警:同值只报一次,避免批量同步刷屏(对齐 TS 版 warnUnknown)
_warned_values: set[str] = set()
def _warn_unknown(map_name: str, value: object) -> None:
key = f"{map_name}:{value!r}"
if key not in _warned_values:
_warned_values.add(key)
print(f"[mapper] unknown {map_name} value: {value!r}")
def safe_date(value: Any, context: str = "") -> datetime:
"""解析日期字符串(无时区视为 +08:00),返回 tz-aware datetime。"""
if not value or not isinstance(value, str):
@@ -188,15 +203,20 @@ class GoodsUpsertInput:
# ---------- RIIN 映射 ----------
def map_riin_system_status(order_status: int) -> str | None:
return RIIN_SYSTEM_STATUS_MAP.get(order_status)
def map_riin_system_status(order_status: int | None) -> str | None:
if order_status is None:
return None
v = RIIN_SYSTEM_STATUS_MAP.get(order_status)
if v is None:
_warn_unknown("RIIN systemStatus", order_status)
return v
def map_riin_order(raw: Any) -> OrderUpsertInput:
platform_oid = raw.platform_oid
ordered_at = _parse(raw.push_time, f"RIIN order {platform_oid} pushTime")
confirmed_at = _parse(raw.distributor_push_time, f"RIIN order {platform_oid} distributorPushTime")
system_status = map_riin_system_status(raw.order_status) if raw.order_status is not None else None
system_status = map_riin_system_status(raw.order_status)
is_terminal = system_status in ("已发货", "已关闭", "已取消")
completed_at: datetime | None = None
if is_terminal:
@@ -236,6 +256,15 @@ def map_riin_order(raw: Any) -> OrderUpsertInput:
)
def map_riin_goods_status(goods_status: str | None) -> str | None:
if not goods_status:
return None
v = RIIN_GOODS_STATUS_MAP.get(goods_status)
if v is None:
_warn_unknown("RIIN goodsStatus", goods_status)
return v
def map_riin_goods(raw: Any, parent_order_no: str, parent_created_at: datetime) -> GoodsUpsertInput:
product_code = raw.product_code
item_name = product_code.split("-")[0] if product_code else None
@@ -250,7 +279,7 @@ def map_riin_goods(raw: Any, parent_order_no: str, parent_created_at: datetime)
itemSize=raw.size_code,
itemStyleNo=style_no,
quantity=raw.num if raw.num is not None else 1,
status=RIIN_GOODS_STATUS_MAP.get(raw.goods_status) if raw.goods_status else None,
status=map_riin_goods_status(raw.goods_status),
)
@@ -262,13 +291,27 @@ def map_sds_channel_type(channel_type: Any) -> str | None:
return SDS_CHANNEL_TYPE_LABEL_MAP.get(channel_type)
def map_sds_system_status(status: int) -> str | None:
return SDS_SYSTEM_STATUS_MAP.get(status)
def map_sds_system_status(status: int | None) -> str | None:
if status is None:
return None
v = SDS_SYSTEM_STATUS_MAP.get(status)
if v is None:
_warn_unknown("SDS systemStatus", status)
return v
def map_sds_goods_status(status: int | None) -> str | None:
if status is None:
return None
v = SDS_GOODS_STATUS_MAP.get(status)
if v is None:
_warn_unknown("SDS goodsStatus", status)
return v
def map_sds_order(raw: Any, channel_type_map: dict[int, str | None], pre_courier: str | None = None) -> OrderUpsertInput:
ordered_at = _parse(raw.gmt_pay, f"SDS order {raw.no} gmtPay")
system_status = map_sds_system_status(raw.status) if raw.status is not None else None
system_status = map_sds_system_status(raw.status)
is_terminal = system_status in ("已完成", "已取消")
completed_at: datetime | None = None
if is_terminal and raw.finish_time and raw.finish_time > 0:
@@ -369,7 +412,7 @@ def map_sds_goods(raw: Any, parent_order_no: str, parent_created_at: datetime) -
itemSize=raw.product_size,
itemStyleNo=_parse_sds_style_no(product_name),
quantity=raw.num if raw.num is not None else 1,
status=SDS_GOODS_STATUS_MAP.get(raw.status) if raw.status is not None else None,
status=map_sds_goods_status(raw.status),
)
+45
View File
@@ -21,6 +21,7 @@ deployments:
schedules:
- cron: 30 * * * *
timezone: Asia/Shanghai
active: false # 暂停:每小时重拉近7天全部订单商品,对 RIIN distribution/order/goods 调用量过大(增量/刷新未完结/日报核验已覆盖)
work_pool:
name: inkreach-pool
- name: riin-refresh-pending
@@ -39,6 +40,7 @@ deployments:
schedules:
- cron: 0 6 * * *
timezone: Asia/Shanghai
active: false # 暂停:与 daily-report 18:30 的核验子流程重复,保留日报一处即可
work_pool:
name: inkreach-pool
- name: sds-order-sync
@@ -80,6 +82,49 @@ deployments:
timezone: Asia/Shanghai
work_pool:
name: inkreach-pool
- name: sds-waybill-autogen
entrypoint: flows/sds/waybill_autogen.py:waybill_autogen
schedules:
- cron: '*/10 * * * *'
timezone: Asia/Shanghai
active: false # 暂停:真实下单 flow(极兔+面单回传)。链路人工核对后改 true 并 `uv run prefect deploy --all` 生效
work_pool:
name: inkreach-pool
- name: platform-order-riin
entrypoint: flows/platform_order/riin.py:riin_platform_sync
schedules:
- cron: '*/30 * * * *'
timezone: Asia/Shanghai
work_pool:
name: inkreach-pool
- name: platform-order-rouhui
entrypoint: flows/platform_order/rouhui.py:rouhui_platform_sync
schedules:
- cron: '*/15 * * * *'
timezone: Asia/Shanghai
work_pool:
name: inkreach-pool
- name: platform-order-sdspod
entrypoint: flows/platform_order/sdspod.py:sdspod_platform_sync
schedules:
- cron: 0 * * * *
timezone: Asia/Shanghai
work_pool:
name: inkreach-pool
- name: platform-order-upper-sync
entrypoint: flows/platform_order/platform_order_upper_sync.py:platform_order_upper_sync
schedules:
- cron: '*/2 * * * *'
timezone: Asia/Shanghai
work_pool:
name: inkreach-pool
- name: shop-dim-sync
entrypoint: flows/platform_order/sync_shop_dim.py:shop_dim_sync
schedules:
- cron: '*/5 * * * *'
timezone: Asia/Shanghai
work_pool:
name: inkreach-pool
- name: label-ocr
entrypoint: flows/label_ocr.py:label_ocr
schedules:
+3
View File
@@ -11,6 +11,9 @@ dependencies = [
"pandas",
"pydantic>=2.0",
"psutil>=7.2.2",
"requests>=2.34.2",
"aiohttp>=3.14.3",
"pillow>=13.0.0",
]
[build-system]
+72
View File
@@ -0,0 +1,72 @@
"""一次性回填:RIIN Goods.status 为 NULL 的历史行(终态"已发货"订单)。
背景:RIIN 商品级 goods_status 复用平台状态枚举,COMPLETE/PAUSE 此前无映射,
约 1/3 商品行 status 被写为 NULL(mappers.py 已补映射)。本脚本按订单终态
将近似值回填;"已关闭"与非终态订单不在此列,走 riin-by-orders 重拉取真值。
幂等:只更新 status IS NULL 的行。分批提交,避免长事务。
运行:uv run python scripts/backfill_riin_goods_status.py [--dry-run] [--batch N]
"""
from __future__ import annotations
import argparse
import os
import psycopg2
from dotenv import load_dotenv
load_dotenv(os.path.join(os.path.dirname(__file__), "..", ".env"))
DEFAULT_BATCH = 50_000
SQL_COUNT = """
SELECT count(*) FROM "Goods" g
JOIN "Orders" o ON o."orderNo" = g."orderNo" AND o."createdAt" = g."createdAt"
WHERE o."systemName" = 'RIIN' AND o."systemStatus" = '已发货' AND g."status" IS NULL
"""
# ctid 分批:PostgreSQL 的 UPDATE 不支持 LIMIT,用子查询圈定每批行
SQL_BACKFILL_BATCH = """
UPDATE "Goods" g SET "status" = '已发货'
WHERE g.ctid IN (
SELECT g2.ctid FROM "Goods" g2
JOIN "Orders" o ON o."orderNo" = g2."orderNo" AND o."createdAt" = g2."createdAt"
WHERE o."systemName" = 'RIIN' AND o."systemStatus" = '已发货' AND g2."status" IS NULL
LIMIT %s
)
"""
def main() -> None:
parser = argparse.ArgumentParser(description="回填 RIIN Goods.status NULL(已发货订单)")
parser.add_argument("--dry-run", action="store_true", help="只统计待回填行数,不写入")
parser.add_argument("--batch", type=int, default=DEFAULT_BATCH, help="每事务回填行数")
args = parser.parse_args()
conn = psycopg2.connect(os.environ["DB_DSN"])
try:
with conn.cursor() as cur:
cur.execute(SQL_COUNT)
(pending,) = cur.fetchone()
print(f"[backfill] 待回填(status IS NULL 且订单已发货): {pending} 行")
if args.dry_run or pending == 0:
return
total = 0
while True:
with conn.cursor() as cur:
cur.execute(SQL_BACKFILL_BATCH, (args.batch,))
updated = cur.rowcount
conn.commit()
total += updated
print(f"[backfill] 本批 {updated} 行,累计 {total}")
if updated < args.batch:
break
print(f"[backfill] 完成,共回填 {total} 行")
finally:
conn.close()
if __name__ == "__main__":
main()
+159
View File
@@ -0,0 +1,159 @@
"""快递100 上门取件API 回调接收服务(本机常驻,经 frpc 暴露公网)。
接收两类推送并验签落盘(MD5(param + salt)):
- POST /kuaidi100/order 下单状态回调(callBackUrl)与面单短链/打印回调(printCallBackUrl)
- POST /kuaidi100/poll 轨迹推送(pollCallBackUrl)
应答格式必须是 {"result":true,"returnCode":"200","message":"成功"},
否则快递100会以 30 分钟间隔重推 3 次。
查看:
- GET / 状态与最近回调摘要
- GET /list?n=20 最近 n 条回调明细(JSON)
落盘: <repo>/data/kuaidi100/callbacks.jsonl,每行 {ts,path,verified,taskId,param}
用法: uv run python scripts/kuaidi100_callback_server.py [--port 9802] [--salt inkreach-test]
salt 需与下单 param.salt 一致;也可用环境变量 KUAIDI100_CALLBACK_SALT 覆盖。
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse
REPO_ROOT = Path(__file__).resolve().parent.parent
LOG_FILE = REPO_ROOT / "data" / "kuaidi100" / "callbacks.jsonl"
MAX_BODY = 1 << 20 # 1MB 上限,防异常大包
_state = {"started_at": time.time(), "received": 0, "verified_ok": 0}
def _append(record: dict) -> None:
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
with LOG_FILE.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
def _recent(n: int = 20) -> list[dict]:
if not LOG_FILE.exists():
return []
try:
lines = LOG_FILE.read_text(encoding="utf-8").splitlines()
except OSError:
return []
out = []
for line in lines[-n:]:
try:
out.append(json.loads(line))
except json.JSONDecodeError:
continue
return out
class Handler(BaseHTTPRequestHandler):
salt: str = os.environ.get("KUAIDI100_CALLBACK_SALT", "inkreach-test")
def log_message(self, fmt: str, *args) -> None: # 静默默认访问日志,落盘代替
pass
# ----- POST 回调 -----
def do_POST(self) -> None:
path = urlparse(self.path).path
if not path.startswith("/kuaidi100/"):
self._json(404, {"result": False, "returnCode": "404", "message": "not found"})
return
length = min(int(self.headers.get("Content-Length") or 0), MAX_BODY)
body = self.rfile.read(length).decode("utf-8", errors="replace")
form = parse_qs(body, keep_blank_values=True)
param_raw = (form.get("param") or [""])[0]
sign = (form.get("sign") or [""])[0].upper()
task_id = (form.get("taskId") or [""])[0]
expect = hashlib.md5(f"{param_raw}{self.salt}".encode("utf-8")).hexdigest().upper()
verified = bool(param_raw) and sign == expect
parsed: dict | None = None
if param_raw:
try:
parsed = json.loads(param_raw)
except json.JSONDecodeError:
parsed = None
_state["received"] += 1
_state["verified_ok"] += int(verified)
_append(
{
"ts": time.strftime("%Y-%m-%d %H:%M:%S"),
"path": path,
"verified": verified,
"sign": sign,
"taskId": task_id or None,
"param": parsed if parsed is not None else param_raw[:2000],
"client": self.client_address[0],
}
)
# 验签失败也先按成功应答(已落盘标记 verified=false),避免测试环境反复重推干扰;
# 生产可改为拒绝: {"result": false, ...}
self._json(200, {"result": True, "returnCode": "200", "message": "成功"})
# ----- GET 查看 -----
def do_GET(self) -> None:
url = urlparse(self.path)
# 兼容两种路径:直连 /list 与经 frp/反代的 /kuaidi100/list(与 POST 路由前缀对齐)
if url.path in ("/list", "/kuaidi100/list"):
qs = parse_qs(url.query)
n = min(int((qs.get("n") or ["20"])[0]), 200)
self._json(200, {"count": n, "items": list(reversed(_recent(n)))})
return
recent = _recent(5)
self._json(
200,
{
"service": "kuaidi100-callback",
"listen": "127.0.0.1 (via frpc)",
"started_at": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(_state["started_at"])),
"uptime_s": int(time.time() - _state["started_at"]),
"received": _state["received"],
"verified_ok": _state["verified_ok"],
"log_file": str(LOG_FILE),
"recent": [
{"ts": r.get("ts"), "path": r.get("path"), "verified": r.get("verified"),
"status": (r.get("param") or {}).get("data", {}).get("status")
if isinstance(r.get("param"), dict) else None}
for r in recent
],
},
)
def _json(self, code: int, payload: dict) -> None:
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def main() -> None:
parser = argparse.ArgumentParser(description="快递100回调接收服务")
parser.add_argument("--port", type=int, default=9802)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--salt", default=Handler.salt, help="验签 salt,须与下单 param.salt 一致")
args = parser.parse_args()
Handler.salt = args.salt
server = ThreadingHTTPServer((args.host, args.port), Handler)
print(f"[kuaidi100-callback] listening on http://{args.host}:{args.port} (salt={args.salt})")
print(f"[kuaidi100-callback] log -> {LOG_FILE}")
server.serve_forever()
if __name__ == "__main__":
main()
+407
View File
@@ -0,0 +1,407 @@
"""快递100「上门取件API(线上支付)」端到端测试脚本。
文档: https://api.kuaidi100.com/document/603cb649a62a19500e19866b
流程:
1. 从 SDS 取一单国内工厂订单(factoryIds=1161),提取收/寄件人地址(收件人=订单 address,寄件人=发货仓 issuing_bay)
2. 逐家查价(method=price),选最便宜的快递公司
3. 下单(method=bOrder)拿 taskId/orderId/kuaidinum
4. 取消订单(method=cancel)收尾,避免遗留测试单
鉴权: 公共参数 method/key/sign/t,param 为 JSON 字符串;
sign = MD5(param + t + key + secret) 转 32 位大写。
测试环境: http://e-test.kuaidilab.com/api/order/borderapi.do(测试 key/secret 需向快递100技术支持申请)
用法:
uv run python scripts/kuaidi100_pickup_test.py --key KEY --secret SECRET [--customer CUSTOMER]
# 或把 JSON {"key":"...","secret":"...","customer":"..."} 存进 Prefect Secret Block kuaidi100-test 后:
uv run python scripts/kuaidi100_pickup_test.py --from-secret
"""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
import time
from pathlib import Path
from typing import Any
import httpx
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
TEST_BASE_URL = "http://e-test.kuaidilab.com/api/order/borderapi.do"
PROD_BASE_URL = "https://poll.kuaidi100.com/order/borderapi.do"
# 文档列出的本 API 支持的快递公司编码(小写)
SUPPORTED_COURIERS = [
"jd",
"debangkuaidi",
"shunfeng",
"jtexpress",
"yuantong",
"shentong",
"zhongtong",
"zhongtongkuaiyun",
"yunda",
"kuayue",
"shunfengkuaiyun",
"ems",
"yimidida",
"kuaidi100zhixuan",
]
COURIER_NAMES = {
"jd": "京东",
"debangkuaidi": "德邦快递",
"shunfeng": "顺丰速运",
"jtexpress": "极兔速递",
"yuantong": "圆通速递",
"shentong": "申通快递",
"zhongtong": "中通快递",
"zhongtongkuaiyun": "中通快运",
"yunda": "韵达快递",
"kuayue": "跨越速运",
"shunfengkuaiyun": "顺丰快运",
"ems": "EMS",
"yimidida": "壹米滴答",
"kuaidi100zhixuan": "快递100智选",
}
# ---------- 快递100 公共调用 ----------
def make_sign(param: str, t: str, key: str, secret: str) -> str:
"""文档签名算法: MD5(param + t + key + secret) 转 32 位大写。"""
raw = f"{param}{t}{key}{secret}"
return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()
def call_api(
method: str,
param: dict[str, Any],
auth: dict[str, str],
base_url: str = TEST_BASE_URL,
timeout: float = 30.0,
) -> dict[str, Any]:
"""POST application/x-www-form-urlencoded,公共参数 method/key/sign/t/param。"""
param_str = json.dumps(param, ensure_ascii=False, separators=(",", ":"))
t = str(int(time.time() * 1000))
body = {
"method": method,
"key": auth["key"],
"sign": make_sign(param_str, t, auth["key"], auth["secret"]),
"t": t,
"param": param_str,
}
resp = httpx.post(base_url, data=body, timeout=timeout)
resp.raise_for_status()
return resp.json()
def check(resp: dict[str, Any], action: str) -> dict[str, Any]:
if resp.get("result") is not True or str(resp.get("returnCode")) != "200":
raise RuntimeError(f"[{action}] 失败: {json.dumps(resp, ensure_ascii=False)[:500]}")
print(f"[{action}] 成功: {resp.get('message')}")
return resp
# ---------- SDS 取单 ----------
def calc_order_weight_grams(client: Any, order: Any) -> tuple[float | None, list[str]]:
"""委托 SdsMngClient.get_order_weight_grams(children 变体克重,24h 缓存)。"""
return client.get_order_weight_grams(order)
def fetch_sds_domestic_order(client: Any | None = None) -> dict[str, str]:
"""取一单国内工厂(factoryIds=1161)订单,返回寄件/收件人字段+结构化克重。"""
if client is None:
from api import SdsMngClient
from prefect.blocks.system import Secret
value = Secret.load("sds-credentials").get()
cred = json.loads(value) if isinstance(value, str) else value
client = SdsMngClient(cred["username"], cred["password"])
from api.sds.models import OrderPageRequest
resp = client.get_orders(OrderPageRequest(factory_ids=[1161], size=1, page=1))
if not resp.records:
raise RuntimeError("SDS 未找到 factoryIds=1161 的订单")
order = resp.records[0]
addr = order.address
bay = order.issuing_bay
if not addr or not bay:
raise RuntimeError("订单缺少收件地址或发货仓信息")
send_print_addr = _join_addr(bay.province, bay.city, bay.detail)
rec_print_addr = _join_addr(addr.province, addr.city, addr.detail)
cargo = order.items[0].product_name or "日用品" if order.items else "日用品"
info = {
"order_no": order.no or order.id,
"send_name": bay.name or "印美达",
"send_addr": send_print_addr,
"rec_name": addr.consignee or "收件人",
"rec_mobile": addr.mobile_phone or "",
"rec_addr": rec_print_addr,
"cargo": cargo,
}
grams, missed = calc_order_weight_grams(client, order)
if grams:
info["weight_kg"] = f"{grams / 1000:.2f}"
print(f"[weight] 结构化克重合计 {round(grams)}g = {info['weight_kg']}kg")
else:
print(f"[weight] 未能取到结构化克重({'; '.join(missed) or '无商品行'}),下单重量回退为默认 1kg")
print(
f"[SDS] 选中订单 {info['order_no']}\n"
f" 寄件: {info['send_name']} / {info['send_addr']}\n"
f" 收件: {info['rec_name']} {info['rec_mobile']} / {info['rec_addr']}\n"
f" 物品: {info['cargo']}"
)
return info
# ---------- 业务步骤 ----------
def compare_prices(auth: dict[str, str], order_info: dict[str, str], base_url: str, weight: str) -> tuple[str, list[dict[str, Any]]]:
"""逐家查价,按 defPrice(默认总价)升序,返回 (最便宜编码, 全部结果)。"""
results: list[dict[str, Any]] = []
for com in SUPPORTED_COURIERS:
param = {
"kuaidiCom": com,
"sendManPrintAddr": order_info["send_addr"],
"recManPrintAddr": order_info["rec_addr"],
"weight": weight,
}
try:
resp = call_api("price", param, auth, base_url)
if resp.get("result") is True:
data = resp.get("data", {})
results.append({"com": com, **data})
print(
f"[price] {COURIER_NAMES.get(com, com):8s} 首重={data.get('defFirstPrice')} "
f"续重={data.get('defOverPrice')} 总价={data.get('defPrice')}"
)
else:
print(f"[price] {COURIER_NAMES.get(com, com):8s} 不支持: {resp.get('message')}")
except Exception as err: # noqa: BLE001
print(f"[price] {COURIER_NAMES.get(com, com):8s} 查询异常: {str(err)[:120]}")
time.sleep(0.3)
priced = [r for r in results if str(r.get("defPrice", "") not in ("", None)) and _to_float(r.get("defPrice")) is not None]
if not priced:
raise RuntimeError("没有任何快递公司返回有效价格")
priced.sort(key=lambda r: _to_float(r["defPrice"]))
best = priced[0]
print(
f"[price] 共 {len(priced)} 家报价,最便宜: {COURIER_NAMES.get(best['com'], best['com'])} "
f"({best['com']}) 总价 {best['defPrice']} 元"
)
return best["com"], results
def _join_addr(province: str | None, city: str | None, detail: str | None) -> str:
"""拼省+市+详细地址;detail 已含省/市名时跳过对应前缀,避免重复。"""
detail = detail or ""
prefix = ""
for part in (province or "", city or ""):
if part and part not in detail and part not in prefix:
prefix += part
return prefix + detail
def _to_float(v: Any) -> float | None:
try:
return float(v)
except (TypeError, ValueError):
return None
def place_order(
auth: dict[str, str], order_info: dict[str, str], base_url: str, kuaidicom: str, weight: str,
sender_mobile: str, callback_url: str, dry_run: bool = False,
) -> dict[str, Any]:
param = {
"kuaidicom": kuaidicom,
"recManName": order_info["rec_name"],
"recManMobile": order_info["rec_mobile"],
"recManPrintAddr": order_info["rec_addr"],
"sendManName": order_info["send_name"],
"sendManMobile": sender_mobile,
"sendManPrintAddr": order_info["send_addr"],
"callBackUrl": callback_url,
"cargo": order_info["cargo"],
"weight": weight,
"payment": "SHIPPER",
"remark": f"kuaidi100-e2e-test {order_info['order_no']}",
"salt": "inkreach-test",
"op": 0,
"resultv2": "0",
}
if kuaidicom in ("shunfeng", "shunfengkuaiyun"): # 文档要求顺丰必填预约取件时间窗(间隔≥1小时)
now = time.localtime()
start_h = (now.tm_hour + 1) % 24
param.update(
dayType="1",
pickupStartTime=f"{start_h:02d}:00",
pickupEndTime=f"{(start_h + 1) % 24:02d}:00",
)
if dry_run:
print("[bOrder dry-run] 以下为将提交的完整 param(未发送请求):")
print(json.dumps(param, ensure_ascii=False, indent=2))
return {}
resp = check(call_api("bOrder", param, auth, base_url), "bOrder 下单")
data = resp.get("data") or {}
print(
f"[bOrder] taskId={data.get('taskId')} orderId={data.get('orderId')} "
f"kuaidinum={data.get('kuaidinum')} pollToken={str(data.get('pollToken'))[:20]}"
)
return data
def query_detail(auth: dict[str, str], base_url: str, task_id: Any) -> dict[str, Any] | None:
"""订单详情查询(method=detail,只传 taskId),校验订单已正确落库。"""
try:
resp = check(call_api("detail", {"taskId": task_id}, auth, base_url), "detail 订单详情")
data = resp.get("data") or {}
print(
f"[detail] status={data.get('status')} kuaidinum={data.get('kuaidiNum')} "
f"serviceType={data.get('serviceType')} payStatus={data.get('payStatus')}"
)
return data
except Exception as err: # noqa: BLE001
print(f"[detail] 查询失败(不中断测试): {str(err)[:160]}")
return None
def cancel_order(auth: dict[str, str], base_url: str, task_id: Any, order_id: Any) -> None:
"""取消订单。taskId/orderId 原样回传;cancelMsg 用文档示例文案(实测含"测试"字样会被判订单信息错误)。"""
param = {"taskId": task_id, "orderId": order_id, "cancelMsg": "暂时不寄件了"}
check(call_api("cancel", param, auth, base_url), "cancel 取消")
def track_query(auth: dict[str, str], com: str, num: str, phone: str = "") -> dict[str, Any] | None:
"""实时轨迹查询(poll/query.do): sign=MD5(param+key+customer),需 customer 参数。"""
if not auth.get("customer"):
print("[track] 未提供 customer,跳过轨迹查询")
return None
url = base_query_url(auth)
param = json.dumps({"com": com, "num": num, "phone": phone}, ensure_ascii=False, separators=(",", ":"))
customer = auth["customer"]
sign = hashlib.md5(f"{param}{auth['key']}{customer}".encode()).hexdigest().upper()
resp = httpx.post(
url,
data={"customer": customer, "sign": sign, "param": param},
timeout=30,
).json()
print(f"[track] {json.dumps(resp, ensure_ascii=False)[:300]}")
return resp
def base_query_url(auth: dict[str, str]) -> str:
base = TEST_BASE_URL.replace("/order/borderapi.do", "")
return f"{base}/poll/query.do"
# ---------- 入口 ----------
def load_auth(args: argparse.Namespace) -> dict[str, str]:
if args.from_secret:
from prefect.blocks.system import Secret
value = Secret.load(args.secret_block).get()
auth = json.loads(value) if isinstance(value, str) else value
else:
auth = {"key": args.key or "", "secret": args.secret or "", "customer": args.customer or ""}
missing = [k for k in ("key", "secret") if not auth.get(k)]
if missing:
raise SystemExit(f"缺少快递100鉴权参数: {missing}。用 --key/--secret 或 --from-secret 提供。")
return auth
def main() -> None:
parser = argparse.ArgumentParser(description="快递100上门取件API端到端测试")
parser.add_argument("--key", help="企业版授权码 key")
parser.add_argument("--secret", help="签名密钥 secret")
parser.add_argument("--customer", default="", help="轨迹查询用 customer(可选)")
parser.add_argument("--from-secret", action="store_true", help="从 Prefect Secret Block 读鉴权")
parser.add_argument("--secret-block", default="kuaidi100-test", help="Secret Block 名(默认 kuaidi100-test)")
parser.add_argument("--base-url", default=TEST_BASE_URL, help=f"API 地址(默认测试环境 {TEST_BASE_URL})")
parser.add_argument("--courier", default="yuantong", help="快递编码(默认固定圆通 yuantong)")
parser.add_argument("--auto-cheapest", action="store_true", help="按比价结果自动选最便宜(默认用 --courier 固定值)")
parser.add_argument("--skip-price", action="store_true", help="跳过比价查询")
parser.add_argument("--weight", default=None, help="重量 KG;默认用 SDS 变体克重自动汇总,取不到回退 1")
parser.add_argument(
"--sender-name", default="张文风",
help="寄件人姓名(固定,覆盖 SDS 发货仓名)",
)
parser.add_argument(
"--sender-mobile", default="18320690872",
help="寄件人手机号(固定)",
)
parser.add_argument(
"--sender-addr", default="广东省广州市番禺区南村镇新基村新盈科技园3栋2楼",
help="寄件地址(固定,覆盖 SDS 发货仓地址)",
)
parser.add_argument("--dry-run", action="store_true", help="只构造并打印下单参数,不实际下单")
parser.add_argument(
"--callback-url",
default="http://8.138.172.239:18090/kuaidi100/order",
help="订单回调地址(默认走专属 frpc 实例 kuaidi100 穿透到本机回调服务)",
)
parser.add_argument("--keep-order", action="store_true", help="下单后不取消")
args = parser.parse_args()
auth = load_auth(args)
order_info = fetch_sds_domestic_order()
# 寄件人固定为广州仓联系人(命令行可覆盖),收件人保持 SDS 订单动态获取
order_info["send_name"] = args.sender_name
order_info["send_addr"] = args.sender_addr
print(f"[sender] 寄件人固定: {order_info['send_name']} {args.sender_mobile} / {order_info['send_addr']}")
weight = args.weight or order_info.get("weight_kg") or "1"
print(f"[weight] 下单申报重量: {weight}kg")
kuaidicom = args.courier
if not args.skip_price:
cheapest, _ = compare_prices(auth, order_info, args.base_url, weight) # 比价仅作参考
if args.auto_cheapest:
kuaidicom = cheapest
print(f"[price] --auto-cheapest 生效,切到最便宜: {COURIER_NAMES.get(cheapest, cheapest)} ({cheapest})")
print(f"[courier] 固定使用: {COURIER_NAMES.get(kuaidicom, kuaidicom)} ({kuaidicom})")
data = place_order(
auth, order_info, args.base_url, kuaidicom, weight,
args.sender_mobile, args.callback_url, dry_run=args.dry_run,
)
if args.dry_run:
print("[done] dry-run 结束,未下单 ✓")
return
task_id = data.get("taskId") # 保持原始类型回传,cancel 接口对类型敏感
order_id = data.get("orderId")
kuaidinum = str(data.get("kuaidinum") or "")
if kuaidinum:
track_query(auth, kuaidicom, kuaidinum, order_info["rec_mobile"])
if task_id:
query_detail(auth, args.base_url, task_id)
if not args.keep_order and task_id and order_id:
cancel_order(auth, args.base_url, task_id, order_id)
else:
print(f"[done] 保留测试单: taskId={task_id} orderId={order_id}")
print("[done] 端到端测试完成 ✓")
if __name__ == "__main__":
main()
+126
View File
@@ -0,0 +1,126 @@
"""mappers 状态映射单测。
运行:uv run python -m unittest discover -s tests -v
"""
from __future__ import annotations
import io
import unittest
from contextlib import redirect_stdout
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
from flows.shared import mappers
from flows.shared.mappers import (
map_riin_goods,
map_riin_goods_status,
map_riin_system_status,
map_sds_goods_status,
map_sds_system_status,
)
TZ_CN = timezone(timedelta(hours=8))
class WarnUnknownTest(unittest.TestCase):
def setUp(self):
mappers._warned_values.clear()
def _capture(self):
return io.StringIO()
def test_未知值告警且同值只报一次(self):
buf = self._capture()
with redirect_stdout(buf):
self.assertIsNone(map_riin_goods_status("SOMETHING_NEW"))
self.assertIsNone(map_riin_goods_status("SOMETHING_NEW")) # 第二次不再报
lines = [ln for ln in buf.getvalue().splitlines() if "SOMETHING_NEW" in ln]
self.assertEqual(len(lines), 1)
self.assertIn("[mapper] unknown RIIN goodsStatus", lines[0])
class RiinGoodsStatusTest(unittest.TestCase):
def setUp(self):
mappers._warned_values.clear()
def test_全部已知枚举可映射(self):
cases = {
"NOT_SHIPPED": "待发货",
"SHIPPED": "已发货",
"CANCEL": "已取消",
"CANCELLED": "已取消",
"REFUNDING": "退款中",
"COMPLETE": "已完成", # 本次补齐(曾导致 NULL)
"PAUSE": "暂停", # 本次补齐(曾导致 NULL)
"CLOSE": "已取消", # 回填重拉时告警发现的新枚举
}
for raw, want in cases.items():
with self.subTest(raw=raw):
self.assertEqual(map_riin_goods_status(raw), want)
def test_空值返回None(self):
self.assertIsNone(map_riin_goods_status(None))
self.assertIsNone(map_riin_goods_status(""))
def test_map_riin_goods_接线_PAUSE与COMPLETE不再落NULL(self):
# 复现线上订单 GSU1S555400MFLL:4 行商品 goods_status=PAUSE/COMPLETE
created = datetime(2026, 9, 14, 9, 39, 24, tzinfo=TZ_CN)
base = dict(
platform_oid="GSU1S555400MFLL", product_code="DG001-BK",
color_code="BK", size_code="L", style_code=None, num=1,
)
g1 = map_riin_goods(SimpleNamespace(id="P1", goods_status="PAUSE", **base),
"GSU1S555400MFLL", created)
g2 = map_riin_goods(SimpleNamespace(id="P2", goods_status="COMPLETE", **base),
"GSU1S555400MFLL", created)
self.assertEqual(g1.status, "暂停")
self.assertEqual(g2.status, "已完成")
self.assertEqual(g1.productionNo, "P1")
self.assertEqual(g1.quantity, 1)
self.assertEqual(g1.itemName, "DG001")
class RiinSystemStatusTest(unittest.TestCase):
def setUp(self):
mappers._warned_values.clear()
def test_已知与空值(self):
self.assertEqual(map_riin_system_status(12), "已发货")
self.assertEqual(map_riin_system_status(-4), "待推送")
self.assertIsNone(map_riin_system_status(None))
def test_未知值返回None并告警(self):
buf = io.StringIO()
with redirect_stdout(buf):
self.assertIsNone(map_riin_system_status(999))
self.assertIn("unknown RIIN systemStatus", buf.getvalue())
class SdsStatusTest(unittest.TestCase):
def setUp(self):
mappers._warned_values.clear()
def test_商品状态含已删除(self):
self.assertEqual(map_sds_goods_status(99), "已删除")
self.assertEqual(map_sds_goods_status(98), "已取消")
self.assertEqual(map_sds_goods_status(4), "已完成")
self.assertIsNone(map_sds_goods_status(None))
def test_未知商品状态返回None并告警(self):
buf = io.StringIO()
with redirect_stdout(buf):
self.assertIsNone(map_sds_goods_status(6))
self.assertIn("unknown SDS goodsStatus", buf.getvalue())
def test_订单状态与未知告警(self):
self.assertEqual(map_sds_system_status(3), "备货中")
self.assertIsNone(map_sds_system_status(None))
buf = io.StringIO()
with redirect_stdout(buf):
self.assertIsNone(map_sds_system_status(42))
self.assertIn("unknown SDS systemStatus", buf.getvalue())
if __name__ == "__main__":
unittest.main()
Generated
+607
View File
@@ -13,6 +13,128 @@ resolution-markers = [
"python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'",
]
[[package]]
name = "aiohappyeyeballs"
version = "2.7.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" },
]
[[package]]
name = "aiohttp"
version = "3.14.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
{ name = "aiosignal" },
{ name = "attrs" },
{ name = "frozenlist" },
{ name = "multidict" },
{ name = "propcache" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" },
{ url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" },
{ url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" },
{ url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" },
{ url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" },
{ url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" },
{ url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" },
{ url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" },
{ url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" },
{ url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" },
{ url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" },
{ url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" },
{ url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" },
{ url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" },
{ url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" },
{ url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" },
{ url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" },
{ url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" },
{ url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" },
{ url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" },
{ url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" },
{ url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" },
{ url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" },
{ url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" },
{ url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" },
{ url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" },
{ url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" },
{ url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" },
{ url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" },
{ url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" },
{ url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" },
{ url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" },
{ url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" },
{ url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" },
{ url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" },
{ url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" },
{ url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" },
{ url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" },
{ url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" },
{ url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" },
{ url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" },
{ url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" },
{ url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" },
{ url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" },
{ url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" },
{ url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" },
{ url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" },
{ url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" },
{ url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" },
{ url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" },
{ url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" },
{ url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" },
{ url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" },
{ url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" },
{ url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" },
{ url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" },
{ url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" },
{ url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" },
{ url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" },
{ url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" },
{ url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" },
{ url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" },
{ url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" },
{ url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" },
{ url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" },
{ url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" },
{ url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" },
{ url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" },
{ url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" },
{ url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" },
{ url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" },
{ url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" },
{ url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" },
{ url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" },
{ url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" },
{ url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" },
{ url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" },
{ url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" },
{ url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" },
{ url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" },
{ url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" },
{ url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" },
]
[[package]]
name = "aiosignal"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "frozenlist" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
]
[[package]]
name = "aiosqlite"
version = "0.22.1"
@@ -522,6 +644,95 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5f/c7/cb03251d9dfb177246a9809a76f189d21df32dbd4a845951881d11323b7f/fastapi-0.139.2-py3-none-any.whl", hash = "sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c", size = 130234, upload-time = "2026-07-16T15:06:19.557Z" },
]
[[package]]
name = "frozenlist"
version = "1.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" },
{ url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" },
{ url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" },
{ url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" },
{ url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" },
{ url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" },
{ url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" },
{ url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" },
{ url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" },
{ url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" },
{ url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" },
{ url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" },
{ url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" },
{ url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" },
{ url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" },
{ url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" },
{ url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" },
{ url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" },
{ url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" },
{ url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" },
{ url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" },
{ url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" },
{ url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" },
{ url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" },
{ url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" },
{ url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" },
{ url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" },
{ url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" },
{ url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" },
{ url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" },
{ url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" },
{ url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" },
{ url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" },
{ url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" },
{ url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" },
{ url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" },
{ url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" },
{ url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" },
{ url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" },
{ url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" },
{ url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" },
{ url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" },
{ url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" },
{ url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" },
{ url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" },
{ url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" },
{ url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" },
{ url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" },
{ url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" },
{ url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" },
{ url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" },
{ url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" },
{ url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" },
{ url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" },
{ url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" },
{ url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" },
{ url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" },
{ url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" },
{ url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" },
{ url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" },
{ url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" },
{ url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" },
{ url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" },
{ url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" },
{ url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" },
{ url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" },
{ url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" },
{ url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" },
{ url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" },
{ url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" },
{ url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" },
{ url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" },
{ url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" },
{ url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" },
{ url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" },
{ url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" },
{ url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" },
{ url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" },
{ url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" },
{ url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
]
[[package]]
name = "fsspec"
version = "2026.6.0"
@@ -738,24 +949,30 @@ name = "inkreach-prefect"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
{ name = "httpx" },
{ name = "pandas" },
{ name = "pillow" },
{ name = "prefect" },
{ name = "prefect-sqlalchemy" },
{ name = "psutil" },
{ name = "psycopg2-binary" },
{ name = "pydantic" },
{ name = "requests" },
]
[package.metadata]
requires-dist = [
{ name = "aiohttp", specifier = ">=3.14.3" },
{ name = "httpx" },
{ name = "pandas" },
{ name = "pillow", specifier = ">=13.0.0" },
{ name = "prefect", specifier = ">=3.4" },
{ name = "prefect-sqlalchemy" },
{ name = "psutil", specifier = ">=7.2.2" },
{ name = "psycopg2-binary" },
{ name = "pydantic", specifier = ">=2.0" },
{ name = "requests", specifier = ">=2.34.2" },
]
[[package]]
@@ -936,6 +1153,144 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "multidict"
version = "6.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/95/989c1b5ca17b72128661530cd6e351a0a83cda9a4d6c036e9ed976c18931/multidict-6.8.0.tar.gz", hash = "sha256:5cd4637ce76312ba1e05eb9c5193fec231f64fee0944e135fa1e951242355b37", size = 122412, upload-time = "2026-09-09T13:57:57.967Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/83/a4621577679149ea001806f5963f3fc687c391c1bd5217157be2278863f5/multidict-6.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22a310ad37672a261e55a8b5e28d0ae08cfb68abb1f46418ccd19835c3b8e836", size = 84146, upload-time = "2026-09-09T13:53:49.163Z" },
{ url = "https://files.pythonhosted.org/packages/09/00/236b063f3e606055a3a9ba8faa5d40e6c688b059a58056b055f213476f46/multidict-6.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bb8c7da8c861391f7ae48e3593762be2dabe405109e01aec520fbe1a6d15d14b", size = 51049, upload-time = "2026-09-09T13:53:50.46Z" },
{ url = "https://files.pythonhosted.org/packages/91/9d/954b139bfa969855f2d4cb5ae7b7d44dd7106f754305b6e21a9068213aa7/multidict-6.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10456943903744ae1249728161c96bd9d2f7eb5ee17fcc2ffda2dc32e1bb36c7", size = 49362, upload-time = "2026-09-09T13:53:51.878Z" },
{ url = "https://files.pythonhosted.org/packages/d7/8a/8774f5b3f6d5266ecd1117876e04b405f0f1ce19aa750b35a826efe6cfe4/multidict-6.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:658f5a1895b804423d97b22d06fc0d0b171c7c01dcc3aa9c8faf0c0e26a249a5", size = 278619, upload-time = "2026-09-09T13:53:53.44Z" },
{ url = "https://files.pythonhosted.org/packages/db/47/736080fec911ed9f2dd57ccab5a8145e4f17c4987de0bfc27bee20e4d170/multidict-6.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c10b22860dbd09982d0b8993b66231a861bea2993d4a817ff35273f6ea285a", size = 283771, upload-time = "2026-09-09T13:53:55.048Z" },
{ url = "https://files.pythonhosted.org/packages/4c/d5/b7f41f59b0583f092602308a5e7c16ec5efd00d60214b22511e89a38dd19/multidict-6.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:088b04a66b3c1fce6fe4d771ec184a0426262d0b86709c908477b4ac7965df40", size = 262108, upload-time = "2026-09-09T13:53:56.631Z" },
{ url = "https://files.pythonhosted.org/packages/54/b2/a52dc06c6e2598672308e3d392fd85b837b23c25dda459bedaea84985080/multidict-6.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9caef53b20a105c0d66518a34be2f71b2783de8d091767575ef86f6ea422236d", size = 289899, upload-time = "2026-09-09T13:53:58.415Z" },
{ url = "https://files.pythonhosted.org/packages/a9/21/00cda7983f37d119b86f1f89d5b4cf771ecb6d0fedeb9a0971758d6d6d4a/multidict-6.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a5e1583c14775580da05641240ce0d93f36ce3ddef3d5083a827468b0bcfe874", size = 293025, upload-time = "2026-09-09T13:53:59.973Z" },
{ url = "https://files.pythonhosted.org/packages/c6/c7/4544cc02e45bbfac4d8788b05379bb360021fd8c53fa74b0f624126ac188/multidict-6.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003a3bddb32915c3f67096ea41d24e53edf710edb65a1f5d0c70ab40b0e4d20b", size = 287410, upload-time = "2026-09-09T13:54:01.652Z" },
{ url = "https://files.pythonhosted.org/packages/43/1a/7abed90b8eba381842235bfa6f4d730204fd7deb374fc87e3ec9b2c2b4ac/multidict-6.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:35977263d9bf506dbc65349f63b3b8c91606d4abc110990945e3b94bc671319c", size = 255878, upload-time = "2026-09-09T13:54:03.366Z" },
{ url = "https://files.pythonhosted.org/packages/25/3e/73fae10e15fc4d711975337caff7e494c87de5d0189afe3518b21b945326/multidict-6.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9dc7b4ff6ef184504b49ef9a4113d49a646653b2ce89f5f48c1f57cdf6ba081", size = 277831, upload-time = "2026-09-09T13:54:04.963Z" },
{ url = "https://files.pythonhosted.org/packages/c5/cf/01cfc81492933331147004861bdff201d8adeba8485ecd8f490e755fe7e8/multidict-6.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:887f9a975996032c686719eb7b3e1e7942fab5079c2b778bbd9afe9a9d78244f", size = 275096, upload-time = "2026-09-09T13:54:06.661Z" },
{ url = "https://files.pythonhosted.org/packages/de/59/e9a3773b17297fa1e38fd4b3c6f5f2f458380796be62eca7d0d77c250618/multidict-6.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f3071e6515cc63714d014da8f738ae9fa3997c476203f3cd46de380c2376ed7b", size = 279803, upload-time = "2026-09-09T13:54:08.389Z" },
{ url = "https://files.pythonhosted.org/packages/64/9d/2d712a2605b3971908e3b4f5eb6f98c353d9991e106f684d0e08ae581814/multidict-6.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5f3a2af441670d80ce5fdf13b6c1b421fc1fc7fc5182d58ac7486738bb2b742", size = 284595, upload-time = "2026-09-09T13:54:10.17Z" },
{ url = "https://files.pythonhosted.org/packages/58/6c/21aded8586e552b29892268c576e5745d1a894c5451c9866ca3c06b7ec50/multidict-6.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:31e8901637e20ccb3cf8f8848b5d0f7a00462bf5b34f7cf3dcbb2753b18e8b39", size = 252641, upload-time = "2026-09-09T13:54:11.811Z" },
{ url = "https://files.pythonhosted.org/packages/2a/70/56a415ae0a45e5eae2ec817d46aeb72a1ae777863621c85f1f39d329275b/multidict-6.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:13967dca8b2f33230a1427b52438326bb1c9101a1df22a3309ed3fcbbb3c96f0", size = 283369, upload-time = "2026-09-09T13:54:13.59Z" },
{ url = "https://files.pythonhosted.org/packages/08/7e/7b7cd611fd94bf2f6bd16244c50495867ba394d5baaf8e6e487d39494ab3/multidict-6.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad474c11d851b6fc97cb625e4822bc0cbd567fc07dc2602e28faec5a36b42bbb", size = 281653, upload-time = "2026-09-09T13:54:15.174Z" },
{ url = "https://files.pythonhosted.org/packages/33/4a/b19a5892ef2ef6c68ae278b4f1504b82e01037baedd92c55d37e55ecad00/multidict-6.8.0-cp312-cp312-win32.whl", hash = "sha256:7bb0dad75068fee80fcb60f88569722c199d8656a16706702dc6e3b786819c90", size = 47936, upload-time = "2026-09-09T13:54:16.638Z" },
{ url = "https://files.pythonhosted.org/packages/29/00/1952f9f282aa71e7c3db3a6b47afb689d0ddf283dbded7e6326a91d421c9/multidict-6.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:7d26dc8f070c0ec5579e987fa615ffd6883086106eefdff9e10d160fc5630630", size = 51723, upload-time = "2026-09-09T13:54:18.05Z" },
{ url = "https://files.pythonhosted.org/packages/49/b5/c9d57dbafe25b8f3460ce2961c968539a81ff7a70160c44dcfd4255cbcd1/multidict-6.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:e6ec7d37841609a691b96a10b4fde386c7cd93ebbb939f59c9f23325ee788395", size = 48492, upload-time = "2026-09-09T13:54:19.42Z" },
{ url = "https://files.pythonhosted.org/packages/84/1f/d7112c2dd7db02677097be72fb65542f51a5aa73cb472b87ec211ba9e0dd/multidict-6.8.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:ec0a4d066356054d569a66e0a94691a2058b680be5e710298f61db11a3c4609f", size = 54197, upload-time = "2026-09-09T13:54:20.814Z" },
{ url = "https://files.pythonhosted.org/packages/ae/24/876015abbcb4a179d946579eb77b778eb5a948fc8381bc7928ba895bc051/multidict-6.8.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:714597cb5d5e15a8a449d2ae23c45b486a9e8fa33c462c7a33d7f35b65d92943", size = 47787, upload-time = "2026-09-09T13:54:22.51Z" },
{ url = "https://files.pythonhosted.org/packages/06/c1/ceb7d25f8a567599db2eb19b08cac58d67ff553cff42dcadbea9aba56a20/multidict-6.8.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0db3a4d1e264e225037a6023888972c25206a96e016021a5bea41c9a939f2a9", size = 48815, upload-time = "2026-09-09T13:54:23.986Z" },
{ url = "https://files.pythonhosted.org/packages/18/e3/e1c6e9c3818c34b782f23ce5fdba3eaa34ec6750dc53078dfac80fa59be7/multidict-6.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:27747162712e85c84598d364425dbf1714ff335bdb6ba3171c4e5081196e8916", size = 83484, upload-time = "2026-09-09T13:54:25.674Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a0/c23f78a4badee9a5b3e760495c661c62a92c340a1dfd00f829cd16e256bb/multidict-6.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:847d6082ae694dc95e548acb201bc100e1cfa96513bc71fdcb86f709dad6c435", size = 50763, upload-time = "2026-09-09T13:54:27.135Z" },
{ url = "https://files.pythonhosted.org/packages/c4/fe/db552d402a3f6b650f5d3ae11b82b93833836aebb51bcda22d8691121129/multidict-6.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:88a6df88567680504ae28bfa7a1f2f64243d91e79a40b2c92ef42efc531e23da", size = 49029, upload-time = "2026-09-09T13:54:28.483Z" },
{ url = "https://files.pythonhosted.org/packages/01/b4/546853fba19dcef77cdf91fc173faf0b02284a49106cf250511166b4ec5c/multidict-6.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:560b211fc3bd4a1e1c6de44f6d38113bf5b410dfc89a4c0d2a3c0edbf1a0dfb8", size = 278863, upload-time = "2026-09-09T13:54:30.145Z" },
{ url = "https://files.pythonhosted.org/packages/ee/3f/4b52dac7db547936eb762123ac1d99df23f92fdb358bae600e322f611247/multidict-6.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202436df907c15adbb94360296c425ea53cf8968a5d2cff9b5b9790ae1972b33", size = 283915, upload-time = "2026-09-09T13:54:31.937Z" },
{ url = "https://files.pythonhosted.org/packages/fd/6e/c0dfbf170e49a91bcb9ce850d51cb98357f3033c5227529200ca7625853e/multidict-6.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c46a08bf070d6849fed483e9d9833f9d06aecb8382ed985be0b38508b3ae958e", size = 260704, upload-time = "2026-09-09T13:54:33.529Z" },
{ url = "https://files.pythonhosted.org/packages/91/02/56973a060ab8dfc2e80bb6797682f6577aff7123cdb1de1a568670ae3499/multidict-6.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cd560498ae8e1bcc955643c1d78eb8e338226d07a983c656ea8c4443d3eec0f", size = 290243, upload-time = "2026-09-09T13:54:35.408Z" },
{ url = "https://files.pythonhosted.org/packages/d5/67/69112989f131bdea4a87b74e82cb0a2daf37880cd92b0e6f0420020adceb/multidict-6.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:758233648ac47b07c575224c4eadd73c8929c3b4c31e2afcfea935fde1cda735", size = 291131, upload-time = "2026-09-09T13:54:37.205Z" },
{ url = "https://files.pythonhosted.org/packages/c2/75/9435f68b0cfc442d4917de85c26f2b2e1292630883414a25576083fa2469/multidict-6.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:122adc7c46ac1e31ecfc7f81b2530533dccafdba70f5d741649f87e336c63384", size = 287551, upload-time = "2026-09-09T13:54:38.835Z" },
{ url = "https://files.pythonhosted.org/packages/13/08/2ee4838081d6587849611aa7ec722c4cb2469e912fd0eaee980e7bac064c/multidict-6.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8125e60f3c70e323ac07dd8b3635f7b3bbc5c3a9ac04ae5988f668ff7ae28a18", size = 254591, upload-time = "2026-09-09T13:54:40.806Z" },
{ url = "https://files.pythonhosted.org/packages/94/f1/05673b51191f77f4198b8e4b35f16ea71c0300c72ca8aa027a66a61b6edc/multidict-6.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:83ff054b04915be5c15680da6c6012474a2cc2bf534129a0e8c6a99f17ba7238", size = 278204, upload-time = "2026-09-09T13:54:42.672Z" },
{ url = "https://files.pythonhosted.org/packages/45/4f/b6cf74322b3fbd3e011a1e903730191922291a7779f6d404114c2189b806/multidict-6.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:930c6058047410e3edff445f5a6e4457f2e089042dede00e2d18ce06f3ceae2e", size = 275600, upload-time = "2026-09-09T13:54:44.348Z" },
{ url = "https://files.pythonhosted.org/packages/1b/ab/958bbb04377159ff03c7314cd9d8a48dd6fc4f78c840589c22ab155ee9c7/multidict-6.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13e26f59f0eecfc5f67c663ad550ffdaf62c0f657547cde387f6c86af1c9449e", size = 279793, upload-time = "2026-09-09T13:54:46.086Z" },
{ url = "https://files.pythonhosted.org/packages/a0/3a/706605ab0dfc4179748ee7949829e63c6f14ae28667aceeefaf2c701807f/multidict-6.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd789a294d8e098528be29b2669b83005ce569339f8cef167fc0274c3115c34c", size = 284751, upload-time = "2026-09-09T13:54:47.793Z" },
{ url = "https://files.pythonhosted.org/packages/b3/a5/567e36c013ad023546de633079c6b22101dd43226b193cba00e6399703be/multidict-6.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3126f2a96704505aa4e92a72d6e8a5d7f29d40a987ced8bf69e29d71dfc71fbc", size = 250812, upload-time = "2026-09-09T13:54:49.509Z" },
{ url = "https://files.pythonhosted.org/packages/0d/5f/6b0b64aa0cd346b07831dabaa6ccda0e73014c5df044b68baa763f0f0552/multidict-6.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:23c9ee89967b6a9b4048acb3b93b660ed714ce9c8bf3bbe652959bc120dc02dc", size = 281606, upload-time = "2026-09-09T13:54:51.288Z" },
{ url = "https://files.pythonhosted.org/packages/31/8c/b846b6796f26d496efb07fedef2b69f6de533da32a56f12d236722a96157/multidict-6.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a62e302fc8cd6aa8972207e7e951d1fdee7c1dda18568305041d19f0e2c00f5", size = 281733, upload-time = "2026-09-09T13:54:53.05Z" },
{ url = "https://files.pythonhosted.org/packages/f0/f3/bf14a39d4af5697fd9404baaf70a0aeeb82d258b95de5cb16b1a7f98ae6f/multidict-6.8.0-cp313-cp313-win32.whl", hash = "sha256:093167d22a8c95af30f597b8a5686f20a14512989942d4be804d119899caca20", size = 47738, upload-time = "2026-09-09T13:54:54.676Z" },
{ url = "https://files.pythonhosted.org/packages/19/0a/598511a5741a3cb374971b3b02eda8a09896118ba528a54795f7e7e8bfb4/multidict-6.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:f25b61a708bd276e8cbb6afcbbf1b8e793a3be70ba0a842d0b8692020f83b706", size = 51609, upload-time = "2026-09-09T13:54:56.38Z" },
{ url = "https://files.pythonhosted.org/packages/fd/b7/6f5c1bd4ffe42d4a6db0f2f65491d4088e9c25c990358fb31a614621d664/multidict-6.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb36381e1f9f9d06eba2f10bdd438e5d20c07d5b55e1a3eee30b9f44cbf52316", size = 48280, upload-time = "2026-09-09T13:54:58.03Z" },
{ url = "https://files.pythonhosted.org/packages/ab/85/153341590e233a967c1d6791a83402d01693dec0f4c1f695606ef16c7ed2/multidict-6.8.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:f8b09b25e0f4dc2ea9e2adbb1cc3ba11a94d6fa3dd978ae659c8743052e1afbc", size = 53758, upload-time = "2026-09-09T13:54:59.563Z" },
{ url = "https://files.pythonhosted.org/packages/fd/ff/44f72d516ece0398683ef52061797d83a74b16b8c1e4587408e97959d783/multidict-6.8.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1f57c414be82490bc0e0305fdb834186229b2d9b6a35fa0afd1eb1a772d125ab", size = 47495, upload-time = "2026-09-09T13:55:01.382Z" },
{ url = "https://files.pythonhosted.org/packages/50/5f/6e118f761b024dd35d26c2fe7ba41572bb0e8ac5f8cfccbbcbc2ff76da4e/multidict-6.8.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:00be37bde741bf60871082cd347a093218c44886e99231b7516671c70f2c280d", size = 48540, upload-time = "2026-09-09T13:55:02.989Z" },
{ url = "https://files.pythonhosted.org/packages/e8/4b/3eed744491b32f0e318e7db89dc06858732362f706e8d045fa9ab51a343a/multidict-6.8.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e37b744849fb631bb52e3dadde35ffeee365a6c41cf71257b5b7acc9cd83fd38", size = 83130, upload-time = "2026-09-09T13:55:04.554Z" },
{ url = "https://files.pythonhosted.org/packages/6d/de/95c2c0ddcccb9a41ffbaa5df8ea059a8ff81916b7617a8847ecd89ed8061/multidict-6.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c2b2a96cf1dd99fe7867be4c013314225f4d5786e6685906e29932d42aca6f11", size = 50574, upload-time = "2026-09-09T13:55:06.387Z" },
{ url = "https://files.pythonhosted.org/packages/f5/b7/f4f4989594f99bc121ad9277090c4e49819b08ab1a96e132b628a9e10b7d/multidict-6.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bea7df027015856ba5d0a88e3b4777ff8cb5c66b58fc108050fe79d4dd9d4d2d", size = 48786, upload-time = "2026-09-09T13:55:08.131Z" },
{ url = "https://files.pythonhosted.org/packages/b2/86/f1d86a0222f31fb3df8eef3d6c9abf7e8d65d49edd8d0d7e7afaf23d23cc/multidict-6.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d3da668e903c934ed0b587ecacfed6901f6ae6384a6e975887592b61845e78bc", size = 276670, upload-time = "2026-09-09T13:55:09.803Z" },
{ url = "https://files.pythonhosted.org/packages/03/50/6945c50f86a978b2bcace9ca344165ff80883be47d984489bbba8fa0ab20/multidict-6.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64eaeda36ee8d88f9e8616a587a8c66a663283cf6e0dcf013c1ddd8c758e4aef", size = 279339, upload-time = "2026-09-09T13:55:11.685Z" },
{ url = "https://files.pythonhosted.org/packages/ee/2c/e649889ba23fd1f4442a85427b99d9e6261226b2ac31914aa7f5b241d947/multidict-6.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ac746cb365bac1c462da9e3e6ab8904a8efe2217a56b0b2e3d9480f41d2b2602", size = 252549, upload-time = "2026-09-09T13:55:13.527Z" },
{ url = "https://files.pythonhosted.org/packages/e5/f8/1023b66e011b1395fb160dabb0f0608ef67e569f0bdb2c1d5ac9b2f2adc6/multidict-6.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:18f0e06360c3e451a3ab800355773c8d125a758238d780c800b0ee5e90ee903c", size = 286203, upload-time = "2026-09-09T13:55:15.19Z" },
{ url = "https://files.pythonhosted.org/packages/7e/6c/48aea545cbda6d0444848ec23d988c13b86538a00a1b7d3868cc2382ff94/multidict-6.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69708fecaa88bcb2341397b49fc95057a835b02a3670c551b37f95dd79e64e3a", size = 285039, upload-time = "2026-09-09T13:55:16.928Z" },
{ url = "https://files.pythonhosted.org/packages/68/2a/066123b17291671bf67d2a5c65ee81a48de53913bd1b1578791519eacdb0/multidict-6.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9606f583e7acaf61e7b3f56074e14037b9af7cb194590edfc0114b3ae5931ff7", size = 281075, upload-time = "2026-09-09T13:55:19.155Z" },
{ url = "https://files.pythonhosted.org/packages/47/20/4f0b2c485da2e8a659cc677717a3745872918c9c85064491a1ef75d7a3bf/multidict-6.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1f66fe6a021173d0d47968491791966b9f3e6d61115f2491744aa0c07a6e67af", size = 250431, upload-time = "2026-09-09T13:55:21.07Z" },
{ url = "https://files.pythonhosted.org/packages/ff/c7/b9a288901577aa0b82c33c64d52246c88076d260ad7b6c16b021ca0f8e99/multidict-6.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:be007d1aee2cbd530347dcafedb400891a3b5f1bd7135f95cf5d5b330b5219ee", size = 273891, upload-time = "2026-09-09T13:55:22.887Z" },
{ url = "https://files.pythonhosted.org/packages/da/51/0ba50cab2cfd067988de2abb73f23076ac727fe18d03f1368a59def64727/multidict-6.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8457aff3c12a89a8e1c4674de5c777857fbc429f40fe117a3d29538547cbc364", size = 265262, upload-time = "2026-09-09T13:55:24.77Z" },
{ url = "https://files.pythonhosted.org/packages/0f/d6/e5be1117dbca6eb9ce231142b7e20599418bb3500147db51bf844ce8afcb/multidict-6.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:95c27b4f3f04320fc44e338573f40c5c956b504a7fcf081a157fd0b02579311c", size = 278033, upload-time = "2026-09-09T13:55:26.67Z" },
{ url = "https://files.pythonhosted.org/packages/d2/28/cad0afaec3caa56ea2c1ceed43c164d62ad3e83e950daf0d0c87bcf9dca7/multidict-6.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d244cf6b52b5ba1c34c3832f4652a668ebb36d95949b96eed9a1c54d916a90dd", size = 281717, upload-time = "2026-09-09T13:55:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d2/025702df0b69b856db70a4d66f77622f51c3d99771ec9a07f3ca80f7e098/multidict-6.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5bbbb696c8024475b1877d14ce20d5f1cc05b8f6d786cea0fe3aa7fedc02e891", size = 247124, upload-time = "2026-09-09T13:55:30.497Z" },
{ url = "https://files.pythonhosted.org/packages/b4/96/9dddca563f06a921956389c0bc9b894355b98b0bdf62299e2560c50afb6d/multidict-6.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:cbd86f9787c5e2f5fd27d8b21458222f107347c6731c4e93dde68f554b466a2d", size = 275954, upload-time = "2026-09-09T13:55:32.57Z" },
{ url = "https://files.pythonhosted.org/packages/ec/91/8b2f1f2a774a955665f268340a2b59db7020c5f12baac02ae9ef1b1660cf/multidict-6.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2f8a4b0b4d639d525928c7f30de527bfdf9ead6e44a5e8cb9c50aced5e4590cb", size = 275508, upload-time = "2026-09-09T13:55:34.368Z" },
{ url = "https://files.pythonhosted.org/packages/b6/1a/e2cabdfc0880a61a99d2b8bc361035036fb5a2c6af31ea3fa054ba1065c5/multidict-6.8.0-cp314-cp314-win32.whl", hash = "sha256:8890c89d662560e51c55ac1304d6f919b23942abe9ae1127cb1de9aa6132fa52", size = 46938, upload-time = "2026-09-09T13:55:36.057Z" },
{ url = "https://files.pythonhosted.org/packages/b9/7c/11234bcba62c22a58f2ba168499cfe3531f49de3edd5090d04a8c6cdc936/multidict-6.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:45cc39ba50fb0754a4359b90f8229ae08598fe2266abe3521b4e5a9ba916534a", size = 50291, upload-time = "2026-09-09T13:55:37.698Z" },
{ url = "https://files.pythonhosted.org/packages/ab/61/793668439df924752a8137d6db0de97ed1add494779b01e4764dfc60571b/multidict-6.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d0264f8d5cb0a803f650a6a8572dfa0cd1e099a2234c588dc8fb220b415b865f", size = 47622, upload-time = "2026-09-09T13:55:39.335Z" },
{ url = "https://files.pythonhosted.org/packages/9c/b8/3c091b929e6b5b2f6e0eba2232178e76d4503c8b96b92dfc281ff1d823be/multidict-6.8.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1969971900b0871530f9b62280dcc2d75688e74d2a69262bc01faf2b96c78f04", size = 88789, upload-time = "2026-09-09T13:55:41.086Z" },
{ url = "https://files.pythonhosted.org/packages/9e/d7/3df83fab22dd64615db71e3b3cc1346b581d1459719637ce52144f9f6558/multidict-6.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8180b635290a75af8478f1b3e9810135381ae24833293fe77b85c1c21ff842ab", size = 53399, upload-time = "2026-09-09T13:55:42.685Z" },
{ url = "https://files.pythonhosted.org/packages/2d/78/41bd04c04b0aed16540c4856c9e012afc1c254298da154398308df05e26a/multidict-6.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4261863fc8b5ab1b815ede94e592e94c6af5b04616014929057e61859e7382a9", size = 51597, upload-time = "2026-09-09T13:55:44.569Z" },
{ url = "https://files.pythonhosted.org/packages/2f/6b/7bc4cdddf624e1e7e0231734b1331729ea46df10d7c8fd3fce79756e7d0e/multidict-6.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0b143d53590e89f43153d81d505a8448d4d57354354385aef8a51d67ffefa27e", size = 264391, upload-time = "2026-09-09T13:55:46.548Z" },
{ url = "https://files.pythonhosted.org/packages/dc/b6/d2a946e5938771e92c39354563e535ef6bc6dfe399dd4307c6df8dfea183/multidict-6.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da1c112c5784ccd9d32cd90be6739fee32644e874eff6ae8f0497cba3e352e58", size = 264680, upload-time = "2026-09-09T13:55:49.915Z" },
{ url = "https://files.pythonhosted.org/packages/33/6b/3f9e981c42e7eb9329918523f0f9362ceb0ac3ee0ee1165c28f674249d75/multidict-6.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f7eefd0233a7c33ca980a5cfef26f1e9b5e2137839e752a99963696729f12d91", size = 235420, upload-time = "2026-09-09T13:55:51.92Z" },
{ url = "https://files.pythonhosted.org/packages/bc/e1/a3a33a039fb6d381800ae5d1d587b697b8c27fcdfe48819420f08703acba/multidict-6.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:348bb85e2038b40c007383616d73f734869063772372519549ebd7da1723d1a4", size = 270309, upload-time = "2026-09-09T13:55:54.023Z" },
{ url = "https://files.pythonhosted.org/packages/95/5d/8b06724a957f2e480f159b9550988a67810fbe9555a09c5f6a2a4b829607/multidict-6.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:095f62ea4e7a3be2f6c567ab695ce10e950f2adb905c1bec82281593e0b2d2ad", size = 275169, upload-time = "2026-09-09T13:55:55.948Z" },
{ url = "https://files.pythonhosted.org/packages/ab/32/8f3dfe2ffa5d0df2a95f71e63c2f11fe3b5e1771f26ef73bb1af84de83f8/multidict-6.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be569fff1d85cd29391c431c5641c8772acb75bbdc61e60a8e82fceb9023d385", size = 264900, upload-time = "2026-09-09T13:55:57.803Z" },
{ url = "https://files.pythonhosted.org/packages/6b/73/d5829fc00a055d6ab445e0876346ee9cdee670766cd4190dc0a496188c0f/multidict-6.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3533a03e4e789baf6a286e7b0b1b6da3f3d7c3eab569686ee29ee1d8b52e2cb4", size = 242486, upload-time = "2026-09-09T13:56:00.002Z" },
{ url = "https://files.pythonhosted.org/packages/b7/58/e8d7874038e31e0533182d1c3c5331a856b9c849a71bb26a21850e8c91e1/multidict-6.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1bdb9b8fba5a9aef673ec90db3f55b1ce743f2fbdea4d37dc04d14ccdfc153ff", size = 259916, upload-time = "2026-09-09T13:56:01.802Z" },
{ url = "https://files.pythonhosted.org/packages/4d/f8/1b56a7401acda20efc016440f4fad3bef66c4aee54ca080ec143881ebb0d/multidict-6.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8d7b66c9e09c0bb0add2b5895e646b62a0849e71155066f215523de6b95cbe6", size = 251209, upload-time = "2026-09-09T13:56:03.767Z" },
{ url = "https://files.pythonhosted.org/packages/bd/5b/68d67a9e302b0645a747ba910c30eb41f2834fcdc1d85f53eae2dfceee0a/multidict-6.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:563d6500ca80dac7bba6f48a78e0ffd87e21a7d4d24642c6503a2ddccd70c110", size = 264505, upload-time = "2026-09-09T13:56:05.795Z" },
{ url = "https://files.pythonhosted.org/packages/18/13/4dc304ba2c5f5307b474ab2ce1ed1f6b02b0b4e233c182e3981ed436c2e3/multidict-6.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:346ac52e56bcda320c0dcdfdd081947ed7cada33afea4e2284bef7b0733bff9b", size = 264916, upload-time = "2026-09-09T13:56:09.079Z" },
{ url = "https://files.pythonhosted.org/packages/dc/0f/7b1f729d18369915009185201be5d0b8df0e525340fe6a600d2f8441d6cf/multidict-6.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4ee953a5ebaeed38dc21cc032ed17a9d9782802e00042200497ab4b01b0bf7c0", size = 236839, upload-time = "2026-09-09T13:56:11.273Z" },
{ url = "https://files.pythonhosted.org/packages/22/d1/eba1b88b18b7019d9136303fe77909257c40fabde5aaf138a4d900b6ce3c/multidict-6.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2f79cc3e8039a8cf5c77e0811b0807953fd52d0863b9b76970b20d696dc64a78", size = 265307, upload-time = "2026-09-09T13:56:13.379Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a6/6c1e4106faa27118ac612f4d664eaf909de252634785286262a627108e58/multidict-6.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43a4b56555bbcf8af161e7c7682bd93eec10f068c95844511864c018c8e5e13b", size = 259041, upload-time = "2026-09-09T13:56:15.666Z" },
{ url = "https://files.pythonhosted.org/packages/c0/bc/ecfb8b6faa8e158a71b03bdf7f947f30e0bc5d899cc357573a76ab7bb1e5/multidict-6.8.0-cp314-cp314t-win32.whl", hash = "sha256:48ea524a25a1cd5972cf293bc95713918cba0bcd6fa9b992d906c857c546abe2", size = 50628, upload-time = "2026-09-09T13:56:17.837Z" },
{ url = "https://files.pythonhosted.org/packages/30/7f/e27fb699b70ad24dbd02ddee604658acb36f907c03c045baffe4ea774501/multidict-6.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d0be2b832435001bc623ca7f1499ca1a853d4f082fb61221a80ce71132f50b26", size = 55592, upload-time = "2026-09-09T13:56:19.652Z" },
{ url = "https://files.pythonhosted.org/packages/eb/7a/76de70b2f6733696803f1ee56abe44a3757a52777383032c7373d3fea0f4/multidict-6.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:62b8e291a4f7edbf7cde7a43d831d893ba443a1b627498b53581943b0e348feb", size = 50300, upload-time = "2026-09-09T13:56:21.516Z" },
{ url = "https://files.pythonhosted.org/packages/ce/32/4de7320ae032dc768090d11f708d2d386df3db04cb6b8b0db0230cfc66c3/multidict-6.8.0-cp315-cp315-android_24_x86_64.whl", hash = "sha256:e192018b732f7b168e6604cbdf40fa8e05c996693b9eb445a0d8a73f4b77c5d3", size = 53761, upload-time = "2026-09-09T13:56:23.192Z" },
{ url = "https://files.pythonhosted.org/packages/5c/45/ecb641309dc2cdc6040f18e22c68eb5e94398f9404c4365d810f4292e053/multidict-6.8.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b25426f9f6ed402835617c8f23609a47045f91ecff365eb6734817e039a8ed25", size = 47505, upload-time = "2026-09-09T13:56:24.902Z" },
{ url = "https://files.pythonhosted.org/packages/eb/68/87d6161b9fef11943e0b894203da3fff561933ca3c9b2952b6e7100e9c9f/multidict-6.8.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:fa6c2880709c84457de104385b704fc28860f27e442ad13966fc4af8e714fe9c", size = 48549, upload-time = "2026-09-09T13:56:26.574Z" },
{ url = "https://files.pythonhosted.org/packages/97/f7/d852d2276407640cdbd29fe11cac6e93f70f59542cba174ef9d146738946/multidict-6.8.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:eabb03dc3e4ed6333ecd1cc9826ec80e7a98b5506deeb832d7260c8e44166d23", size = 83157, upload-time = "2026-09-09T13:56:28.227Z" },
{ url = "https://files.pythonhosted.org/packages/14/e3/16fe7ffa6090591d83cf6bc2486e77ce891705fb6d0191823140928311b5/multidict-6.8.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:59e539c4eb4d3a53b0e630a6ba2b2f2824732b5e73f90e30a280f12fde157b15", size = 50578, upload-time = "2026-09-09T13:56:30Z" },
{ url = "https://files.pythonhosted.org/packages/d0/0c/e38e41c1087a599f86ff58a01f358abf7c4db3c26a3e90eebb3e02193ef1/multidict-6.8.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:835d5a90b11d1f5f8200ff3cc8316bded76eebebc92436398947a27657e645e7", size = 48815, upload-time = "2026-09-09T13:56:32.056Z" },
{ url = "https://files.pythonhosted.org/packages/d3/f0/eb691f42af8e7775992f57904ec75dc356fc7cdc896e5f30879decdd26f2/multidict-6.8.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d2d236b8a44ae91536a12ebcb996bdb31cf27425f36b4d05c87f2ba2716050ba", size = 274804, upload-time = "2026-09-09T13:56:36.741Z" },
{ url = "https://files.pythonhosted.org/packages/87/05/28472ccfeb43c00a043c0385ca4294da21a5957859fb7860e2ebdb3e3011/multidict-6.8.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb9a60b7faa5d37c426fa91cf4d6738182a1f2755b9fab7c9c64cd466c4ce51e", size = 279693, upload-time = "2026-09-09T13:56:38.531Z" },
{ url = "https://files.pythonhosted.org/packages/f3/a1/2b4fe73e5fecff807b47650a155c391a103136428cb21d6ba8e39c5912b5/multidict-6.8.0-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ef606c15cac6c90279acf34120784b6f36662cbf382defd3955cd8f1115336b", size = 254969, upload-time = "2026-09-09T13:56:40.407Z" },
{ url = "https://files.pythonhosted.org/packages/44/e0/c97d1822783dfe52e02fd150fa3f02eb22410211a9e2615f71541803ed4b/multidict-6.8.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:68186a2d4051c8ffd17be33553bea2ec9bbc8ef860fe2980a221d96126296f31", size = 286392, upload-time = "2026-09-09T13:56:42.234Z" },
{ url = "https://files.pythonhosted.org/packages/2f/d9/772f1339e1d051236bcc137b0eac2b4aaaa0bbb56aaf924e9aaba901d9c1/multidict-6.8.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2cc66abb85e2108c9ff8a1c0d20fa260bf690bbb33caef4ff3ecb2c2cbdfff5d", size = 285348, upload-time = "2026-09-09T13:56:44.255Z" },
{ url = "https://files.pythonhosted.org/packages/06/ae/cd045747e4680362e02955a82c468e95b5e4d319e3a79574b3fb677de568/multidict-6.8.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69b3e519a132bb943b0daae15fc8c2168706b17f826481d32a32a5e784b129e3", size = 282721, upload-time = "2026-09-09T13:56:46.088Z" },
{ url = "https://files.pythonhosted.org/packages/35/14/0802d9a3aae4ef21eaa39adbd729a380fa095932105e1424e417b53e783f/multidict-6.8.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e41226ecf607f062fe34a2f4cf64ad3a89e3a0180dc800b463b6b14c06dd10dc", size = 253168, upload-time = "2026-09-09T13:56:48.07Z" },
{ url = "https://files.pythonhosted.org/packages/b1/64/3f92298bab8fbe1332e708863fb55b66e755be6f416b3459720d48b33af9/multidict-6.8.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:610c7637bc36b90f39e6c66f710f93d57018f83d53e1e187caaa218c6892b95f", size = 274209, upload-time = "2026-09-09T13:56:50.023Z" },
{ url = "https://files.pythonhosted.org/packages/08/c2/2001ac0eac1a8b7390a5902d7115f66d4f256268057a502200b6ab12dad7/multidict-6.8.0-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:65c85c79f5a2c04fbbc18f006c014674dc5fdf270cb978d8862c82c6f694e60c", size = 268044, upload-time = "2026-09-09T13:56:52.033Z" },
{ url = "https://files.pythonhosted.org/packages/0d/90/78a9e26c85f89abd562a67f7fcbaef9007fd5c37bb9efac19f1cf604e7c2/multidict-6.8.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:628ff11e6720f90acd0c305dfa3339f04a783a20de8cda6ac333ba46447261e8", size = 274806, upload-time = "2026-09-09T13:56:53.975Z" },
{ url = "https://files.pythonhosted.org/packages/3d/71/713bd445421b21531234c1f3630b768192cb9d80c8b1c5b05c5b505ff4c0/multidict-6.8.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:0935971bffd0b479fc90c4811ca787703e93fcb6afea939a375dfc80285ab368", size = 281890, upload-time = "2026-09-09T13:56:55.848Z" },
{ url = "https://files.pythonhosted.org/packages/de/a5/1387c538663e2dc8c27bbc7cd6955cb66de0f55c780cf7cd0fc06a1a16ca/multidict-6.8.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:9442b14eec262a1f74369bbd07e75bc5155105164649a4b9fbc1ebc7b8fb0b14", size = 249749, upload-time = "2026-09-09T13:56:58.01Z" },
{ url = "https://files.pythonhosted.org/packages/91/15/104296c9d70896b9759ce0812aa4899fab76d8b16bb32dcc5a78ab547c89/multidict-6.8.0-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:397599503b718f0137f26d3f6532d6955069cd2e5917c47ef581495bc2529ff8", size = 276138, upload-time = "2026-09-09T13:57:03.591Z" },
{ url = "https://files.pythonhosted.org/packages/f7/0a/f2a0c2658e9d7ff5964ec2820a02054558636fafd663230ddc8310b8ed39/multidict-6.8.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9e37024b41d7a7e7e9cce14b248d54707c21c2a2ea30a47b71bdcefcafec00f2", size = 277077, upload-time = "2026-09-09T13:57:06.024Z" },
{ url = "https://files.pythonhosted.org/packages/98/50/bc46566caffba5c1c4a510519156371edf7c4ecd35c9ef917d0c1803487d/multidict-6.8.0-cp315-cp315-win32.whl", hash = "sha256:071da134651b04a8507dfb331ac0988f376337c2aea59486bf20989fb5b5a64e", size = 46930, upload-time = "2026-09-09T13:57:08.009Z" },
{ url = "https://files.pythonhosted.org/packages/6f/1a/cafb31049ecc1a6ce52bcc69fa436cca239adc057b1718a0c49044848663/multidict-6.8.0-cp315-cp315-win_amd64.whl", hash = "sha256:3bafff8598f0528017ddc74194e5451d5c22d046c98935f8f86247b0f286e4f8", size = 50294, upload-time = "2026-09-09T13:57:09.986Z" },
{ url = "https://files.pythonhosted.org/packages/6b/51/00e037da14cd1d894b123e0bbe62de5c561679a6ab23ab1c009f2965dcda/multidict-6.8.0-cp315-cp315-win_arm64.whl", hash = "sha256:e886ef8c9879105fe4fc99417447b3a5f35d1131412ce839470bd2089fe2043f", size = 47626, upload-time = "2026-09-09T13:57:11.738Z" },
{ url = "https://files.pythonhosted.org/packages/52/f7/aeb947982197e8b4f5c4da3961ee473ea5a050b94a6ff3b88baf64621401/multidict-6.8.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:883284137e25318ed9735b742ae46341a864888fae28e8b6314c4f84da080f08", size = 88801, upload-time = "2026-09-09T13:57:13.957Z" },
{ url = "https://files.pythonhosted.org/packages/35/d8/593948c016c3f850e3cd56a4e0144151eb409d2b8690f0c0ce7f7d33dbea/multidict-6.8.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:ca52b9ec80851366197577154c862c4c4c7036ca76ae94cef5cb59c5cfeab944", size = 53376, upload-time = "2026-09-09T13:57:15.94Z" },
{ url = "https://files.pythonhosted.org/packages/58/b9/097a05bca533027c0477b6a90bf927dbbb4b23cc9090bbb37a2e972af8d5/multidict-6.8.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:91fa75d0a693832106d98f66c849f034f21c828d14437f1fb97d3784aab89e84", size = 51629, upload-time = "2026-09-09T13:57:17.685Z" },
{ url = "https://files.pythonhosted.org/packages/fe/07/938ed21967f12380d0b8861645fb65a942f3669e31d5163ed94d23103b61/multidict-6.8.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:307c1acd812fe897e7fbe10c6758822e8c04be4e7c60a9f54901cdf8b5ab8bc3", size = 261967, upload-time = "2026-09-09T13:57:19.752Z" },
{ url = "https://files.pythonhosted.org/packages/89/e8/e66bf843fd29c01712dde9edeb9f4ad0ffab06ab4ada4b721ad7bc73b3d5/multidict-6.8.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf684986a2490628f059a99dd107b566a2d34cf947f8eb8387e0500a1f90c5", size = 265923, upload-time = "2026-09-09T13:57:21.784Z" },
{ url = "https://files.pythonhosted.org/packages/8a/f8/e9be849b225af28a8eee2c6bfea23594a777c753fe97e2ff7e2180c8935a/multidict-6.8.0-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:77745725125d01fd613b6db043362aa7c6bfbfdb23d45dbfc3d92bf58160af62", size = 239380, upload-time = "2026-09-09T13:57:24.3Z" },
{ url = "https://files.pythonhosted.org/packages/ab/67/4dbad08f5081978c591afae9e836ec9ddae90e9e76be6d6ce10757483dc4/multidict-6.8.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8daafaa0b2eb43f76898ced78b1e0fb91b38c4fa50da516c18067f2a2d578c20", size = 271591, upload-time = "2026-09-09T13:57:26.611Z" },
{ url = "https://files.pythonhosted.org/packages/92/3f/e9c97222d7e104e54e556f118ec7d091ab41a0c10c630f2b97e5b43f5404/multidict-6.8.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c68e0c0649d17c2d0339e3674e86a4aeba4a7e6b21c1e394cf947a95433b31d0", size = 276091, upload-time = "2026-09-09T13:57:28.997Z" },
{ url = "https://files.pythonhosted.org/packages/26/ca/728e7ce05ac9c0303554e7162e74d91fe49e65bad7dfbb377f783dd32c0a/multidict-6.8.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8a5ac357ac283490a8d1899b0383355fd1f8634b14ba0d59e4c0dd97db85556", size = 266493, upload-time = "2026-09-09T13:57:31.256Z" },
{ url = "https://files.pythonhosted.org/packages/8f/74/7c658d2769863af16fb7d7c6be50b29659892a06a632858863eee3a31842/multidict-6.8.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:46029e6e27a3ec0dc55b53f58df82d10f04c5e111f78248279b530bedad2c30a", size = 245302, upload-time = "2026-09-09T13:57:33.464Z" },
{ url = "https://files.pythonhosted.org/packages/2d/2c/d4350a20a0e8c66a447d694e8713438262665203fe826c3f4e385f052b72/multidict-6.8.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:8d1046b5427dcafe6e8a0e07527dd74f1ee694006160162f53f3a17f15aad3b4", size = 261016, upload-time = "2026-09-09T13:57:35.651Z" },
{ url = "https://files.pythonhosted.org/packages/1f/78/83df999c8beb72a012cfac42f2b833c4a48f8e836fd4407747b355a2430e/multidict-6.8.0-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:f1f4a220db6ed7c8fd16b6d644ffd1f082651693204daf3275e049fadc849e39", size = 255021, upload-time = "2026-09-09T13:57:37.758Z" },
{ url = "https://files.pythonhosted.org/packages/fb/13/f2c0a2dac6d91f74aa124f3e9f07ec497ceae5ed2df2753d249601cd7262/multidict-6.8.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:029897732a9c798737457e382bf84e8c64237eff224a90aea2639f4413c45e4e", size = 263066, upload-time = "2026-09-09T13:57:39.897Z" },
{ url = "https://files.pythonhosted.org/packages/59/1d/730008d4639ace731bbb1399e1ac13cbdf506f7d6fb861d75044ffb3994d/multidict-6.8.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:29be9fd289e9ab8f480996ea2f686e1654b80242033843cb11691688329423f1", size = 266510, upload-time = "2026-09-09T13:57:42.39Z" },
{ url = "https://files.pythonhosted.org/packages/ab/ca/bec67a5d206dc5748e50c93f6f71deec14305c3657cfe250c3887caf7839/multidict-6.8.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:29631224698de1e42abc8fa7658d830e0aed0029785144b5832b695da5adef2f", size = 239423, upload-time = "2026-09-09T13:57:44.304Z" },
{ url = "https://files.pythonhosted.org/packages/9e/db/5f153fe51fbac7d80f3bb8bd6fab8db8b6cd061e7a11371676dfed3712bc/multidict-6.8.0-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:962f18c59a000f30b084ea2e6b8001521bb315efd4e5f10acf9fb36f366b7882", size = 266902, upload-time = "2026-09-09T13:57:46.297Z" },
{ url = "https://files.pythonhosted.org/packages/89/0f/9efca48a351551de4dc0c183f523109dbe87c645a4732d5f1c70b4880dca/multidict-6.8.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c60e50bc5b07faac92fd3a20fa21cc8cf3e3f7204d2867b206c73293ebc19101", size = 260887, upload-time = "2026-09-09T13:57:48.268Z" },
{ url = "https://files.pythonhosted.org/packages/df/d8/bb879a62e0809448e53f6237e71670066ecf3bbc5896a7a6705b6628d86a/multidict-6.8.0-cp315-cp315t-win32.whl", hash = "sha256:fc5460940f50dff00731b4132366840ba9685286ea88ea104b661899084f3fea", size = 50533, upload-time = "2026-09-09T13:57:50.31Z" },
{ url = "https://files.pythonhosted.org/packages/fe/62/3e5308d8871636e4b9620e4b3acfcf2b5caf79b19d317690ec13f7fc8b57/multidict-6.8.0-cp315-cp315t-win_amd64.whl", hash = "sha256:b367c342327717d644db4c0ddb37ceb655c84822215ea0773a3a36911b74b71d", size = 55572, upload-time = "2026-09-09T13:57:52.301Z" },
{ url = "https://files.pythonhosted.org/packages/b9/cc/d3c10e10ee3bb7a7b4abbb3157306b2ce7e0018c9c2d16b32b468739d2b7/multidict-6.8.0-cp315-cp315t-win_arm64.whl", hash = "sha256:0c1c4debad7337627b86837abdf0237ca3cb3d7e17de7eab0177c263878546d4", size = 50322, upload-time = "2026-09-09T13:57:54.099Z" },
{ url = "https://files.pythonhosted.org/packages/b1/ee/be4e1a4b7a2b27f4fb6936510d4bebcb41b0562c946930ad26916e069cf9/multidict-6.8.0-py3-none-any.whl", hash = "sha256:75daa15ca16d6285eb2e104b2f05ee6f8d9836c68da3ce5c85f615a0450eed0e", size = 16297, upload-time = "2026-09-09T13:57:56.106Z" },
]
[[package]]
name = "numpy"
version = "2.5.1"
@@ -1168,6 +1523,82 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/02/fb/d65db067a67df7252f18b0cb7420dda84078b9e8bfb375215469c14a50be/pendulum-3.2.0-py3-none-any.whl", hash = "sha256:f3a9c18a89b4d9ef39c5fa6a78722aaff8d5be2597c129a3b16b9f40a561acf3", size = 114111, upload-time = "2026-01-30T11:22:22.361Z" },
]
[[package]]
name = "pillow"
version = "13.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ce/a4/9cc30690a394a8305e8be4c9036aa16d0dfddadd09079860b582f562496d/pillow-13.0.0.tar.gz", hash = "sha256:cdcd1ba258178f7356d74170c9a81ba4eab6a5d55748bc402a5a2a6fe6be3510", size = 47058631, upload-time = "2026-10-16T22:18:09.718Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/b8/58d79332229482eb0d27c82973850808c19d37e5bdc14443d3417bd373f5/pillow-13.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:67c8d44ad89fa7eb88de5d6ef6199b1e03dd24974f85a67639fc8badb801fe79", size = 5568643, upload-time = "2026-10-16T22:15:44.474Z" },
{ url = "https://files.pythonhosted.org/packages/39/4b/f2ed53d83aa44ac19599ac0a54897f867fafb95f406980d1e24083f8cbe3/pillow-13.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc481f742ccda20e9ed760b8f978dff50f2a4e9bc253ac167fb0a05b0783ef52", size = 4852329, upload-time = "2026-10-16T22:15:46.284Z" },
{ url = "https://files.pythonhosted.org/packages/3b/bb/9e30e8ed18c20800ee327a12107d5249ad8dc444867b3adfcc21654e3c71/pillow-13.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8bf295fe459343bc6093c8e5e0df60bfa189e0fcb775a22e2c2488fdeccc6c0d", size = 6519309, upload-time = "2026-10-16T22:15:48.14Z" },
{ url = "https://files.pythonhosted.org/packages/e6/99/fdf65cc539e266ab93546c261bdf98411fe165c6e4e13f9dbf597097192b/pillow-13.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42d63be3af4f9b951b199b0a9d23365d37ab14687e6d77b74940432c8515c4ca", size = 7144542, upload-time = "2026-10-16T22:15:50.093Z" },
{ url = "https://files.pythonhosted.org/packages/26/9b/f38b7c4d8f599537e2d1a6d546cec9beef7536ff8f5c49a3f9c78179d8ac/pillow-13.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bd024d567eca931b668978d672dba914d4b8e2628e2c3073a2ad178de8a2620", size = 6618042, upload-time = "2026-10-16T22:15:52.054Z" },
{ url = "https://files.pythonhosted.org/packages/a8/7c/45ee28531f89ee4582967d8a17f7a26d71ca64cd902b93c269590cd60a00/pillow-13.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:23edf4ec4b6f4f240e8df6e4985e9ee2ced86474632433e55fb1e2548744bf40", size = 7228137, upload-time = "2026-10-16T22:15:54.009Z" },
{ url = "https://files.pythonhosted.org/packages/01/bc/9c2a969cb1b70201d04fa17fd5aedf02a732870b8da86bceaaa30b0ca385/pillow-13.0.0-cp312-cp312-win32.whl", hash = "sha256:02464da86f561f410e13b061ec37c3068a9cb557a095494157742d07b3c24a27", size = 6515423, upload-time = "2026-10-16T22:15:56.25Z" },
{ url = "https://files.pythonhosted.org/packages/ff/cd/73a79ecdc822076002eb24b5e1e4a530b0bb5310fcb6d239abbec877110b/pillow-13.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ff3fe9eb621fbb9db4c4be33ab946f6bac5f98ab50ca8d3260503653656c961b", size = 7272166, upload-time = "2026-10-16T22:15:58.597Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a3/be5a1236efd085b3984e4305da8f179ae9dab80c991d2ad78f2bd5c4c843/pillow-13.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3bc8ee0271387e0b1e3ad0ff893645990a656e79bf9c42f4f734a3642b11d51", size = 2698943, upload-time = "2026-10-16T22:16:00.637Z" },
{ url = "https://files.pythonhosted.org/packages/82/e2/9132d38df320e8099e2642d9fa538f18a7dfebf68a875a31725dc2a115aa/pillow-13.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e98b4f91f99b24b1da72e10621ba181f7281922384a7403094debb4199de6c00", size = 4190620, upload-time = "2026-10-16T22:16:02.527Z" },
{ url = "https://files.pythonhosted.org/packages/cb/be/436dac0996876f65ef1916ffb711c5ef4af2ebc57414b6f16642e07f6b47/pillow-13.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:34fb3db1149c879343861831728c243c432d4a5afc8f1878ca7612ef8de2a033", size = 4290286, upload-time = "2026-10-16T22:16:04.861Z" },
{ url = "https://files.pythonhosted.org/packages/60/16/7c1007162e4169ea0d5bec47e2e0b2174f56c50cf5b8c02a1dc23e41037e/pillow-13.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:2d50500a4a3bc6469adcdb319cf05d688b08ca3bf0f22a05339f3b25b433a599", size = 3723169, upload-time = "2026-10-16T22:16:06.956Z" },
{ url = "https://files.pythonhosted.org/packages/08/bc/578ab2eaa04705a8978bd9be0fe15c3d43a8fc95097c4f7e39540e79e4fa/pillow-13.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef5fe6b7db3f501c56000ba8fe7c7e1a35ca1d45824fbeb95a67daa85cff4dbe", size = 5568469, upload-time = "2026-10-16T22:16:08.664Z" },
{ url = "https://files.pythonhosted.org/packages/98/c8/ccede3dcc4f274b80fb49199eed1ef34ba05566c8b71976b9d1f111da2e3/pillow-13.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f11a3c1358bb3c68012a7b1f41b223c3db9f7bfde90b481df028cfe1c3363313", size = 4852187, upload-time = "2026-10-16T22:16:10.693Z" },
{ url = "https://files.pythonhosted.org/packages/fb/3c/cd0770e96d20bfd990db024bcbd43897ff3e88da1f4782cf90ea231b1d4f/pillow-13.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19b66ca0a4d25cc0200b381ce824c7ea090db61427315e73cfb05dbf1b35c369", size = 6515908, upload-time = "2026-10-16T22:16:12.896Z" },
{ url = "https://files.pythonhosted.org/packages/ef/fe/f5cdaa2d1c4086e757430f368c271a58ee29cfe7378e7ae7b9d964015c25/pillow-13.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5f56499c1a74d2b862beb0c33b8016f99d095858455df9d21583bbbc1c15b582", size = 7142544, upload-time = "2026-10-16T22:16:14.844Z" },
{ url = "https://files.pythonhosted.org/packages/fb/b5/a54f6d44adbcd23737c8c12953f42be107e4cca64e247c0ca3a5b563061c/pillow-13.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2a14cd73d5e6c927f644b0110ee81f59c4ef506259ec9183904747f2caacc81", size = 6613097, upload-time = "2026-10-16T22:16:16.893Z" },
{ url = "https://files.pythonhosted.org/packages/e6/b4/9e6c1207c21b2e91db0d15c16d30f3701ce5add1bd4da0e787761e66cb6e/pillow-13.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08b826a9bf1265c76de04a42061d61ea6b109418b7826f28ff1ecdfaa26c5502", size = 7224846, upload-time = "2026-10-16T22:16:19.195Z" },
{ url = "https://files.pythonhosted.org/packages/78/9f/d3c3cb0518f3dd1eee0ef501535f53d15597a53c5c4118d178086143397a/pillow-13.0.0-cp313-cp313-win32.whl", hash = "sha256:12b0fa15879fe5a9d9eebe66e4e2d74128a773b1a4448010c9c714f45e04b9d8", size = 6516013, upload-time = "2026-10-16T22:16:21.389Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ed/1b16fb66a37e1b9e41370b93b2c614c7da761a5b2a41dc333f7af4639962/pillow-13.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:9dda0788a9932776a75d8bcc020076edc308ce2508b51f6739a45e3158b3fc42", size = 7284777, upload-time = "2026-10-16T22:16:23.393Z" },
{ url = "https://files.pythonhosted.org/packages/87/7d/8d02eba9f7ecc87f8e90f731e032cc4773802f2a843763afd0c66a535ff4/pillow-13.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:40a266371ece581e5de13b6635ef78d7487824af80561b6ca27694a88c19bc0f", size = 2698826, upload-time = "2026-10-16T22:16:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/8e/45/fbad9c7f5e2f1745c373a434d46072bc84eddfefaa28b852425ac957fda6/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:44629d4498c9cb6b2051806155f22141df0d8a929c56134b166655232dfb2f8c", size = 4190721, upload-time = "2026-10-16T22:16:27.524Z" },
{ url = "https://files.pythonhosted.org/packages/a2/bf/2074902a259a90692c5dbf132885d21671644cbbd3f690047516243a55db/pillow-13.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bf97ba1b5542050e9b0c74e632323b6d8136cc99d64e193abebee22fe6013c76", size = 4290236, upload-time = "2026-10-16T22:16:29.562Z" },
{ url = "https://files.pythonhosted.org/packages/ff/a5/3d1bb93b8ea38d15d21356c1b8e9f50971ac592aaa248f7fb71739094c0e/pillow-13.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:aa694c49787040943570d61c9b8fc01271bd2a2cfdd55f0d896f8aecdce1a981", size = 3723093, upload-time = "2026-10-16T22:16:31.249Z" },
{ url = "https://files.pythonhosted.org/packages/84/a1/be060135d85187b25fa8b2573786c0e45c62ae27527fff5257675eb97f4a/pillow-13.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:49c12af515073e9bf511e4ef736743bf70d531a643668722279314b937742ba4", size = 5572253, upload-time = "2026-10-16T22:16:33.103Z" },
{ url = "https://files.pythonhosted.org/packages/b5/f4/0bf6f1ac9ba7ee2ffe9914aeb410a1035a0c1a3c42f122388d7fc950bc4b/pillow-13.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d9b0a2bee3f3e03e9f29057aedb807dbf1e1d96ee1b9d0f422d9db6c8583271d", size = 4852269, upload-time = "2026-10-16T22:16:35.102Z" },
{ url = "https://files.pythonhosted.org/packages/83/16/b603274617b3e6728da3a0133d4fba1ea8d7fbe0c4ea5fef9cbffa398b8b/pillow-13.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5933fcea5a5ba64c686f55c886a115cbfd92f2900460784a1e79909fdbafd7b6", size = 6516058, upload-time = "2026-10-16T22:16:37.228Z" },
{ url = "https://files.pythonhosted.org/packages/1d/f2/1371707ce438ba9a43f9d24089828494ec6f9396e28cd7903aa704dd6306/pillow-13.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e91c2b1cbeae768c98752e67e717e283d68dae45717b41f2d91eaee648e37b7d", size = 7142574, upload-time = "2026-10-16T22:16:39.172Z" },
{ url = "https://files.pythonhosted.org/packages/b9/a9/62cf68587f910bc87c5951bcb6e9430c7643a65076adf5f18b4bf350e2bd/pillow-13.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f6299e494e46253d40c34653cac369412f44ca53b5f85d848d3f95b1ed0546c", size = 6614295, upload-time = "2026-10-16T22:16:41.117Z" },
{ url = "https://files.pythonhosted.org/packages/6c/c9/9e9b48178649730299be0bafbf5f810a5b6977538f5a92d47e4ba5f5d608/pillow-13.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f9bd7d260d986f3553518c5d723544346fa48031f8aef3265d786aeb59845bc0", size = 7224841, upload-time = "2026-10-16T22:16:43.118Z" },
{ url = "https://files.pythonhosted.org/packages/8f/48/c44e91384b1b6c072a714a63d00608acc16a6a968735e478e4be4384e5ac/pillow-13.0.0-cp314-cp314-win32.whl", hash = "sha256:2e31c4e68cffd7b1f116127626813b82fcba19edf6b940a7b0c266d6484d0055", size = 6508694, upload-time = "2026-10-16T22:16:45.883Z" },
{ url = "https://files.pythonhosted.org/packages/50/e2/7ad17747517e4c813dca78257d6bb593466f8231baf1384bc98d92745edb/pillow-13.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:f7cbb7a64e7ec664775195e4f81923bc8a93d9451b963f844e59118f63147a7a", size = 7281830, upload-time = "2026-10-16T22:16:47.725Z" },
{ url = "https://files.pythonhosted.org/packages/41/9f/e5b51f2ce8ef4563215334035403ce61a244f93a2e65c24a940131618f87/pillow-13.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6aaa7871aebca081eb5c45f83e845bbd9712ae99fa9286dbfdcee835e63f4b4", size = 2695468, upload-time = "2026-10-16T22:16:49.515Z" },
{ url = "https://files.pythonhosted.org/packages/97/73/a53502f731f6c84805a4ce87a8881e86d07a65aee64728b900d112dc703e/pillow-13.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7b999816b5850a35ada08443e6e03945f1f6488144a83afe2d37b240a552aafa", size = 5578196, upload-time = "2026-10-16T22:16:51.291Z" },
{ url = "https://files.pythonhosted.org/packages/21/50/7e949959a56b5559c78ef172b70fc6025706acb289f3f648a15329e4fa6f/pillow-13.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:67871a13cf47275201553b536c025da777f997fa562a19eb798af4ad4b97c18f", size = 4857276, upload-time = "2026-10-16T22:16:53.399Z" },
{ url = "https://files.pythonhosted.org/packages/c9/52/43ded0ac08e77e39d17bd6d894162572201b5e8fdc4a022b14c55a1960a4/pillow-13.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ca8f69b7c77fa96dad99f8638c1fc4f1c0bed4acaa1baf7a3c31bdb80a0ab1", size = 6607008, upload-time = "2026-10-16T22:16:55.525Z" },
{ url = "https://files.pythonhosted.org/packages/0d/2e/4a57ed216d5bf877387cba4d44c02aa87abe51ad2e06404aa6b597c4da39/pillow-13.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bce6a29c3898191f4c9998e4d4e8f69790caeabde46e54da4bc235ce5bde683", size = 7224281, upload-time = "2026-10-16T22:16:57.738Z" },
{ url = "https://files.pythonhosted.org/packages/5c/3d/2929b248e135752d63aaeabf53f6de797e905b7b6263f9c1e2adade20aba/pillow-13.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:816752a5ef76a80484c8c441c85593b7ea9304f6231fc5776ad1fbff7e773058", size = 6695600, upload-time = "2026-10-16T22:16:59.842Z" },
{ url = "https://files.pythonhosted.org/packages/bc/33/fb0da992cf084ed1ee6ee05e96b6191e35c351eb707ca2df874feb24ce47/pillow-13.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5bdb38ba220b61b4ec31dd1046dea6b016d3c6850a23f43ed8b58332b2fc668e", size = 7300589, upload-time = "2026-10-16T22:17:01.686Z" },
{ url = "https://files.pythonhosted.org/packages/bb/af/c30dd9ef6a3dce84ecb1b2393cac629047a32e66279056c6cb46c3ffd65c/pillow-13.0.0-cp314-cp314t-win32.whl", hash = "sha256:06903aac074d00c988e77a1fc435286dc498506d4ca8d6f69df9babddf4ecc15", size = 6517204, upload-time = "2026-10-16T22:17:03.722Z" },
{ url = "https://files.pythonhosted.org/packages/85/d5/ef3a8ae7609c8ab26570abf18194882534b480d245a9148b895a2262c004/pillow-13.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9a635068d9793c7f0d929f39f18b58f1bd86c236e8e7b64167b86af9232ebca0", size = 7286695, upload-time = "2026-10-16T22:17:05.543Z" },
{ url = "https://files.pythonhosted.org/packages/84/3c/0861db0c69a03689ebe3e7599d35668a414deb5aaafd8255215e4e62e3d3/pillow-13.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0d1a9cf7bc6917538b20f8d06b9ab2147e542f68849e96fc58eb66bdf6acc692", size = 2698098, upload-time = "2026-10-16T22:17:07.674Z" },
{ url = "https://files.pythonhosted.org/packages/6e/eb/e86a41962a3a31ad495a3d6a912d9002b01bba4a6fa71c28703abdca7f69/pillow-13.0.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7d5e097eabd5d4915c27ac9f6e9325bb1e01a7c94d7348b5c4451d3fc30e8521", size = 4191213, upload-time = "2026-10-16T22:17:09.449Z" },
{ url = "https://files.pythonhosted.org/packages/dd/04/7e7445e706afec84ef06dc25b2c77f9cecff242c23730c62928ad1089d4a/pillow-13.0.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:51ebb529fc5e8abdd55302f6c3f8943037348ec2384329a3e55677f69ae2fdfc", size = 4290650, upload-time = "2026-10-16T22:17:11.196Z" },
{ url = "https://files.pythonhosted.org/packages/46/85/d534089068ac40c0f61f650389a296c812eab745a8a733f81feec39ae955/pillow-13.0.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:c87a04e7f152ee4db5dbe15f0bfa3fe628a1aeedbe66b81c43b87f52c813f9ac", size = 3723078, upload-time = "2026-10-16T22:17:12.772Z" },
{ url = "https://files.pythonhosted.org/packages/ce/2b/00b5c3b401df538b5dbe311d3899730fae5ba1e21cb8b80a128887ac6747/pillow-13.0.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:20722cd5375533ad754053363a80b8fb30c79d37930a6f7228030e237ce3bd5d", size = 5572292, upload-time = "2026-10-16T22:17:14.539Z" },
{ url = "https://files.pythonhosted.org/packages/72/f5/a6b0610f946e9b54ae888ba7ad29d6e2f8d9b11f69e212d70416b7186c09/pillow-13.0.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:04a63720f79e22fd54d897c233a68314c50ca0603360f54ce2bd5db8f0f43782", size = 4852222, upload-time = "2026-10-16T22:17:16.521Z" },
{ url = "https://files.pythonhosted.org/packages/1e/d0/c0ff25bb0427ec70f32aac995453321fc3c4494a46c7292ff02d9d260b04/pillow-13.0.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f8db54177035a2b5f717f94475b74907e12a96dc74f6d487f3966b0a4694071", size = 6527349, upload-time = "2026-10-16T22:17:18.534Z" },
{ url = "https://files.pythonhosted.org/packages/85/11/e46fa0bcc0fc117f1eacc206b3d51f04d583fab328b4b740672d0438d83f/pillow-13.0.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f36aa77f74b14a2103622b196402c71330e4e7cf4122ee45c854e9a01e0eecf", size = 7152873, upload-time = "2026-10-16T22:17:20.415Z" },
{ url = "https://files.pythonhosted.org/packages/7c/53/2bdeca705f3a58679fa9201233193c86a30238340ca29b3a152c3fcc719b/pillow-13.0.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:149a1c323ba366f90b48e6f1a6db6582eb50512df374588de56fc909abc0f0f3", size = 6626871, upload-time = "2026-10-16T22:17:22.361Z" },
{ url = "https://files.pythonhosted.org/packages/14/de/3829030f209722ade3753bb456b805a0e7f885cf885b483407365c82e9ca/pillow-13.0.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e892354a424b1e82a78ba16785822bdd40de06f19446a3d4a28047df1a4f8932", size = 7236439, upload-time = "2026-10-16T22:17:24.352Z" },
{ url = "https://files.pythonhosted.org/packages/44/b1/67fc37388382bc036a74f04da5bb6fcbf90ec3ac1f45893e06176c373fd3/pillow-13.0.0-cp315-cp315-win32.whl", hash = "sha256:8e459b613cf31f8980f584387b4fe47bf08e076e288d61aa8eba1f7fd4ddbb81", size = 6508688, upload-time = "2026-10-16T22:17:26.314Z" },
{ url = "https://files.pythonhosted.org/packages/44/05/fdb6a79aee10861165526b41b7b440e36ee1ff58613b2fbefe3acbf06d4f/pillow-13.0.0-cp315-cp315-win_amd64.whl", hash = "sha256:d4737f50d23ddd11d6685faacbb9252887dde0bc7997127757247983d74399d6", size = 7281107, upload-time = "2026-10-16T22:17:28.198Z" },
{ url = "https://files.pythonhosted.org/packages/e6/9c/6c6f7dc2a566467baf121394e83de25ac287cffe34cc030726beb4be0224/pillow-13.0.0-cp315-cp315-win_arm64.whl", hash = "sha256:2310e2adfc13c6d457d7d0f2ffdd5c56a74a317650cf765887df8d515a647ac0", size = 2695513, upload-time = "2026-10-16T22:17:30.013Z" },
{ url = "https://files.pythonhosted.org/packages/23/16/e9120defdb8bff693b582d035312acc6f530cdc2bc5238a87f410efccd82/pillow-13.0.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:21c32061a5512dc13f87cc7303e120e4d77c2f70df89a35af0e3ce246e5ac57f", size = 5578129, upload-time = "2026-10-16T22:17:31.642Z" },
{ url = "https://files.pythonhosted.org/packages/47/22/76faf2bb2448f48a84cf894c1e2bb1090d7d2b1aa521ece131cb2f05d631/pillow-13.0.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c774b9c0ba0d0b356f71530677b39028878a2cae5a93e0a13779cfd17120f88", size = 4857428, upload-time = "2026-10-16T22:17:33.43Z" },
{ url = "https://files.pythonhosted.org/packages/b3/64/0861ea4e03c50b14af9ed9dd8b9f388ab42f445c623a9fcbab19aef7aa55/pillow-13.0.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20822140c9a2ba9c5e42df342b0a892ab2e102fc0e3db967a133bb9ce3f898bb", size = 6612582, upload-time = "2026-10-16T22:17:35.415Z" },
{ url = "https://files.pythonhosted.org/packages/5c/20/cd1e9b3f60a9f3d5ebe46da36955c26004092a501f7f2c42f9fcc26611bc/pillow-13.0.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffea6b2986a797c84b6d1d27d4f43e0b83c73fc16281a930891cd237cf0d08fe", size = 7230176, upload-time = "2026-10-16T22:17:37.362Z" },
{ url = "https://files.pythonhosted.org/packages/b6/ef/ffb0a2fc85690714fb7ac662956949645d57f15bf4426f520da361e7b6e3/pillow-13.0.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:05cd2cd39055d0b9650060e4513ef4a9a0aea870083950bd046a0d6490f93a22", size = 6703640, upload-time = "2026-10-16T22:17:39.313Z" },
{ url = "https://files.pythonhosted.org/packages/e0/c2/6402ee3ccb3363aeed8fdb3f9fe56703ac5377a539f0b81b53999d7e121f/pillow-13.0.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:c6fd031333dedcad7ce3e03d244093f43504f209e86925ebdb2b868fa4037630", size = 7309231, upload-time = "2026-10-16T22:17:41.021Z" },
{ url = "https://files.pythonhosted.org/packages/fc/96/d6b82e8b8c06ad143b66b12e0b31051e4bf489b4fabca1f390a14fe1ebde/pillow-13.0.0-cp315-cp315t-win32.whl", hash = "sha256:c50c80e3591e5cf9a8dfa6007d4f10732f02e32263850999286e56ccd650cfb2", size = 6517200, upload-time = "2026-10-16T22:17:42.886Z" },
{ url = "https://files.pythonhosted.org/packages/60/d4/0062e6005e9b270b165015aafd343364ac378ec5a685431ea42baad33357/pillow-13.0.0-cp315-cp315t-win_amd64.whl", hash = "sha256:adb3c2124c68e480f4632d06cb7caa3b48a51678475f483b4edb1c6dee29ea43", size = 7287488, upload-time = "2026-10-16T22:17:45.56Z" },
{ url = "https://files.pythonhosted.org/packages/e4/81/b8eb349746be7bce47dd95337c6634e84910af91b59aaa9159c034c614fa/pillow-13.0.0-cp315-cp315t-win_arm64.whl", hash = "sha256:4a25756244bdcce3b50580baadf320fa48297f0eeb8e114ba64139a40d4d0fc8", size = 2698073, upload-time = "2026-10-16T22:17:47.556Z" },
{ url = "https://files.pythonhosted.org/packages/60/da/f6d644e631c5b370c5d0b899f3ccadd2780aab070b29a110527cf4e1fd2c/pillow-13.0.0-pp312-pypy312_pp80-macosx_10_15_x86_64.whl", hash = "sha256:8619685cad8efc4c38c66d3b36aad854523297c43d748b3b6f0cc03c90ba3463", size = 5523188, upload-time = "2026-10-16T22:17:59.334Z" },
{ url = "https://files.pythonhosted.org/packages/e4/b0/2594f202d743a35d4fe80682bfd96f277e5ca7aecb27cf64ef5bfc1d000b/pillow-13.0.0-pp312-pypy312_pp80-macosx_11_0_arm64.whl", hash = "sha256:7e91e97a929232cdb828889ee6ad0e48d7f40f4ec38818f4e4a8968b2b1af22e", size = 4806808, upload-time = "2026-10-16T22:18:01.188Z" },
{ url = "https://files.pythonhosted.org/packages/8e/ed/d504165ef79af6fafe25c76726cf30f544cc236002f7ed054f1d51adc4ff/pillow-13.0.0-pp312-pypy312_pp80-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93ff9c9c3663ce23aaa5d0b6b6abac55359e2a0c06c7e30f3771829af816be63", size = 5464358, upload-time = "2026-10-16T22:18:02.779Z" },
{ url = "https://files.pythonhosted.org/packages/7b/fe/a10aba5503ca13e9e009b8bd0a1a4ce5584bdce246245fd6f3a83701369c/pillow-13.0.0-pp312-pypy312_pp80-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e65dd6e75de67b2362a4fa3e16aa627595f65054dc4ec73d64680c84d592fa61", size = 6043514, upload-time = "2026-10-16T22:18:04.538Z" },
{ url = "https://files.pythonhosted.org/packages/c0/37/66b12dc476041fe523bca29c9d58fbf1ea92a4eaf73acfb9e993948f1191/pillow-13.0.0-pp312-pypy312_pp80-win_amd64.whl", hash = "sha256:567f20f1c75897d7663830da6ea8af719e03d712b1d9ee7f813319f9ece42e5a", size = 7366030, upload-time = "2026-10-16T22:18:06.638Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
@@ -1267,6 +1698,100 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" },
]
[[package]]
name = "propcache"
version = "0.5.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" },
{ url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" },
{ url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" },
{ url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" },
{ url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" },
{ url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" },
{ url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" },
{ url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" },
{ url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" },
{ url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" },
{ url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" },
{ url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" },
{ url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" },
{ url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" },
{ url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" },
{ url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" },
{ url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" },
{ url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" },
{ url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" },
{ url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" },
{ url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" },
{ url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" },
{ url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" },
{ url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" },
{ url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" },
{ url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" },
{ url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" },
{ url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" },
{ url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" },
{ url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" },
{ url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" },
{ url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" },
{ url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" },
{ url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" },
{ url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" },
{ url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" },
{ url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" },
{ url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" },
{ url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" },
{ url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" },
{ url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" },
{ url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" },
{ url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" },
{ url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" },
{ url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" },
{ url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" },
{ url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" },
{ url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" },
{ url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" },
{ url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" },
{ url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" },
{ url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" },
{ url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" },
{ url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" },
{ url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" },
{ url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" },
{ url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" },
{ url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" },
{ url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" },
{ url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" },
{ url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" },
{ url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" },
{ url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" },
{ url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" },
{ url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" },
{ url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" },
{ url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" },
{ url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" },
{ url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" },
{ url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" },
{ url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" },
{ url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" },
{ url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" },
{ url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" },
{ url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" },
{ url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" },
{ url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" },
{ url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" },
{ url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" },
{ url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" },
{ url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" },
{ url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" },
{ url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" },
{ url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" },
]
[[package]]
name = "psutil"
version = "7.2.2"
@@ -2307,3 +2832,85 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/21/d43cfdb655638f008e349a4ad62b5d45dde5322f45146262a0aba55ee130/whenever-0.10.3-cp314-cp314t-win_amd64.whl", hash = "sha256:2a761adc97ced36455c8b94d39cee43f45c009cd2870d246e518072fd3f2730b", size = 565695, upload-time = "2026-07-17T07:35:22.398Z" },
{ url = "https://files.pythonhosted.org/packages/18/00/50e9b44b080813feb958b31e9e370a430444e8676d451a35f28bc638074c/whenever-0.10.3-py3-none-any.whl", hash = "sha256:d35778c88db6d0229c436014a4704180520315a1d5ed3b71ee6ba120cbfb9b3e", size = 121694, upload-time = "2026-07-17T07:35:24.291Z" },
]
[[package]]
name = "yarl"
version = "1.24.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "multidict" },
{ name = "propcache" },
]
sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/84/71d051c850b5af41d168c679d9eb67eb7c55283ac4ee131673edf134bc4e/yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d", size = 136035, upload-time = "2026-07-20T02:05:25.489Z" },
{ url = "https://files.pythonhosted.org/packages/03/4d/8ad27f9a1b7e69313cca5d695b925b48efe51208d3490e0844bae97cabc0/yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec", size = 97642, upload-time = "2026-07-20T02:05:27.429Z" },
{ url = "https://files.pythonhosted.org/packages/ea/b4/05b4131c407006cd1e410e9c6539f16a0945724677e5364447313c15ea3e/yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c", size = 97323, upload-time = "2026-07-20T02:05:29.441Z" },
{ url = "https://files.pythonhosted.org/packages/20/16/e618c875c73e0e39611f20a581b3d5e8d59b8857bf001bee3263044c6deb/yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54", size = 107741, upload-time = "2026-07-20T02:05:31.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/9a/c4defeaf3ed33fcb346aacf9c6e971a8d4e2bde04a0310e79abb208e7965/yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12", size = 103570, upload-time = "2026-07-20T02:05:33.303Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e7/0e0e0de5865ebd5914537ef486f36c727a59865c3ac0cf5ff1b32aececbf/yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d", size = 115815, upload-time = "2026-07-20T02:05:35.292Z" },
{ url = "https://files.pythonhosted.org/packages/2b/27/ca56b700cb170aba25a3893b75355b213935657dc5714d2383354a270e62/yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1", size = 116025, upload-time = "2026-07-20T02:05:37.503Z" },
{ url = "https://files.pythonhosted.org/packages/d6/d0/d56c859b8222116f5d68459199f48359e0bf121b6f65a69bf329b3602ba0/yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9", size = 109835, upload-time = "2026-07-20T02:05:39.506Z" },
{ url = "https://files.pythonhosted.org/packages/70/a2/3a35557e4d1a79425040eba202ccaf08bdc8717680fc77e2498a1ad2e0a5/yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027", size = 108884, upload-time = "2026-07-20T02:05:41.584Z" },
{ url = "https://files.pythonhosted.org/packages/e4/35/ef4c26356b7913c68983bac2d72a4212b3347af551cb8d250b99b5ed7b7f/yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b", size = 107308, upload-time = "2026-07-20T02:05:43.697Z" },
{ url = "https://files.pythonhosted.org/packages/d5/91/ff0dc66c2ccf3e0153ab97ff61eabab4400e6a5264af427ab30cd69f1857/yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293", size = 103646, upload-time = "2026-07-20T02:05:45.895Z" },
{ url = "https://files.pythonhosted.org/packages/74/f0/33b9271c7f881766359d58266fa0811d2e5210ed860e28da7dc6d7786344/yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e", size = 115305, upload-time = "2026-07-20T02:05:47.832Z" },
{ url = "https://files.pythonhosted.org/packages/ef/65/fd79fb1868c4a80db8661091de525bf430f63c3bea1b20e8b6a84fc7d359/yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b", size = 108404, upload-time = "2026-07-20T02:05:49.604Z" },
{ url = "https://files.pythonhosted.org/packages/ff/ba/dbabe6b262f17a816c70cfc09558dbf03ece3ec76684d02f911a3d3a189c/yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce", size = 115940, upload-time = "2026-07-20T02:05:51.741Z" },
{ url = "https://files.pythonhosted.org/packages/a5/43/fab2d1dad9d340a268cdde63756a123d069723efff6a372d123fa74a9517/yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba", size = 110006, upload-time = "2026-07-20T02:05:53.554Z" },
{ url = "https://files.pythonhosted.org/packages/c4/27/41eb51bbd1b8d89546b83897cfb0164f1e109304fd408dbb151b639eec0f/yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b", size = 97618, upload-time = "2026-07-20T02:05:55.57Z" },
{ url = "https://files.pythonhosted.org/packages/3c/25/b2553764b3d65db711d8f45416351ec4f420847558eb669edcbcaadf5780/yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c", size = 93018, upload-time = "2026-07-20T02:05:57.554Z" },
{ url = "https://files.pythonhosted.org/packages/e1/63/64ef361967cc983573149dc1515d531db5da8a4c92d22bb833d59e01b313/yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2", size = 135075, upload-time = "2026-07-20T02:05:59.671Z" },
{ url = "https://files.pythonhosted.org/packages/bb/89/55920fd853ce43e608adbc3962456f0d649d6bb15250dc2988321da0fe1c/yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb", size = 97225, upload-time = "2026-07-20T02:06:01.769Z" },
{ url = "https://files.pythonhosted.org/packages/15/f0/7688d3f2cfff7590df2af38ec46d969f4281a4dddb08a9ad2eafbcdddf98/yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075", size = 96751, upload-time = "2026-07-20T02:06:03.676Z" },
{ url = "https://files.pythonhosted.org/packages/05/1a/a851a0f94aaaf379dd4f901bfc80f634280bec51eb260b47363e2a4cd62e/yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff", size = 107960, upload-time = "2026-07-20T02:06:05.699Z" },
{ url = "https://files.pythonhosted.org/packages/6c/a8/faea066c12f9c77ca0de90641f1655f9dd7b412477bf28c76d692f3aecff/yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448", size = 103500, upload-time = "2026-07-20T02:06:07.556Z" },
{ url = "https://files.pythonhosted.org/packages/fb/9c/1e67084c2a6e2f2db0e3be798328cb3be42c0119b621d25461479a224d21/yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f", size = 115780, upload-time = "2026-07-20T02:06:09.599Z" },
{ url = "https://files.pythonhosted.org/packages/58/86/1f94664e147474337e3359f52012cf3d02f825f694317b178bfba1078c62/yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd", size = 115308, upload-time = "2026-07-20T02:06:11.352Z" },
{ url = "https://files.pythonhosted.org/packages/0a/43/8e55ae7538ba5f28ccb3c845c6dd4549cf7016d5992e5326512519107cdd/yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16", size = 110574, upload-time = "2026-07-20T02:06:13.129Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ba/a889ec8765cedcf2ac44dcb02d6a21e4861399b243b263c5f2dde27ee740/yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213", size = 109914, upload-time = "2026-07-20T02:06:15.243Z" },
{ url = "https://files.pythonhosted.org/packages/9c/c3/e45f821af67b791c2dbbe4a9f4137a1d33f8d386654a05a0c3f47bdfa25d/yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24", size = 107712, upload-time = "2026-07-20T02:06:17.443Z" },
{ url = "https://files.pythonhosted.org/packages/02/00/2ab0f42c9857fcb490bfaa6647b14540b53d241ab209f23220b958cc5832/yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385", size = 104251, upload-time = "2026-07-20T02:06:19.259Z" },
{ url = "https://files.pythonhosted.org/packages/7a/70/709d9a286e98af2c7fd8e4e6cada658b5c0e30d87dd7e2a63c2fb5767217/yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c", size = 115319, upload-time = "2026-07-20T02:06:21.207Z" },
{ url = "https://files.pythonhosted.org/packages/5c/6c/3eaa515142991fe84cfc483ff986492211f1978f90161ccefdbec919d09b/yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4", size = 109163, upload-time = "2026-07-20T02:06:23.006Z" },
{ url = "https://files.pythonhosted.org/packages/bb/64/711dafce66c323a3144d470547a71c5384c57623308ac8bb5e4b903ac148/yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144", size = 115435, upload-time = "2026-07-20T02:06:24.923Z" },
{ url = "https://files.pythonhosted.org/packages/cf/f3/9b9d0e6d84bea851eb1ba99e4bdc755b86fd813e49ec86dfe42f26befdef/yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4", size = 110691, upload-time = "2026-07-20T02:06:26.973Z" },
{ url = "https://files.pythonhosted.org/packages/86/e4/62a06b7e87c4246ac76b7c2da136f972eb4a3a1fc94abb07e7022d6fdb0a/yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740", size = 97454, upload-time = "2026-07-20T02:06:29.163Z" },
{ url = "https://files.pythonhosted.org/packages/9e/c9/5fc8025b318ab10db413b61056bd0d95c557a70e8df4210c7511f866329c/yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1", size = 92813, upload-time = "2026-07-20T02:06:31.113Z" },
{ url = "https://files.pythonhosted.org/packages/a9/08/5f3085fef9564217074db9dd8573de1795bc82cde61a7ad10b6a7234a569/yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76", size = 135680, upload-time = "2026-07-20T02:06:33.273Z" },
{ url = "https://files.pythonhosted.org/packages/98/35/ba9436e579bd48a8801f2021d842d9ab4994c26e4c7dd3a4c1f1bcb57a9e/yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d", size = 97395, upload-time = "2026-07-20T02:06:35.259Z" },
{ url = "https://files.pythonhosted.org/packages/18/a9/a07f76f3c44e02b25cc743af5ef93eef27f7013eadca770451b6a6ccb5db/yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75", size = 97223, upload-time = "2026-07-20T02:06:37.216Z" },
{ url = "https://files.pythonhosted.org/packages/77/f7/a9a1d6fa7dd9e388f95b30f6ad3ec4e285f6c8f61f44ce16070c3fcfe414/yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9", size = 108777, upload-time = "2026-07-20T02:06:39.292Z" },
{ url = "https://files.pythonhosted.org/packages/2f/44/e0b86c302471fabd6f02808ecf2ac52b8412b624787849d4bf2cdb466f6f/yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede", size = 103119, upload-time = "2026-07-20T02:06:41.456Z" },
{ url = "https://files.pythonhosted.org/packages/d1/16/9c16d180bf8faaf223225eb50e1245870ff1ae0e302a27153988e65c51fd/yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca", size = 116471, upload-time = "2026-07-20T02:06:43.696Z" },
{ url = "https://files.pythonhosted.org/packages/d2/8d/b219b9df28a02ce95cfbdd41d2f7caa5669d0ff979c1c9975697145e33c5/yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027", size = 115974, upload-time = "2026-07-20T02:06:45.874Z" },
{ url = "https://files.pythonhosted.org/packages/9b/e8/f20557aca240d88e69850ad1ee91756821d094bb1310565c04d25c6682a2/yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9", size = 110830, upload-time = "2026-07-20T02:06:47.852Z" },
{ url = "https://files.pythonhosted.org/packages/db/18/199b85109a53eeca64ee19c9cca228287e8e4ab0cc1a09b28f530e65cce0/yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41", size = 110054, upload-time = "2026-07-20T02:06:49.84Z" },
{ url = "https://files.pythonhosted.org/packages/aa/2f/ed28147f8cd7f48c49367c90713b30a555284b6105a6a56f3a05568da795/yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373", size = 108312, upload-time = "2026-07-20T02:06:51.835Z" },
{ url = "https://files.pythonhosted.org/packages/c5/c5/55e16ae0a5c227cea8df1c6871ba57d614a34243146c05729caf2a1bd9c5/yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36", size = 103662, upload-time = "2026-07-20T02:06:54.061Z" },
{ url = "https://files.pythonhosted.org/packages/8d/ea/dbd7c2caec459c9a426f18b02688ecbfb58620d0f6a3422d24769fbaf8ab/yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0", size = 116090, upload-time = "2026-07-20T02:06:56.015Z" },
{ url = "https://files.pythonhosted.org/packages/06/84/39ce4ce3059e07fece5fbdbee8c4053406af9aca911ce9fa5f8548aab6af/yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5", size = 109523, upload-time = "2026-07-20T02:06:57.926Z" },
{ url = "https://files.pythonhosted.org/packages/a9/8b/71ff44137b405c64a7788075669c24010019f57a7464b78c3a6cbee539d9/yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5", size = 116084, upload-time = "2026-07-20T02:06:59.868Z" },
{ url = "https://files.pythonhosted.org/packages/62/c0/423078fdd4042e1862c11f0ffd977a0ffa393783c12bee94685923bc189e/yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4", size = 111006, upload-time = "2026-07-20T02:07:01.907Z" },
{ url = "https://files.pythonhosted.org/packages/cf/52/6daa2ee9d95e5c98b8128f8df91eb692eb423ab274b8cf08db52152fad26/yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad", size = 99215, upload-time = "2026-07-20T02:07:03.852Z" },
{ url = "https://files.pythonhosted.org/packages/ec/0e/464a847d7359e0da75dd9fc5c1d1aa35d0159ea31e5f8e66a3c1c29ff3d0/yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f", size = 94566, upload-time = "2026-07-20T02:07:06.074Z" },
{ url = "https://files.pythonhosted.org/packages/e2/55/e03acc4446772660bc335e86e41ef31e4d0d838fd641531a11a5ee33b493/yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88", size = 142533, upload-time = "2026-07-20T02:07:08.284Z" },
{ url = "https://files.pythonhosted.org/packages/ae/71/4acd3a1fc7cf14345cdb302665ecd2097f62c365b4f14ca17d4f37775cf9/yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba", size = 100776, upload-time = "2026-07-20T02:07:10.197Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0b/cfb76b7fe99686db264bff829779a539d923e7564ffd7ef18da6c54c3774/yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928", size = 100913, upload-time = "2026-07-20T02:07:12.357Z" },
{ url = "https://files.pythonhosted.org/packages/8b/3f/7116e782992abbd4fb6948488aec72078895e929a23078290739e8396fce/yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f", size = 106507, upload-time = "2026-07-20T02:07:14.173Z" },
{ url = "https://files.pythonhosted.org/packages/33/90/d4d2d73ee78229cc889872eb8e085d8f5c6f51abdb178409fd9b23cf74fd/yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95", size = 99219, upload-time = "2026-07-20T02:07:16.019Z" },
{ url = "https://files.pythonhosted.org/packages/3e/fa/a6df1a9bccd644eec00abee0dff4277416222cec435330fd1f2858523ec1/yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc", size = 111804, upload-time = "2026-07-20T02:07:18.141Z" },
{ url = "https://files.pythonhosted.org/packages/8a/9e/7b2a1f4bcc20e9447156dd2b1c4d01f70d9df0759025ee7d09a84ffae134/yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da", size = 110943, upload-time = "2026-07-20T02:07:20.06Z" },
{ url = "https://files.pythonhosted.org/packages/08/ff/22c92affb0f9b623ca753d27d968b5625b868f12c6378d049d55ae247643/yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a", size = 108251, upload-time = "2026-07-20T02:07:22.217Z" },
{ url = "https://files.pythonhosted.org/packages/45/44/5769b96298c1e195fb412997b6090af2a84105cf59c17613558a2d011d1f/yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0", size = 106025, upload-time = "2026-07-20T02:07:24.083Z" },
{ url = "https://files.pythonhosted.org/packages/4c/40/009e8e791fd9762c0e1567e69248acb4f49064597e1680874c16dd8bb798/yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498", size = 106573, upload-time = "2026-07-20T02:07:26.248Z" },
{ url = "https://files.pythonhosted.org/packages/20/c6/b7480578f8a0a80946f36ad6df547ecec704f9ba69d2de60f8aa6f1c1cbf/yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104", size = 100751, upload-time = "2026-07-20T02:07:28.098Z" },
{ url = "https://files.pythonhosted.org/packages/d4/27/4476f3360b91a48c5cf125e91f59a3bd35299d84a431a258d57f5977bb11/yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331", size = 111643, upload-time = "2026-07-20T02:07:30.88Z" },
{ url = "https://files.pythonhosted.org/packages/4c/4b/5cdd3e5ee944e8af31e52f6cd3d3af5fd7b937e036ccbbba2c9ffebede95/yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550", size = 106312, upload-time = "2026-07-20T02:07:33.06Z" },
{ url = "https://files.pythonhosted.org/packages/18/86/f406b0c2a6f99575de2da671ef47aa06f89a5be83a27a46971c3b86cecdb/yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6", size = 110379, upload-time = "2026-07-20T02:07:35.155Z" },
{ url = "https://files.pythonhosted.org/packages/f0/6c/9f3adfbd3b30b4fa0f7ccb3a83eba2c1152d3fff554d535e640ba0f7ba2b/yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047", size = 108497, upload-time = "2026-07-20T02:07:37.35Z" },
{ url = "https://files.pythonhosted.org/packages/dd/37/91eb2e5ca883a529c1b390348a74cd9fc0512171727f547ce70bfe02be5c/yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104", size = 102450, upload-time = "2026-07-20T02:07:39.578Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f4/ed5c402ac8fde4403ed3366c2716bfddc8a6677ebd59f3d62772cc7fe468/yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688", size = 97222, upload-time = "2026-07-20T02:07:41.55Z" },
{ url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" },
]