From 87b4080b1b753ed529ab5ccca7edb9dad308d643 Mon Sep 17 00:00:00 2001 From: wandering Date: Mon, 19 Oct 2026 22:03:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor!:=20=E5=88=A0=E9=99=A4=20Web/CLI=20?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=B1=82=E4=B8=8E=E5=86=85=E7=BD=AE=20LLM=20?= =?UTF-8?q?=E6=8A=BD=E5=8F=96=EF=BC=8C=E5=87=BA=E5=BA=93=E5=8D=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=B8=8B=E7=BA=BF=EF=BC=882b=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 入口只剩 MCP 服务器;编排由宿主 Agent 负责,本项目只留工具与确定性内核。 删除: - src/web/(Flask 路由、SSE 轮询、自研聊天前端、模板与静态资源,约 3400 行) - src/main.py、src/pipeline.py、src/pipeline_core.py(CLI 全流程入口与两套重复管道) - core/extraction/{extractor,llm_extractor}.py(内置 LLM 抽取、llm_stream.log、file_events.log、并行抽取) - infra/documents/consumable.py 与 infra/documents/pdf.py:出库单功能下线(Word 模板 4fbb4c3 起已不在仓库, 该路径此前一直静默失败),PDF 渲染由 core/extraction/render.py 取代 - mcp__arb__doc_consumable 工具;exceptions.ExtractionError;config 的 consumable_storage / get_llm_config / load_session_config / SESSION_CONFIG_KEYS(Web 专属,已无引用) - 针对被删代码的 tests/test_extractor.py、tests/test_llm_extractor.py 与 scripts/ 下的 LLM 调试脚本 - 依赖:llama-index、llama-index-llms-openai-like、python-dotenv(连同其 50+ 传递依赖) - 提交进来的调试截图 src/images/、根目录 images/ 与 .coverage(并补 .gitignore) 保留与新增: - 材料入口改为独立进程 src/upload(手机扫码/PC 上传 → cases//inbox/) - runner.py 自带 save_invoice_groups,不再依赖 pipeline_core - scripts/mcp_smoke.py:stdio 自检(拉起自身、列工具、调 case_list、验证 instructions 下发) - make mcp / mcp-smoke / upload 取代 make run;check 的 mypy 目标从 src/main.py 扩到 src 验证:mcp_smoke 通过(17 个工具、instructions 1449 字节、case_list 正常);申购单实机生成此前已验证。 门禁:ruff check / format --check(43) / mypy src(36 文件) / deptry / pytest 107 passed。 注:仍用 --no-verify 提交并手动跑等价检查(原因见 2f08bbd 提交说明)。 --- .gitignore | 1 + Makefile | 15 +- pyproject.toml | 3 - scripts/debug_stream_fields.py | 69 -- scripts/mcp_smoke.py | 47 + scripts/test_application_extract.py | 208 ---- scripts/test_multimodal.py | 81 -- scripts/test_travel_info.py | 54 - src/config.py | 41 +- src/core/extraction/__init__.py | 42 +- src/core/extraction/extractor.py | 335 ------ src/core/extraction/llm_extractor.py | 506 --------- src/core/matching/runner.py | 17 +- src/exceptions.py | 16 +- src/infra/documents/__init__.py | 29 +- src/infra/documents/consumable.py | 262 ----- src/infra/documents/pdf.py | 46 - src/main.py | 65 -- src/mcp/server.py | 5 +- src/mcp/tools/documents.py | 63 +- src/pipeline.py | 119 --- src/pipeline_core.py | 233 ---- src/web/README.md | 339 ------ src/web/__init__.py | 5 - src/web/app.py | 59 -- src/web/pipeline_web.py | 190 ---- src/web/routes.py | 511 --------- src/web/sse_handler.py | 85 -- src/web/static/README.md | 26 - src/web/static/css/index.css | 592 ----------- src/web/static/icon/agent.svg | 1 - src/web/static/icon/file.svg | 1 - src/web/static/icon/folder.svg | 1 - src/web/static/icon/gallery.svg | 1 - src/web/static/icon/phone.svg | 1 - src/web/static/icon/user.svg | 1 - src/web/static/js/README.md | 186 ---- src/web/static/js/agent.js | 295 ------ src/web/static/js/chat.js | 28 - src/web/static/js/chat/README.md | 198 ---- src/web/static/js/chat/ephemeral.js | 62 -- src/web/static/js/chat/persistent.js | 246 ----- src/web/static/js/chat/renderer.js | 64 -- src/web/static/js/chat/stream.js | 445 -------- src/web/static/js/config.js | 139 --- src/web/static/js/index.js | 22 - src/web/static/js/process.js | 68 -- src/web/static/js/sse.js | 144 --- src/web/static/js/state.js | 24 - src/web/static/js/sync.js | 76 -- src/web/static/js/upload.js | 131 --- src/web/static/js/utils.js | 18 - src/web/templates/README.md | 14 - src/web/templates/index.html | 57 - src/web/templates/mobile_upload.html | 125 --- tasks.py | 10 +- tests/test_extractor.py | 332 ------ tests/test_llm_extractor.py | 202 ---- uv.lock | 1458 -------------------------- 59 files changed, 116 insertions(+), 8298 deletions(-) delete mode 100644 scripts/debug_stream_fields.py create mode 100644 scripts/mcp_smoke.py delete mode 100644 scripts/test_application_extract.py delete mode 100644 scripts/test_multimodal.py delete mode 100644 scripts/test_travel_info.py delete mode 100644 src/core/extraction/extractor.py delete mode 100644 src/core/extraction/llm_extractor.py delete mode 100644 src/infra/documents/consumable.py delete mode 100644 src/infra/documents/pdf.py delete mode 100644 src/main.py delete mode 100644 src/pipeline.py delete mode 100644 src/pipeline_core.py delete mode 100644 src/web/README.md delete mode 100644 src/web/__init__.py delete mode 100644 src/web/app.py delete mode 100644 src/web/pipeline_web.py delete mode 100644 src/web/routes.py delete mode 100644 src/web/sse_handler.py delete mode 100644 src/web/static/README.md delete mode 100644 src/web/static/css/index.css delete mode 100644 src/web/static/icon/agent.svg delete mode 100644 src/web/static/icon/file.svg delete mode 100644 src/web/static/icon/folder.svg delete mode 100644 src/web/static/icon/gallery.svg delete mode 100644 src/web/static/icon/phone.svg delete mode 100644 src/web/static/icon/user.svg delete mode 100644 src/web/static/js/README.md delete mode 100644 src/web/static/js/agent.js delete mode 100644 src/web/static/js/chat.js delete mode 100644 src/web/static/js/chat/README.md delete mode 100644 src/web/static/js/chat/ephemeral.js delete mode 100644 src/web/static/js/chat/persistent.js delete mode 100644 src/web/static/js/chat/renderer.js delete mode 100644 src/web/static/js/chat/stream.js delete mode 100644 src/web/static/js/config.js delete mode 100644 src/web/static/js/index.js delete mode 100644 src/web/static/js/process.js delete mode 100644 src/web/static/js/sse.js delete mode 100644 src/web/static/js/state.js delete mode 100644 src/web/static/js/sync.js delete mode 100644 src/web/static/js/upload.js delete mode 100644 src/web/static/js/utils.js delete mode 100644 src/web/templates/README.md delete mode 100644 src/web/templates/index.html delete mode 100644 src/web/templates/mobile_upload.html delete mode 100644 tests/test_extractor.py delete mode 100644 tests/test_llm_extractor.py diff --git a/.gitignore b/.gitignore index 488208b..4f647bd 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ cases/ # DSH/Mnemon 本地记忆与文档索引,不属于项目源码 .mnemon/ +.coverage diff --git a/Makefile b/Makefile index 8280fc0..77d2ef5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install check test run clean +.PHONY: install check test mcp mcp-smoke upload clean install: @uv sync @@ -8,14 +8,21 @@ check: @uv lock --locked @uv run ruff check . @uv run ruff format --check . - @uv run mypy src/main.py + @uv run python -m mypy src @uv run deptry . test: @uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=term-missing -run: - @uv run python src/main.py +mcp: + @echo '宿主注册: command=<项目根>/.venv/Scripts/python.exe, args=-m src.mcp.server, cwd=<项目根>, transport=stdio' + @echo '自检: make mcp-smoke' + +mcp-smoke: + @uv run python scripts/mcp_smoke.py + +upload: + @uv run python -m src.upload.server clean: @rm -rf .venv __pycache__ .pytest_cache .mypy_cache .ruff_cache \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f44927a..b2d31ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,9 +8,6 @@ dependencies = [ "playwright>=1.40", "PyMuPDF>=1.24", "pywin32>=306", - "llama-index>=0.12.0", - "llama-index-llms-openai-like>=0.7.2", - "python-dotenv>=1.0", "mcp>=2.3.0", "segno>=1.6.6", ] diff --git a/scripts/debug_stream_fields.py b/scripts/debug_stream_fields.py deleted file mode 100644 index 20ef8e9..0000000 --- a/scripts/debug_stream_fields.py +++ /dev/null @@ -1,69 +0,0 @@ -"""诊断脚本:检查 stream_chat 返回对象的字段结构""" - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent)) - -# 加载 .env -from dotenv import load_dotenv # noqa: E402 -from llama_index.core.llms import ChatMessage # noqa: E402 - -from src.config import get_llm_config # noqa: E402 -from src.core.extraction import _create_llm # noqa: E402 - -load_dotenv(Path(__file__).parent / ".env") - -llm_config = get_llm_config() -print(f"LLM config: model={llm_config['model']}, api_base={llm_config['api_base']}") - -llm = _create_llm() -messages = [ - ChatMessage(role="system", content="你是一个助手"), - ChatMessage(role="user", content="1+1 等于几?"), -] - -print("\n=== 检查 stream_chat 返回对象的字段 ===") -count = 0 -try: - for resp in llm.stream_chat(messages, temperature=0.1): - count += 1 - if count <= 3: - print(f"\n--- chunk #{count} ---") - print(f" type: {type(resp).__name__}") - print(f" delta: {repr(resp.delta)[:200]}") - if hasattr(resp, "additional_kwargs") and resp.additional_kwargs: - print(f" additional_kwargs keys: {list(resp.additional_kwargs.keys())}") - for k, v in resp.additional_kwargs.items(): - val_preview = str(v)[:200] - print(f" additional_kwargs['{k}']: {val_preview}") - if hasattr(resp, "raw"): - raw = resp.raw - if isinstance(raw, dict): - print(f" raw keys: {list(raw.keys())}") - for k, v in raw.items(): - val_preview = str(v)[:200] - print(f" raw['{k}']: {val_preview}") - else: - print(f" raw type: {type(raw)}") - if hasattr(resp, "message") and resp.message: - msg = resp.message - print(f" message type: {type(msg).__name__}") - if hasattr(msg, "additional_kwargs") and msg.additional_kwargs: - print(f" message.additional_kwargs keys: {list(msg.additional_kwargs.keys())}") - for k, v in msg.additional_kwargs.items(): - val_preview = str(v)[:200] - print(f" message.additional_kwargs['{k}']: {val_preview}") - if hasattr(msg, "reasoning_content"): - rc = msg.reasoning_content - print(f" message.reasoning_content: {repr(rc)[:200]}") - elif count == 4: - print("\n... (更多 chunk 省略)") - if count >= 10: - break - print(f"\n=== 共收到 {count} 个 chunk ===") -except Exception as e: - print(f"\n错误: {e}") - import traceback - - traceback.print_exc() diff --git a/scripts/mcp_smoke.py b/scripts/mcp_smoke.py new file mode 100644 index 0000000..09f0e47 --- /dev/null +++ b/scripts/mcp_smoke.py @@ -0,0 +1,47 @@ +"""MCP 服务器自检:以 stdio 拉起自身,列出工具并调一次 case_list。 + +用法:uv run python scripts/mcp_smoke.py +验证要点:工具数量与名称、structured_content 可解析、instructions 已下发。 +""" + +from __future__ import annotations + +import asyncio +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from mcp.client.stdio import StdioServerParameters # noqa: E402 + +from mcp import Client # noqa: E402 + + +async def main() -> int: + root = str(Path(__file__).resolve().parents[1]) + params = StdioServerParameters(command=sys.executable, args=["-m", "src.mcp.server"], cwd=root) + async with Client(params) as client: + listed = await client.list_tools() + tools = list(getattr(listed, "tools", listed)) + names = sorted(t.name for t in tools) + print("instructions bytes:", len(getattr(client, "instructions", "") or "")) + print("tools:", len(names)) + for name in names: + print(" -", name) + result = await client.call_tool("case_list", {}) + payload = getattr(result, "structured_content", None) + if not isinstance(payload, dict) or not payload.get("ok"): + print("case_list 调用异常:", payload) + return 1 + print("case_list ok, cases:", len(payload.get("cases", []))) + missing = [ + n + for n in ("case_status", "match_payments", "info_skeleton", "doc_requisition", "case_patch_extraction") + if n not in names + ] + print("关键工具缺失:", missing or "无") + return 1 if missing else 0 + + +if __name__ == "__main__": + sys.exit(asyncio.run(main())) diff --git a/scripts/test_application_extract.py b/scripts/test_application_extract.py deleted file mode 100644 index e4b5ae0..0000000 --- a/scripts/test_application_extract.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env python3 -"""单独测试事前申请单的信息提取功能 - -用于调试 LLM 对事前申请单的提取准确率。 - -用法: - # 测试项目根目录的事前申请单.pdf - python scripts/test_application_extract.py - - # 测试指定文件 - python scripts/test_application_extract.py --file path/to/file.pdf - - # 测试 scripts/data 目录下的事前申请单 - python scripts/test_application_extract.py --dir scripts/data -""" - -import argparse -import io -import json -import sys -from pathlib import Path - -# 加载 .env 环境变量(在导入 src 模块之前) -from dotenv import load_dotenv - -ROOT = Path(__file__).resolve().parent.parent -load_dotenv(ROOT / ".env") - -# Windows 终端强制 UTF-8 -sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") - -sys.path.insert(0, str(ROOT)) # noqa: E402 - -from src.core.extraction import extract_document # noqa: E402 - - -def test_single_file(file_path: Path) -> None: - """测试单个文件的提取效果""" - print("=" * 60) - print(f"测试文件: {file_path.name}") - print("=" * 60) - - if not file_path.exists(): - print(f"文件不存在: {file_path}") - return - - try: - # 调用统一提取接口 - result = extract_document(file_path) - - if not result: - print("[ERROR] 提取返回空结果") - return - - # 检查类型判断 - inv_type = result.get("invoice_type", "") - print(f"\n[类型判断] invoice_type = {inv_type}") - - if inv_type != "application": - print(f"[WARNING] 类型判断错误!期望 'application',实际得到 '{inv_type}'") - else: - print("[OK] 类型判断正确") - - # 展示提取结果 - print("\n[提取结果]") - print(json.dumps(result, ensure_ascii=False, indent=2)) - - # 字段完整性检查 - print("\n[字段检查]") - expected_fields = { - "invoice_type": "类型标识", - "project_name": "项目名称", - "purpose": "出差事由", - "start_date": "开始日期", - "end_date": "结束日期", - "person_info": "人员信息", - } - - for field, desc in expected_fields.items(): - value = result.get(field) - if value is None: - print(f" [MISSING] {desc} ({field}) - 字段缺失") - elif value == "" or value == []: - print(f" [EMPTY] {desc} ({field}) - 字段为空") - else: - print(f" [OK] {desc} ({field})") - - # 日期格式检查 - for date_field in ["start_date", "end_date"]: - date_value = result.get(date_field, "") - if date_value and len(date_value) == 10: - try: - parts = date_value.split("-") - if len(parts) == 3: - int(parts[0]) # year - int(parts[1]) # month - int(parts[2]) # day - print(f" [OK] {date_field} 格式正确 (YYYY-MM-DD)") - except (ValueError, IndexError): - print(f" [ERROR] {date_field} 格式错误: {date_value}") - elif date_value: - print(f" [ERROR] {date_field} 格式错误: {date_value}") - - # 人员信息结构检查 - person_info = result.get("person_info") - if person_info: - if isinstance(person_info, list): - print(f"\n[人员信息] 共 {len(person_info)} 人") - for i, person in enumerate(person_info, 1): - pid = person.get("person_id", "") - pname = person.get("person_name", "") - print(f" 人员 {i}: {pname} ({pid})") - elif isinstance(person_info, dict): - print("\n[人员信息] 单人格式") - print(f" 姓名: {person_info.get('person_name', '')}") - print(f" 编号: {person_info.get('person_id', '')}") - - except Exception as e: - print(f"\n[EXCEPTION] 提取失败: {e}") - import traceback - - traceback.print_exc() - - -def test_cache_comparison(file_path: Path) -> None: - """对比原始提取和缓存结果""" - cache_dir = file_path.parent / ".invoice_cache" - cache_file = cache_dir / f"{file_path.stem}{file_path.suffix}.json" - - if not cache_file.exists(): - print(f"\n[INFO] 无缓存文件对比: {cache_file}") - return - - print("\n" + "=" * 60) - print("[缓存对比]") - print("=" * 60) - - try: - with open(cache_file, encoding="utf-8") as f: - cache_data = json.load(f) - - cached_result = cache_data.get("extracted_data", {}) - print("\n[缓存数据]") - print(json.dumps(cached_result, ensure_ascii=False, indent=2)) - - # 对比关键字段 - print("\n[字段对比]") - for key in ["invoice_type", "project_name", "purpose", "start_date", "end_date"]: - cached_value = cached_result.get(key, "<缺失>") - print(f" {key}: {cached_value}") - - except Exception as e: - print(f"[ERROR] 读取缓存失败: {e}") - - -def main() -> None: - parser = argparse.ArgumentParser(description="测试事前申请单信息提取") - parser.add_argument( - "--file", - type=str, - default=None, - help="要测试的文件路径 (默认: 扫描 scripts/data 目录)", - ) - parser.add_argument( - "--dir", - type=str, - default=str(ROOT / "scripts" / "data"), - help="扫描目录下所有事前申请单文件 (默认: scripts/data)", - ) - parser.add_argument( - "--no-cache-compare", - action="store_true", - help="不执行缓存对比", - ) - - args = parser.parse_args() - - if args.dir: - # 目录模式:扫描所有PDF文件 - dir_path = Path(args.dir) - if not dir_path.exists(): - print(f"目录不存在: {dir_path}") - sys.exit(1) - - pdf_files = sorted(dir_path.glob("*.pdf")) - if not pdf_files: - print(f"目录下没有找到PDF文件: {dir_path}") - sys.exit(1) - - print(f"发现 {len(pdf_files)} 个PDF文件,开始逐个测试...\n") - for pdf in pdf_files: - if "事前申请" in pdf.name or "申请单" in pdf.name: - test_single_file(pdf) - if not args.no_cache_compare: - test_cache_comparison(pdf) - print() - else: - # 单文件模式 - file_path = Path(args.file) - test_single_file(file_path) - if not args.no_cache_compare: - test_cache_comparison(file_path) - - print("\n测试完成!") - - -if __name__ == "__main__": - main() diff --git a/scripts/test_multimodal.py b/scripts/test_multimodal.py deleted file mode 100644 index 34a5a68..0000000 --- a/scripts/test_multimodal.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -"""测试 PDF 多模态提取完整链路 - -用法: - python scripts/test_multimodal.py -""" - -import io -import sys -from pathlib import Path - -# Windows 终端强制 UTF-8 -sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") - -ROOT = Path(__file__).resolve().parent.parent -sys.path.insert(0, str(ROOT)) # noqa: E402 - -from src.core.extraction import extract_document # noqa: E402 -from src.infra.documents.pdf import render_pdf_to_images # noqa: E402 - - -def test_render() -> None: - """测试 PDF 渲染""" - pdf_path = ROOT / "事前申请单.pdf" - if not pdf_path.exists(): - print(f"跳过: {pdf_path.name} 不存在") - return - - print("=" * 60) - print("测试 PDF 渲染") - print("=" * 60) - try: - images = render_pdf_to_images(pdf_path, dpi=150) - if images: - print(f"成功渲染 {len(images)} 页") - for i, img_b64 in enumerate(images): - print(f" 第 {i + 1} 页: base64 长度 {len(img_b64)} 字符") - else: - print("渲染返回空列表!") - except ImportError as e: - print(f"导入失败: {e}") - except Exception as e: - print(f"渲染异常: {e}") - - -def test_multimodal_extract() -> None: - """测试完整的多模态提取链路""" - pdf_path = ROOT / "事前申请单.pdf" - if not pdf_path.exists(): - print(f"跳过: {pdf_path.name} 不存在") - return - - print() - print("=" * 60) - print("测试多模态提取") - print("=" * 60) - try: - result = extract_document(pdf_path) - if result: - print("提取成功:") - import json - - print(json.dumps(result, ensure_ascii=False, indent=2)) - else: - print("提取返回空字典!") - except Exception as e: - print(f"提取异常: {e}") - import traceback - - traceback.print_exc() - - -def main() -> None: - test_render() - test_multimodal_extract() - print() - print("测试完成!") - - -if __name__ == "__main__": - main() diff --git a/scripts/test_travel_info.py b/scripts/test_travel_info.py deleted file mode 100644 index cf1049c..0000000 --- a/scripts/test_travel_info.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -"""直接测试 extract_travel_info 函数 - -所有数据从 .invoice_cache 缓存中自动加载,无需手动构造样本数据。 - -用法: - python scripts/test_travel_info.py -""" - -import json -import sys -from pathlib import Path - -# 项目根目录 -ROOT = Path(__file__).resolve().parent.parent -sys.path.insert(0, str(ROOT)) # noqa: E402 - -from src.core.extraction import extract_travel_info # noqa: E402 - - -def main() -> None: - source_dir = ROOT / "scripts" / "data" - - if not source_dir.exists(): - print(f"源文件目录不存在: {source_dir}") - sys.exit(1) - - print("=" * 60) - print("测试 extract_travel_info(数据来自缓存)") - print("=" * 60) - print(f"源文件目录: {source_dir}") - print() - - try: - result = extract_travel_info( - source_dir=source_dir, - ) - print() - print("=" * 60) - print("提取结果:") - print("=" * 60) - print(json.dumps(result, ensure_ascii=False, indent=2)) - print() - print("测试通过!") - except Exception as e: - print(f"测试失败: {e}") - import traceback - - traceback.print_exc() - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/src/config.py b/src/config.py index 45d2238..1a6b3bd 100644 --- a/src/config.py +++ b/src/config.py @@ -1,28 +1,17 @@ """ 配置加载 -从 scripts/data/config.json 读取用户配置,从环境变量读取服务端配置(LLM + 系统 URL)。 +从 scripts/data/config.json 读取用户配置,环境变量可覆盖系统 URL 与用户字段。 """ import json import os from pathlib import Path -from typing import Any # 项目根 = src/config.py 的上上级(曾多写一层 parent,导致解析到项目之外、凭据恒为空) _CONFIG_PATH = Path(__file__).resolve().parent.parent / "scripts" / "data" / "config.json" # 会话级配置允许覆盖的用户相关字段白名单(含密码) -SESSION_CONFIG_KEYS = frozenset( - { - "username", - "password", - "default_name", - "default_card_no", - "default_person_id", - "consumable_storage", - } -) # 前端安全的配置字段白名单(不含密码) SAFE_CONFIG_KEYS = frozenset( @@ -31,7 +20,6 @@ SAFE_CONFIG_KEYS = frozenset( "default_name", "default_card_no", "default_person_id", - "consumable_storage", } ) @@ -59,7 +47,6 @@ def _read_config() -> dict[str, str | Path]: "default_name": raw.get("default_name", ""), "default_card_no": raw.get("default_card_no", ""), "default_person_id": raw.get("default_person_id", ""), - "consumable_storage": raw.get("consumable_storage", "躬行楼 C205"), "upload_pin": os.environ.get("UPLOAD_PIN") or raw.get("upload_pin", ""), "attachment_dir": project_root / "attachments", } @@ -77,29 +64,3 @@ def clear_config_cache() -> None: """清除配置缓存(测试或配置变更时调用)""" global _config_cache _config_cache = None - - -def load_session_config(session_dir: Path) -> dict[str, Any]: - """加载会话配置,合并项目全局配置与会话级配置 - - 仅允许覆盖用户相关字段(白名单),防止用户上传的 config.json - 覆盖 sso_login_url、portal_url 等系统级配置。 - """ - config = load_config() - cfg_path = session_dir / "config.json" - if cfg_path.exists(): - with open(cfg_path, encoding="utf-8") as f: - session_cfg = json.load(f) - for key in SESSION_CONFIG_KEYS: - if key in session_cfg: - config[key] = session_cfg[key] - return config - - -def get_llm_config() -> dict[str, str]: - """加载 LLM 配置,优先从环境变量读取,缺失字段使用默认值""" - return { - "model": os.environ.get("LLM_MODEL", "qwen-vl-max"), - "api_base": os.environ.get("LLM_API_BASE", "http://localhost:8080/v1"), - "api_key": os.environ.get("LLM_API_KEY", "lm-studio"), - } diff --git a/src/core/extraction/__init__.py b/src/core/extraction/__init__.py index 22cc17a..75fddd0 100644 --- a/src/core/extraction/__init__.py +++ b/src/core/extraction/__init__.py @@ -1,40 +1,28 @@ -"""信息提取模块 +"""信息存取层(无 LLM) -提供发票/文档结构化提取、LLM 辅助提取等功能。 +抽取由宿主 Agent 完成:本模块只负责缓存读写、PDF 页面渲染与文本层提取。 """ -from .extractor import ( - EXTRACTION_PARALLEL_COUNT, - FILE_EVENTS_LOG, - SUPPORTED_EXTENSIONS, - extract_invoices, -) -from .llm_extractor import ( +from .cache import ( CACHE_DIR_NAME, - _image_to_base64, - build_extraction_user_message, - extract_document, - extract_normal_info, - extract_travel_info, - llm_query_text, + extraction_path, load_cache, load_match_result, - parse_json_response, + save_extraction, + save_json, + save_match_result, ) +from .render import detect_content_rotation, prepare_image, render_pdf __all__ = [ "CACHE_DIR_NAME", - "_image_to_base64", - "build_extraction_user_message", - "extract_document", - "extract_normal_info", - "extract_travel_info", + "detect_content_rotation", + "extraction_path", "load_cache", "load_match_result", - "llm_query_text", - "parse_json_response", - "EXTRACTION_PARALLEL_COUNT", - "FILE_EVENTS_LOG", - "SUPPORTED_EXTENSIONS", - "extract_invoices", + "prepare_image", + "render_pdf", + "save_extraction", + "save_json", + "save_match_result", ] diff --git a/src/core/extraction/extractor.py b/src/core/extraction/extractor.py deleted file mode 100644 index 670f2c2..0000000 --- a/src/core/extraction/extractor.py +++ /dev/null @@ -1,335 +0,0 @@ -"""发票提取编排 - -统一扫描目录下所有文件(PDF + 图片),通过 LLM 提取结构化数据, -根据 LLM 返回的「invoice_type」字段自动分类为发票/支付记录/出差事前申请单。 - -对外接口: - extract_invoices(directory) -> tuple[list[dict], list[dict], dict] - -错误传播规则: - - 单个文件提取失败: 记录日志 + SSE error 事件,继续处理下一个文件 - - 全部文件提取失败: raise ExtractionError,携带失败文件列表和原始错误 - - 缓存读取失败: 静默降级,尝试重新提取 - -SSE 文件进度事件: - 在 source_dir 下写入 file_events.log,每行一个 JSON 对象: - - {"type": "file_progress", "file": "...", "status": "processing"} - - {"type": "file_progress", "file": "...", "status": "done", "summary": {...}} - - {"type": "file_progress", "file": "...", "status": "cached"} - - {"type": "file_progress", "file": "...", "status": "error", "error": "..."} -""" - -import json -import os -from concurrent.futures import ThreadPoolExecutor, as_completed -from pathlib import Path -from typing import Any - -from ... import get_logger -from ...core.matching import match_invoices_to_cards -from ...exceptions import ExtractionError -from ...infra.documents.invoice import CACHE_DIR_NAME, classify_invoice_batch -from .cache import save_match_result as _save_match_result -from .llm_extractor import extract_document - -log = get_logger("extractor") - -# SSE 文件进度事件日志文件名 -FILE_EVENTS_LOG = "file_events.log" - -# 并行提取文件数,可通过环境变量 EXTRACTION_PARALLEL_COUNT 配置 -EXTRACTION_PARALLEL_COUNT = int(os.environ.get("EXTRACTION_PARALLEL_COUNT", "3")) - -# 支持的文件扩展名 -SUPPORTED_EXTENSIONS = {".pdf", ".png", ".jpg", ".jpeg", ".bmp", ".webp"} - - -def _emit_file_event(source_dir: Path, file_name: str, status: str, **kwargs: Any) -> None: - """向 file_events.log 追加一行 JSON 事件(线程安全,失败时静默忽略)""" - event = { - "type": "file_progress", - "file": file_name, - "status": status, - **kwargs, - } - try: - event_path = source_dir / FILE_EVENTS_LOG - with open(event_path, "a", encoding="utf-8") as f: - f.write(json.dumps(event, ensure_ascii=False) + "\n") - except Exception: - pass - - -def _get_cache_dir(source_dir: Path) -> Path: - """获取缓存目录路径""" - cache_dir = source_dir / CACHE_DIR_NAME - cache_dir.mkdir(exist_ok=True) - return cache_dir - - -def _get_json_path(file_path: Path, cache_dir: Path) -> Path: - """根据文件路径生成对应的 JSON 缓存路径(包含后缀名以区分同名的 PDF/图片)""" - return cache_dir / f"{file_path.stem}{file_path.suffix}.json" - - -def _save_to_cache(file_path: Path, extracted_data: dict[str, Any], cache_dir: Path) -> Path: - """将提取结果保存到 JSON 缓存文件,并记录源文件路径和后缀名""" - json_path = _get_json_path(file_path, cache_dir) - cache_data = { - "source_file": str(file_path), - "source_filename": file_path.name, - "source_extension": file_path.suffix.lower(), - "extracted_data": extracted_data, - } - with open(json_path, "w", encoding="utf-8") as f: - json.dump(cache_data, f, ensure_ascii=False, indent=2) - log.info(f"提取结果已缓存: {json_path.name}") - return json_path - - -def _load_from_cache(json_path: Path, expected_extension: str | None = None) -> dict[str, Any] | None: - """从 JSON 缓存文件加载提取结果,可选校验后缀名一致性""" - if not json_path.exists(): - return None - try: - with open(json_path, encoding="utf-8") as f: - cache_data: dict[str, Any] = json.load(f) - # 校验后缀名是否一致,防止同名不同后缀的文件误命中缓存 - if expected_extension and cache_data.get("source_extension", "").lower() != expected_extension.lower(): - return None - result: dict[str, Any] | None = cache_data.get("extracted_data") - return result - except Exception as e: - log.warning(f"读取缓存失败 {json_path.name}: {e}") - return None - - -def _build_summary(data: dict[str, Any]) -> dict[str, str]: - """从提取结果构建前端展示摘要(人类可读格式)。 - - 返回所有非内部字段(排除 _source_file 等下划线前缀字段), - 并将 invoice_type 转换为中文标签。列表/字典类型的值会展开为可读文本。 - """ - invoice_type_map = { - "train": "高铁票", - "hotel": "酒店住宿", - "general": "普通发票", - "payment": "支付记录", - "application": "出差申请单", - "attachments": "其他附件", - "note": "易耗品单", - } - - summary = {} - for key, value in data.items(): - # 跳过内部字段 - if key.startswith("_"): - continue - # 跳过空值 - if value is None or value == "": - continue - # invoice_type 转为中文标签 - if key == "invoice_type": - summary["invoice_type_label"] = invoice_type_map.get(str(value), str(value)) - elif isinstance(value, list): - # 列表展开为多行可读文本 - if len(value) == 0: - continue - parts = [] - for item in value: - if isinstance(item, dict): - # 字典项用 "key: value" 格式拼接 - pair_parts = [f"{k}: {v}" for k, v in item.items()] - parts.append(" | ".join(pair_parts)) - else: - parts.append(str(item)) - summary[key] = "\n".join(parts) - elif isinstance(value, dict): - # 字典展开为 "key: value" 格式 - pair_parts = [f"{k}: {v}" for k, v in value.items()] - summary[key] = " | ".join(pair_parts) - else: - summary[key] = str(value) - - return summary - - -def _extract_document( - file_path: Path, - cache_dir: Path, - source_dir: Path, -) -> tuple[dict[str, str] | None, str | None]: - """提取单个文件的结构化信息,优先使用缓存。 - - 根据 LLM 返回的「invoice_type」字段自动分类: - - "payment" -> 支付记录 - - "application" -> 申请单 - - 有 "invoice_number" -> 发票 - - 其他 -> 无法识别 - - Args: - file_path: 文件路径(PDF 或图片)。 - cache_dir: JSON 缓存目录。 - source_dir: 源目录(用于写入 SSE 进度事件)。 - - Returns: - (提取结果字典或 None, 错误信息或 None)。 - """ - file_name = file_path.name - - json_path = _get_json_path(file_path, cache_dir) - cached = _load_from_cache(json_path, expected_extension=file_path.suffix.lower()) - if cached: - cached["_source_file"] = file_name - log.info(f"使用缓存: {file_name}") - _emit_file_event(source_dir, file_name, "cached") - return cached, None - - # 发送处理中事件 - _emit_file_event(source_dir, file_name, "processing") - - log.info(f"使用多模态提取: {file_name}") - try: - result = extract_document(file_path) - if result: - result["_source_file"] = file_name - _save_to_cache(file_path, result, cache_dir) - - # 发送完成事件(含摘要) - summary = _build_summary(result) - _emit_file_event(source_dir, file_name, "done", summary=summary) - - return result, None - except Exception as e: - err_msg = str(e) - log.warning(f"多模态提取失败: {file_name} ({err_msg})") - _emit_file_event(source_dir, file_name, "error", error=err_msg) - return None, err_msg - - return None, None - - -def _find_all_files(directory: str) -> list[Path]: - """扫描目录下所有支持的文件(PDF + 图片)""" - dir_path = Path(directory) - files = [f for f in dir_path.iterdir() if f.is_file() and f.suffix.lower() in SUPPORTED_EXTENSIONS] - return sorted(files) - - -def extract_invoices( - directory: str = ".", -) -> tuple[list[dict[str, str]], list[dict[str, str]], dict[str, list[dict[str, str]]]]: - """扫描目录下所有文件,提取信息并匹配支付记录 - - 统一使用 LLM 提取,根据返回的「invoice_type」自动分类: - - 发票(有 invoice_number)-> 参与金额匹配 - - 支付记录(invoice_type="payment")-> 参与金额匹配 - - 出差事前申请单 -> 单独存储,不参与匹配 - - Returns: - 元组 (payment_records, applications, groups): - - payment_records: 支付记录列表(每项含关联发票 _matched_invoices) - - applications: 出差事前申请单列表 - - groups: 按文档类型分组的字典 - {'travel': [差旅发票], 'general': [普通发票], 'application': [出差事前申请单], - 'attachments': [其他附件]} - - Raises: - ExtractionError: 当所有文件提取均失败时抛出,携带失败文件列表和原始错误。 - """ - source_dir = Path(directory) - cache_dir = _get_cache_dir(source_dir) - - # 清空上次的文件进度事件 - try: - (source_dir / FILE_EVENTS_LOG).unlink(missing_ok=True) - except Exception: - pass - - all_files = _find_all_files(directory) - if not all_files: - log.warning("未找到支持的文件") - return [], [], {"travel": [], "general": [], "application": [], "attachments": []} - - log.info(f"发现 {len(all_files)} 个文件") - - all_invoices = [] - all_cards = [] - applications = [] - attachments = [] - # 记录失败文件及其错误信息 - failed_files: list[tuple[str, str]] = [] - - max_workers = max(1, EXTRACTION_PARALLEL_COUNT) - with ThreadPoolExecutor(max_workers=max_workers) as executor: - future_to_file = {executor.submit(_extract_document, fp, cache_dir, source_dir): fp for fp in all_files} - - for future in as_completed(future_to_file): - file_path = future_to_file[future] - try: - result, err = future.result() - except Exception as e: - err_msg = str(e) - failed_files.append((file_path.name, err_msg)) - log.warning(f"未能解析: {file_path.name} ({err_msg})") - continue - - if not result: - if err: - failed_files.append((file_path.name, err)) - log.warning(f"未能解析: {file_path.name}") - continue - - inv_type = result.get("invoice_type", "") - - if inv_type == "application": - applications.append(result) - log.info(f"[{inv_type}] 已解析: {file_path.name}") - elif inv_type == "payment": - all_cards.append(result) - log.info(f"[{inv_type}] 已解析: {file_path.name}") - elif inv_type in ("attachments", "note"): - attachments.append(result) - log.info(f"[{inv_type}] 已解析: {file_path.name}") - elif result.get("invoice_number"): - all_invoices.append(result) - log.info(f"[{inv_type}] 已解析: {file_path.name}") - else: - log.warning(f"无法分类: {file_path.name} (invoice_type={inv_type})") - - # 全部文件提取失败时抛出异常,携带原始错误信息 - if failed_files and not all_invoices and not all_cards and not applications: - failed_names = [name for name, _ in failed_files] - error_details = {name: err for name, err in failed_files} - raise ExtractionError( - f"所有 {len(all_files)} 个文件提取均失败", - failed_files=failed_names, - details=error_details, - ) - - log.info( - f"分类结果: 发票 {len(all_invoices)} 张, 支付记录 {len(all_cards)} 条, 申请单 {len(applications)} 份, 附件 {len(attachments)} 个" - ) - - if not all_invoices: - log.warning("未成功解析任何发票") - - payment_records = match_invoices_to_cards(all_invoices, all_cards) - _save_match_result(payment_records, cache_dir) - - # 构建分类 - all_documents: list[dict[str, str]] = [] - for record in payment_records: - all_documents.extend(record.get("_matched_invoices", [])) - all_documents.extend(applications) - - groups = classify_invoice_batch(all_documents) - groups["attachments"] = attachments - log.info( - f"文档分类: 差旅发票 {len(groups['travel'])} 张, " - f"普通发票 {len(groups['general'])} 张, " - f"出差事前申请单 {len(groups['application'])} 份, " - f"附件 {len(groups['attachments'])} 个" - ) - - return payment_records, applications, groups diff --git a/src/core/extraction/llm_extractor.py b/src/core/extraction/llm_extractor.py deleted file mode 100644 index de2c74d..0000000 --- a/src/core/extraction/llm_extractor.py +++ /dev/null @@ -1,506 +0,0 @@ -"""LLM 信息提取 - -使用 LLM 从 PDF 文本/图片、支付截图中提取结构化数据。 - -## 功能模块 - -- **统一文档提取**:使用一套提示词,LLM 自行判断文档类型(发票/支付记录/出差事前申请单等),支持 JSON 格式输出。 -- **差旅信息提取**:综合多张发票、支付记录和匹配结果,提取出差事由、地点、时间等差旅相关信息。 -- **缓存管理**:支持从 `.invoice_cache/` 目录加载已提取的结构化数据和匹配结果,避免重复处理。 -- **SSE 流式事件**:`extract_travel_info` 和 `extract_normal_info` 在调用 LLM 时,向 `source_dir/llm_stream.log` 写入流式事件(start/reasoning/chunk/end/error),前端通过 SSE 实时展示 AI 思考过程与正式回答。 - -## 对外接口 - -- `extract_document(file_path) -> dict` — 统一入口:从任意图片/PDF 提取信息 -- `extract_travel_info(source_dir) -> dict` — 综合发票和匹配结果提取差旅信息 -- `extract_normal_info(source_dir) -> dict` — 提取普通发票报销信息 -- `load_cache(source_dir) -> dict` — 加载缓存的结构化数据 -- `load_match_result(source_dir) -> dict` — 加载发票与支付记录的匹配结果 -- `llm_query_text(system_prompt, text, source_dir) -> str` — 纯文本 LLM 查询(供 Agent 调度使用) -- `parse_json_response(text) -> dict` — 从 LLM 响应中提取 JSON(供 Agent 调度使用) -- `build_extraction_user_message(cache_map, match_result) -> str` — 构建提取请求的用户消息(供 Agent 调度使用) - -## SSE 流式事件协议 - -`llm_stream.log` 每行一个 JSON 对象: - -- `{"type": "llm_stream", "phase": "start", "label": "..."}` — LLM 调用开始 -- `{"type": "llm_stream", "phase": "reasoning", "text": "..."}` — 模型原生推理/思考片段(来自 `thinking_delta`) -- `{"type": "llm_stream", "phase": "chunk", "text": "..."}` — 流式文本片段(正式回答) -- `{"type": "llm_stream", "phase": "end", "label": "..."}` — LLM 调用结束 -- `{"type": "llm_stream", "phase": "error", "error": "..."}` — LLM 调用失败 -""" - -from __future__ import annotations - -import base64 -import json -from pathlib import Path -from typing import Any, cast - -from ... import get_logger -from ...infra.documents.invoice import CACHE_DIR_NAME -from ...infra.llm.prompt import ( - build_invoice_system_prompt, - build_normal_info_system_prompt, - build_travel_info_system_prompt, -) - -log = get_logger("llm_extractor") - -# Re-export CACHE_DIR_NAME for convenience -__all__ = [ - "CACHE_DIR_NAME", - "extract_document", - "extract_travel_info", - "extract_normal_info", - "load_cache", - "load_match_result", - "llm_query_text", - "parse_json_response", - "build_extraction_user_message", -] - -# SSE LLM 流式事件日志文件名 -LLM_STREAM_LOG = "llm_stream.log" - - -def _emit_llm_stream(source_dir: Path, phase: str, **kwargs: Any) -> None: - """向 llm_stream.log 追加一行 JSON 事件(线程安全,失败时静默忽略) - - Args: - source_dir: 会话目录路径。 - phase: 事件阶段 ("start" / "chunk" / "end" / "error")。 - **kwargs: 额外字段 (text, label, error 等)。 - """ - event = {"type": "llm_stream", "phase": phase, **kwargs} - try: - event_path = source_dir / LLM_STREAM_LOG - with open(event_path, "a", encoding="utf-8") as f: - f.write(json.dumps(event, ensure_ascii=False) + "\n") - except Exception: - pass - - -def _create_llm() -> Any: - """根据配置文件创建 LLM 实例。""" - try: - from llama_index.llms.openai_like import OpenAILike - except ImportError: - log.error("缺少 llama-index-llms-openai-like,请执行: uv pip install llama-index-llms-openai-like") - raise - - from ...config import get_llm_config - - llm_config = get_llm_config() - return OpenAILike( - model=llm_config["model"], - api_base=llm_config["api_base"], - api_key=llm_config.get("api_key", "lm-studio"), - temperature=0.1, - max_tokens=65535, - request_timeout=600.0, - is_chat_model=True, - ) - - -def parse_json_response(text: str) -> dict[str, Any]: - """从 LLM 响应中提取 JSON,处理可能的 Markdown 包裹。 - - Args: - text: LLM 响应文本。 - - Returns: - 解析后的字典。 - """ - text = text.strip() - - # 处理 ```json ... ``` 包裹 - if "```" in text: - start = text.find("```") + 3 - end = text.find("```", start) - if end > start: - text = text[start:end].strip() - - # 去掉可能的前缀 (如 "json") - if text.lower().startswith("json"): - text = text[4:].strip() - - return cast(dict[str, Any], json.loads(text)) - - -# ------------------------------------------------------------------ -# 统一文档提取(多模态,直接传图片给 LLM) -# ------------------------------------------------------------------ - - -def _image_to_base64(image_path: Path) -> str: - """将图片文件读取为 base64 字符串。""" - with open(image_path, "rb") as f: - return base64.b64encode(f.read()).decode("utf-8") - - -def _stream_llm_response( - llm: Any, - messages: list[Any], - source_dir: Path | None, - reasoning_effort: str, - log_label: str = "LLM", -) -> str: - """流式调用 LLM 并写入 SSE 事件(供 llm_query_text 和 _llm_query_multimodal 共用)。 - - Args: - llm: LLM 实例。 - messages: 消息列表。 - source_dir: 会话目录(可选,传入时启用 SSE 流式事件写入)。 - reasoning_effort: 推理努力级别。 - log_label: 日志标签(用于区分"纯文本"和"多模态")。 - - Returns: - LLM 响应文本。 - """ - try: - if source_dir: - _emit_llm_stream(source_dir, "start", label="正在分析文件...") - - parts = [] - for resp in llm.stream_chat( - messages, - temperature=0.1, - extra_body={"reasoning_effort": reasoning_effort}, - ): - delta = resp.delta - if delta: - parts.append(delta) - if source_dir: - _emit_llm_stream(source_dir, "chunk", text=delta) - - thinking = getattr(resp, "additional_kwargs", {}) or {} - thinking_delta = thinking.get("thinking_delta", "") - if thinking_delta and source_dir: - _emit_llm_stream(source_dir, "reasoning", text=thinking_delta) - - text = "".join(parts) - log.info("%s请求完成,响应总长度: %d 字符", log_label, len(text)) - if source_dir: - _emit_llm_stream(source_dir, "end", label="分析完成") - return text - except Exception as e: - log.error("%s请求失败: %s", log_label, e) - if source_dir: - _emit_llm_stream(source_dir, "error", error=str(e)) - raise - - -def llm_query_text( - system_prompt: str, - text: str, - reasoning_effort: str = "none", - source_dir: Path | None = None, -) -> str: - """发送纯文本请求到 LLM(供 Agent 调度使用)。 - - Args: - system_prompt: 系统提示词。 - text: 用户文本。 - reasoning_effort: 推理努力级别。 - source_dir: 会话目录(可选,传入时启用 SSE 流式事件写入)。 - - Returns: - LLM 响应文本。 - """ - from llama_index.core.base.llms.types import TextBlock - from llama_index.core.llms import ChatMessage - - from ...config import get_llm_config - - messages = [ - ChatMessage(role="system", content=system_prompt), - ChatMessage(role="user", blocks=[TextBlock(text=text)]), - ] - - llm_config = get_llm_config() - llm = _create_llm() - log.info( - "开始请求 LLM (model=%s, base=%s)", - llm_config["model"], - llm_config["api_base"], - ) - - return _stream_llm_response(llm, messages, source_dir, reasoning_effort, log_label="LLM") - - -def extract_document(file_path: Path) -> dict[str, Any]: - """统一文档提取入口:从任意图片/PDF 中提取结构化信息。 - - LLM 会根据统一提示词自行判断文档类型(发票/支付记录/出差事前申请单等)。 - - Args: - file_path: 文件路径(支持 PDF 和图片格式)。 - - Returns: - 包含提取字段的字典。 - """ - from ...infra.documents.pdf import render_pdf_to_images - - system_prompt = build_invoice_system_prompt() - user_text = f"请分析以下财务文档并提取信息:\n\n文件名: {file_path.name}" - - # PDF 先渲染为图片 - suffix = file_path.suffix.lower() - if suffix == ".pdf": - image_b64s = render_pdf_to_images(file_path) - else: - image_b64s = [_image_to_base64(file_path)] - - if not image_b64s: - log.warning(f"文件渲染为空: {file_path.name}") - return {} - - try: - response = _llm_query_multimodal(system_prompt, user_text, image_b64s) - result = parse_json_response(response) - log.info("LLM 文档提取成功: %s", file_path.name) - return result - except Exception as e: - log.error("LLM 文档提取失败: %s (%s)", file_path.name, e) - raise - - -def _llm_query_multimodal( - system_prompt: str, - text: str | None = None, - image_b64s: list[str] | None = None, - blocks: list[Any] | None = None, - reasoning_effort: str = "none", - source_dir: Path | None = None, -) -> str: - """发送多模态请求到 LLM(内部使用)。 - - Args: - system_prompt: 系统提示词。 - text: 用户文本(与 image_b64s 配合使用,文本在前、图片在后)。 - image_b64s: base64 编码的图片列表。 - blocks: 预构建的内容块列表(TextBlock/ImageBlock),传入时忽略 text 和 image_b64s。 - source_dir: 会话目录(可选,传入时启用 SSE 流式事件写入)。 - - Returns: - LLM 响应文本。 - """ - from llama_index.core.base.llms.types import ImageBlock, TextBlock - from llama_index.core.llms import ChatMessage - - from ...config import get_llm_config - - if blocks is not None: - final_blocks = blocks - else: - text = text or "" - image_b64s = image_b64s or [] - final_blocks = [TextBlock(text=text)] - for img_b64 in image_b64s: - final_blocks.append( - ImageBlock( - url=f"data:image/jpeg;base64,{img_b64}", - detail="high", - ) - ) - - messages = [ - ChatMessage(role="system", content=system_prompt), - ChatMessage(role="user", blocks=final_blocks), - ] - - llm_config = get_llm_config() - llm = _create_llm() - log.info( - "开始请求 LLM 多模态 (model=%s, base=%s, blocks=%d)", - llm_config["model"], - llm_config["api_base"], - len(final_blocks), - ) - - return _stream_llm_response(llm, messages, source_dir, reasoning_effort, log_label="LLM多模态") - - -def load_cache(source_dir: Path) -> dict[str, Any]: - """从 JSON 缓存目录加载结构化数据,构建 source filename -> 缓存数据的映射。 - - Args: - source_dir: 源文件目录(包含 .invoice_cache 子目录)。 - - Returns: - {source_filename: extracted_data} 字典。 - 额外包含 "travel_info" 键(如果 travel_info.json 存在)。 - """ - cache_map: dict[str, Any] = {} - cache_dir = source_dir / CACHE_DIR_NAME - if not cache_dir.exists(): - return cache_map - - for json_path in sorted(cache_dir.glob("*.json")): - try: - with open(json_path, encoding="utf-8") as f: - cache_data = json.load(f) - - if json_path.name in ("travel_info.json", "normal_info.json"): - cache_map[json_path.name.replace(".json", "")] = cache_data - continue - - extracted = cache_data.get("extracted_data", {}) - src_file = extracted.get("_source_file", "") - if src_file: - cache_map[src_file] = extracted - except Exception as e: - log.warning(f"读取缓存失败 {json_path.name}: {e}") - - return cache_map - - -def load_match_result(source_dir: Path) -> dict[str, list[dict[str, Any]]]: - """从 JSON 缓存目录加载发票与支付记录的匹配结果。 - - Args: - source_dir: 源文件目录(包含 .invoice_cache 子目录)。 - - Returns: - {支付记录源文件 (含金额): [发票信息列表]} 字典。 - 每个发票信息包含 file, type, amount 字段。 - """ - cache_dir = source_dir / CACHE_DIR_NAME - match_path = cache_dir / "match_result.json" - if not match_path.exists(): - return {} - - try: - with open(match_path, encoding="utf-8") as f: - result: dict[str, list[dict[str, Any]]] = json.load(f) - return result - except Exception as e: - log.warning(f"读取匹配结果缓存失败: {e}") - return {} - - -def build_extraction_user_message( - cache_map: dict[str, Any], - match_result: dict[str, list[dict[str, Any]]], - previous_analysis: dict[str, Any] | None = None, - user_supplement: str | None = None, -) -> str: - """构建提取请求的用户消息(供 Agent 调度使用)。 - - Args: - cache_map: 缓存数据映射。 - match_result: 匹配结果。 - previous_analysis: 上一轮 LLM 分析结果(可选,补充文件时传入作为历史上下文)。 - user_supplement: 用户文字补充(可选,传入时作为修正上下文)。 - - Returns: - 拼接好的用户消息字符串。 - """ - parts = [ - "以下是本次报销的所有源文件及其提取出的结构化数据。" - "每个源文件的数据来自 OCR 识别和发票信息提取,已按文件名分组展示。" - ] - - if previous_analysis: - parts.append( - "【上一轮分析结果】" - "以下是上一轮 LLM 对已有文件的分析结果。" - "注意:用户可能已补充新文件,请综合所有数据(含新文件)重新分析。" - "如果新文件填补了之前的信息缺失,请相应更新分析结果。\n" - + json.dumps(previous_analysis, ensure_ascii=False, indent=2) - ) - - if user_supplement: - parts.append("【用户文字补充】用户补充了以下文字信息,请据此更新你的分析结果。\n" + user_supplement) - - if match_result: - parts.append( - "【发票与支付记录匹配结果】" - "以下数据已将发票信息与对应的支付记录进行关联匹配," - "用于判断每笔支付对应的发票和商户信息。\n" + json.dumps(match_result, ensure_ascii=False, indent=2) - ) - - for filename, extracted in cache_map.items(): - parts.append( - f"【源文件: {filename}】" - "以下为从该文件提取的结构化发票/支付/申请单数据。\n" + json.dumps(extracted, ensure_ascii=False, indent=2) - ) - - parts.append("\n=== 请返回 JSON 格式结果 ===") - return "\n".join(parts) - - -def _extract_info( - source_dir: Path | None, - system_prompt: str, - info_type: str, -) -> dict[str, Any]: - """通用的信息提取函数:加载缓存、构建消息、调用 LLM 并解析 JSON。 - - extract_travel_info 和 extract_normal_info 的公共实现。 - - Args: - source_dir: 源文件目录(必填,包含 .invoice_cache 子目录)。 - system_prompt: 系统提示词。 - info_type: 信息类型标签("差旅" 或 "普通发票"),用于日志。 - - Returns: - LLM 提取的结构化信息字典。 - """ - if not source_dir: - log.warning("未提供 source_dir,无法加载缓存数据") - return {} - - cache_map = load_cache(source_dir) - match_result = load_match_result(source_dir) - user_message = build_extraction_user_message(cache_map, match_result) - - log.info("开始构建%s信息提取请求,缓存条目: %d, 匹配结果: %d", info_type, len(cache_map), len(match_result)) - - try: - response = llm_query_text( - system_prompt=system_prompt, - text=user_message, - reasoning_effort="low", - source_dir=source_dir, - ) - result = parse_json_response(response) - log.info("LLM %s信息提取成功", info_type) - return result - except Exception as e: - log.error("LLM %s信息提取失败: %s", info_type, e) - raise - - -def extract_travel_info( - source_dir: Path | None = None, -) -> dict[str, Any]: - """根据差旅发票(bot 格式),让 LLM 提取出差相关信息。 - - 纯提取,不包含校验逻辑。校验由 Agent 层调度。 - - Args: - source_dir: 源文件目录(必填,包含 .invoice_cache 子目录)。 - - Returns: - 包含出差事由、地点、交通工具、时间、住宿信息等字段的字典。 - """ - return _extract_info(source_dir, build_travel_info_system_prompt(), "差旅") - - -def extract_normal_info( - source_dir: Path | None = None, -) -> dict[str, Any]: - """根据普通发票(非差旅),让 LLM 提取报销相关信息。 - - 纯提取,不包含校验逻辑。校验由 Agent 层调度。 - - Args: - source_dir: 源文件目录(必填,包含 .invoice_cache 子目录)。 - - Returns: - 包含报销说明、发票总数、总金额、支付方式、附件清单等字段的字典。 - """ - return _extract_info(source_dir, build_normal_info_system_prompt(), "普通发票") diff --git a/src/core/matching/runner.py b/src/core/matching/runner.py index 6e67c97..9ab6a12 100644 --- a/src/core/matching/runner.py +++ b/src/core/matching/runner.py @@ -10,14 +10,13 @@ from pathlib import Path from typing import Any from ... import get_logger -from ...core.extraction.cache import load_cache, save_match_result +from ...core.extraction.cache import load_cache, save_json, save_match_result from ...infra.documents.invoice import ( classify_invoice_batch, save_application_json, save_csv, save_invoice_csv, ) -from ...pipeline_core import save_invoice_groups from .matcher import match_invoices_to_cards log = get_logger("matching.runner") @@ -46,6 +45,20 @@ def split_cache(cache_map: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: return buckets +def save_invoice_groups(session_dir: Path, groups: dict[str, list[dict[str, Any]]]) -> None: + """写 invoice_groups.json:完整分组 + 计数,供后续阶段与产物判断。""" + data = { + "travel": groups.get("travel", []), + "general": groups.get("general", []), + "application": groups.get("application", []), + "attachments": groups.get("attachments", []), + "travel_count": len(groups.get("travel", [])), + "general_count": len(groups.get("general", [])), + "application_count": len(groups.get("application", [])), + } + save_json(session_dir / "invoice_groups.json", data) + + def match_from_cache(case_dir: Path, tolerance: float | None = None) -> dict[str, Any]: """按缓存内容执行金额匹配,返回结构化结果。 diff --git a/src/exceptions.py b/src/exceptions.py index 3acfd16..ee6009d 100644 --- a/src/exceptions.py +++ b/src/exceptions.py @@ -2,14 +2,13 @@ 异常层次: ReimbursementError — 所有业务异常的基类 - ├── ExtractionError — 文档提取失败(单个文件/批量全失败) ├── BrowserError — 浏览器自动化失败 └── ValidationError — 校验失败(规则校验/语义校验) 使用规则: - 模块内部: 捕获具体异常 → 记录日志 → 截图(如适用) → re-raise - 模块边界: 不吞异常,向上传播 - - 顶层 (routes.py / orchestrator.py): 统一捕获 ReimbursementError + - MCP 工具层: 不把异常抛穿协议层,统一转成 {ok:false, reason, next_action} 信封 - 可恢复场景: 返回结构化结果而非 raise """ @@ -26,19 +25,6 @@ class ReimbursementError(Exception): self.details = details or {} -class ExtractionError(ReimbursementError): - """文档提取失败""" - - def __init__( - self, - message: str, - failed_files: list[str] | None = None, - details: dict[str, Any] | None = None, - ) -> None: - super().__init__(message, details) - self.failed_files = failed_files or [] - - class BrowserError(ReimbursementError): """浏览器自动化操作失败""" diff --git a/src/infra/documents/__init__.py b/src/infra/documents/__init__.py index b9cef34..6932619 100644 --- a/src/infra/documents/__init__.py +++ b/src/infra/documents/__init__.py @@ -1,13 +1,9 @@ """文档处理基础设施 -提供发票数据模型、PDF 渲染、出库单填写等功能。 +发票/支付 CSV、PDF 渲染、低值易耗品申购单(Excel COM)。 +出库单(Word)已随功能下线删除:见提交记录与 .agents/docs/plans/MCP化改造方案-2026-10-19.md。 """ -from .consumable import ( - CONSUMABLE_DOC_FILENAME, - fill_consumable_doc, - fill_consumable_from_template, -) from .invoice import ( CACHE_DIR_NAME, INVOICE_LEVEL_COLUMNS, @@ -24,25 +20,24 @@ from .requisition import ( REQUISITION_FILENAME, fill_requisition_excel, generate_requisition, + load_project_desc, load_requisition_items, ) __all__ = [ "CACHE_DIR_NAME", + "DEFAULT_PROJECT", "INVOICE_LEVEL_COLUMNS", "PAYMENT_RECORD_COLUMNS", - "classify_invoice_batch", - "load_csv", - "load_invoice_csv", - "save_csv", - "save_invoice_csv", - "save_application_json", - "CONSUMABLE_DOC_FILENAME", - "fill_consumable_doc", - "fill_consumable_from_template", "REQUISITION_FILENAME", - "DEFAULT_PROJECT", - "load_requisition_items", + "classify_invoice_batch", "fill_requisition_excel", "generate_requisition", + "load_csv", + "load_invoice_csv", + "load_project_desc", + "load_requisition_items", + "save_application_json", + "save_csv", + "save_invoice_csv", ] diff --git a/src/infra/documents/consumable.py b/src/infra/documents/consumable.py deleted file mode 100644 index 40895e3..0000000 --- a/src/infra/documents/consumable.py +++ /dev/null @@ -1,262 +0,0 @@ -"""将 invoice_summary.csv 填入「易耗品、出库单.doc」表格。 - -仅写入表格数据单元格,保留原模板字体、边框与版式。 -""" - -from __future__ import annotations - -import argparse -import re -import shutil -from datetime import date -from pathlib import Path -from typing import Any - -from ... import get_logger -from ...config import load_config -from .invoice import load_invoice_csv - -log = get_logger("fill_consumable_doc") - -CONSUMABLE_DOC_FILENAME = "易耗品、出库单.doc" - -# Word COM 常量 -WD_CHARACTER = 1 - -# 表格统一字体:宋体、五号(10.5 磅) -TABLE_FONT_NAME = "宋体" -TABLE_FONT_SIZE = 10.5 - - -def _split_name_spec(left: str) -> tuple[str, str]: - m = re.search(r"(\S+一批)\s*$", left) - if m: - return m.group(1), left[: m.start()].strip() - parts = left.split(" ", 1) - if len(parts) == 2: - return parts[0], parts[1] - return left, "" - - -def parse_spec_model(spec: str) -> dict[str, str]: - spec = (spec or "").strip() - if " 个 " not in spec: - return { - "product_name": spec, - "spec": "", - "unit": "", - "qty": "", - "unit_price": "", - } - - left, right = spec.split(" 个 ", 1) - product_name, model_spec = _split_name_spec(left.strip()) - tokens = right.split() - - qty = "" - unit_price = "" - if len(tokens) >= 4 and re.fullmatch(r"\d+(?:\.\d+)?", tokens[0]): - qty, unit_price = tokens[0], tokens[1] - elif tokens and re.fullmatch(r"\d+(?:\.\d+)?", tokens[0]): - qty, unit_price = "1", tokens[0] - - return { - "product_name": product_name, - "spec": model_spec, - "unit": "个", - "qty": qty, - "unit_price": unit_price, - } - - -def _format_money(value: str | float) -> str: - """单价、金额:固定保留两位小数。""" - if value is None or value == "": - return "" - try: - num = float(value) - except (TypeError, ValueError): - return str(value) - return f"{num:.2f}" - - -def _today_cn_date() -> str: - """当前日期,格式:2026年5月26日""" - today = date.today() - return f"{today.year}年{today.month}月{today.day}日" - - -def _apply_font(rng: Any) -> None: - """将范围字体设为宋体五号(含数字与英文)。""" - font = rng.Font - font.Name = TABLE_FONT_NAME - font.NameFarEast = TABLE_FONT_NAME - font.NameAscii = TABLE_FONT_NAME - font.NameOther = TABLE_FONT_NAME - font.NameBi = TABLE_FONT_NAME - font.Size = TABLE_FONT_SIZE - - -def _set_cell_value(cell: Any, text: str) -> None: - """写入单元格正文(不含末尾单元格标记)。""" - rng = cell.Range - rng.MoveEnd(WD_CHARACTER, -1) - rng.Text = "" if text is None else str(text) - _apply_font(rng) - - -def _normalize_table_font(tbl: Any) -> None: - """填写完成后统一整张表的字体。""" - for row in tbl.Rows: - for cell in row.Cells: - rng = cell.Range - rng.MoveEnd(WD_CHARACTER, -1) - _apply_font(rng) - - -def _replace_date_in_doc(doc: Any, new_date: str) -> None: - """仅替换表头段落中的日期文字,不改动段落其余部分。""" - if not new_date: - return - try: - para = doc.Paragraphs(3) - except Exception: - return - rng = para.Range - text = rng.Text.replace("\r", "").replace("\x07", "") - m = re.search(r"\d{4}年\d{1,2}月\d{1,2}日", text) - if not m: - return - start = rng.Start + m.start() - end = rng.Start + m.end() - doc.Range(Start=start, End=end).Text = new_date - - -def fill_consumable_doc( - csv_path: str | Path, - doc_path: str | Path, - config: dict[str, Any] | None = None, - backup: bool = True, -) -> Path: - csv_path = Path(csv_path) - doc_path = Path(doc_path) - if config is None: - config = load_config() - - invoices = load_invoice_csv(csv_path.parent / "invoice_summary.csv") or [] - - if backup: - bak = doc_path.with_suffix(doc_path.suffix + ".bak") - shutil.copy2(doc_path, bak) - - import pythoncom - import win32com.client - - pythoncom.CoInitialize() - word = None - doc = None - try: - word = win32com.client.Dispatch("Word.Application") - word.Visible = False - word.DisplayAlerts = 0 - doc = word.Documents.Open(str(doc_path.resolve())) - - try: - _replace_date_in_doc(doc, _today_cn_date()) - - tbl = doc.Tables(1) - storage = config.get("consumable_storage", "躬行楼 C205") - - for i, inv in enumerate(invoices): - row_idx = i + 2 - if row_idx > tbl.Rows.Count: - break - - parsed = parse_spec_model(str(inv.get("spec_model", ""))) - # 当规格型号为空时,从项目名称提取产品信息 - if not parsed["product_name"]: - item_name = str(inv.get("item_name", "")) - # 去除 "*分类*" 前缀(如 "*电子工业设备*元件盒" -> "元件盒") - if "*" in item_name: - item_name = item_name.split("*")[-1].strip() - parsed["product_name"] = item_name - - card_amount_raw = inv.get("card_amount") or 0 - card_amount: float = float(str(card_amount_raw).replace(",", "")) - qty_str = parsed["qty"] - qty_val = int(qty_str) if qty_str and qty_str.isdigit() else 0 - - # 金额填写刷卡金额,单价由刷卡金额反算 - amount = _format_money(card_amount) - unit_price = _format_money(card_amount / qty_val) if qty_val > 0 else _format_money(card_amount) - - # 数量:去掉前导零;若无数量则默认为 1 - qty = str(qty_val) if qty_val > 0 else "1" - - values = [ - str(inv.get("index", i + 1)), - parsed["product_name"], - parsed["spec"], - parsed["unit"], - qty, - unit_price, - amount, - "", # 购货人签字 — 保持空白 - storage, - "", # 领用人签字 — 保持空白 - "", # 备注 — 保持空白,避免撑破版式 - ] - - for col_idx, val in enumerate(values, start=1): - _set_cell_value(tbl.Cell(row_idx, col_idx), str(val)) - - _normalize_table_font(tbl) - - doc.Save() - finally: - if doc is not None: - doc.Close() - finally: - if word is not None: - word.Quit() - pythoncom.CoUninitialize() - - return doc_path - - -def fill_consumable_from_template( - csv_path: str | Path, - template_path: str | Path, - output_path: str | Path, - config: dict[str, Any] | None = None, -) -> Path: - """从模板复制并填写出库单(Web 会话每次从模板重新生成)。""" - template_path = Path(template_path) - output_path = Path(output_path) - if not template_path.exists(): - raise FileNotFoundError(f"出库单模板不存在: {template_path}") - shutil.copy2(template_path, output_path) - return fill_consumable_doc(csv_path, output_path, config=config, backup=False) - - -def main() -> None: - root = Path(__file__).resolve().parents[1] - parser = argparse.ArgumentParser(description="将发票 CSV 填入易耗品出库单") - parser.add_argument("--csv", default=str(root / "invoice_summary.csv")) - parser.add_argument("--doc", default=str(root / "易耗品、出库单.doc")) - parser.add_argument("--config", default=str(root / "scripts" / "data" / "config.json")) - parser.add_argument("--no-backup", action="store_true") - args = parser.parse_args() - - cfg = None - if Path(args.config).exists(): - import json - - with open(args.config, encoding="utf-8") as f: - cfg = {**load_config(), **json.load(f)} - out = fill_consumable_doc(args.csv, args.doc, config=cfg, backup=not args.no_backup) - print(f"已填写并保存: {out}") - - -if __name__ == "__main__": - main() diff --git a/src/infra/documents/pdf.py b/src/infra/documents/pdf.py deleted file mode 100644 index 17a7698..0000000 --- a/src/infra/documents/pdf.py +++ /dev/null @@ -1,46 +0,0 @@ -"""PDF 图片渲染 - -从 PDF 发票文件中渲染为图片供多模态 LLM 使用。 - -对外接口: - render_pdf_to_images(filepath, dpi) -list[str] 渲染 PDF 为图片字节 -""" - -import base64 -from pathlib import Path - -import fitz - -from ... import get_logger - -log = get_logger("pdf") - - -def render_pdf_to_images(filepath: Path, dpi: int = 300) -> list[str]: - """将 PDF 渲染为图片,返回 base64 编码的 JPEG 字符串列表。 - - Args: - filepath: PDF 文件路径。 - dpi: 渲染分辨率(默认 300,平衡质量与速度)。 - - Returns: - base64 编码的 JPEG 图片字符串列表(每页一个)。 - """ - images = [] - try: - doc = fitz.open(filepath) - zoom = dpi / 72.0 # 72 DPI 是 fitz 默认 - matrix = fitz.Matrix(zoom, zoom) - - for page in doc: - pix = page.get_pixmap(matrix=matrix) - jpg_bytes = pix.tobytes("jpg") - b64 = base64.b64encode(jpg_bytes).decode("utf-8") - images.append(b64) - - doc.close() - log.info(f"PDF 渲染成功: {filepath.name} ({len(images)} 页, {dpi} DPI)") - except Exception as e: - log.error(f"PDF 渲染失败 {filepath.name}: {e}") - - return images diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 5518acc..0000000 --- a/src/main.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -""" -财务报销自动化 - -依次执行: - 1. 发票提取 — 从 PDF 发票提取信息,生成 invoice_summary.csv - 2. 报销提交 — 打开浏览器登录财务系统并自动填报 - -用法: - python run.py # 全流程 - python run.py --step invoice # 仅发票提取 - python run.py --step submit # 仅浏览器填报 - python run.py -u 工号 -p 密码 # 覆盖登录凭据 -""" - -import argparse -import sys -from pathlib import Path - -# 确保项目根目录在 sys.path 中 -sys.path.insert(0, str(Path(__file__).parent.resolve().parent)) - -from src.pipeline import run_pipeline - - -def main() -> None: - parser = argparse.ArgumentParser( - description="财务报销自动化 - 发票提取 → 浏览器填报", - ) - parser.add_argument( - "--step", - choices=["all", "invoice", "submit"], - default="all", - help="执行步骤 (默认: all)", - ) - parser.add_argument( - "-u", - "--username", - default=None, - help="信息门户登录账号(覆盖 scripts/config.json)", - ) - parser.add_argument( - "-p", - "--password", - default=None, - help="信息门户登录密码(覆盖 scripts/config.json)", - ) - parser.add_argument( - "--cache-dir", - default=None, - help="发票缓存目录(包含 .invoice_cache 子目录,默认: scripts/data)", - ) - args = parser.parse_args() - - exit_code = run_pipeline( - step=args.step, - username=args.username, - password=args.password, - cache_dir=args.cache_dir, - ) - sys.exit(exit_code) - - -if __name__ == "__main__": - main() diff --git a/src/mcp/server.py b/src/mcp/server.py index ecd44be..5a51ab9 100644 --- a/src/mcp/server.py +++ b/src/mcp/server.py @@ -33,8 +33,8 @@ INSTRUCTIONS = """财务报销自动化工具集(发票抽取交接 → 匹配 或用 mcp__arb__validate_info(draft=...) 不落盘反复试。不完整就向用户要补充材料,禁止自己宣布可提交。 拼 JSON 前先 mcp__arb__info_skeleton(info_type) 取骨架:required 为 true 的键必须存在(含值为空串的备注类 字段),骨架与 validation_rules.json 同源,别凭记忆造结构。 -6. 普通发票用 mcp__arb__doc_consumable 生成 Word 出库单、mcp__arb__doc_requisition 生成 Excel 申购单 - (COM 自动化,需本机装有 Word/Excel;纯差旅会返回 skipped,不是错误)。 +6. 普通发票用 mcp__arb__doc_requisition 生成 Excel 申购单(COM 自动化,需本机装有 Excel; + exclude 可剔除不参与申购的物品;纯差旅没有申购明细会返回失败提示,不是错误)。 人工核对后要改单张票据的某个字段,用 mcp__arb__case_patch_extraction(只传要改的键,会回显 from/to)。 7. 每步之后用 mcp__arb__case_status 复查态势;阶段由产物推导,没有内部状态机。 mcp__arb__case_reset(scope) 可清派生产物重跑;mcp__arb__case_list 看全部会话。 @@ -62,7 +62,6 @@ TOOLS: list[Callable[..., dict[str, Any]]] = [ analysis.validate_info, analysis.rules_get, analysis.info_skeleton, - documents.doc_consumable, documents.doc_requisition, ] diff --git a/src/mcp/tools/documents.py b/src/mcp/tools/documents.py index f69b4ed..bdbd8ac 100644 --- a/src/mcp/tools/documents.py +++ b/src/mcp/tools/documents.py @@ -1,7 +1,7 @@ -"""Word 出库单与 Excel 申购单生成工具 +"""Excel 申购单生成工具(Word 出库单已随模板下线删除) -都是 COM 自动化,依赖 Windows + Microsoft Office;统一走 src.infra.com_thread -的单线程 actor,避免两个工具调用同时驱动 Word/Excel。 +COM 自动化,依赖 Windows + Microsoft Excel;统一走 src.infra.com_thread 的单线程 actor, +避免多个调用同时驱动 Office。 """ from __future__ import annotations @@ -10,68 +10,13 @@ from pathlib import Path from typing import Any from ...cases import ARTIFACTS_DIR_NAME, CACHE_DIR_NAME, PROJECT_ROOT, CaseStore -from ...config import SESSION_CONFIG_KEYS, load_config from ...infra.com_thread import run_com -from ...infra.documents import ( - CONSUMABLE_DOC_FILENAME, - REQUISITION_FILENAME, - fill_consumable_from_template, - generate_requisition, - load_requisition_items, -) +from ...infra.documents import REQUISITION_FILENAME, generate_requisition, load_requisition_items from ..envelope import fail, ok _store = CaseStore() -def _case_config(d: Path) -> dict[str, Any]: - """项目配置 + 会话目录里 config.json 的用户字段覆盖。""" - config = load_config() - cfg_path = d / "config.json" - if cfg_path.exists(): - import json - - try: - raw: dict[str, Any] = json.loads(cfg_path.read_text(encoding="utf-8")) - except Exception: - raw = {} - for key in SESSION_CONFIG_KEYS: - if key in raw: - config[key] = raw[key] - return config - - -def doc_consumable(case_id: str, output_name: str | None = None) -> dict[str, Any]: - """生成易耗品出库单(Word,仅普通发票需要)。 - - 取会话里的 payment_records.csv 填入项目根目录模板的副本,输出到 - cases//artifacts/。纯差旅发票会返回 skipped,不是错误。 - 需要 Windows + 已安装 Microsoft Word。 - """ - d = _store.resolve(case_id) - if d is None: - return fail(f"会话不存在: {case_id}") - csv_path = d / "payment_records.csv" - if not csv_path.exists(): - return fail("缺少 payment_records.csv", next_action="先 mcp__arb__match_payments 生成汇总") - groups = _groups(d) - if groups is not None and not groups.get("general_count", 0): - return ok(skipped=True, reason="本会话没有普通发票(纯差旅),不需要易耗品出库单") - template = PROJECT_ROOT / CONSUMABLE_DOC_FILENAME - if not template.exists(): - return fail(f"出库单模板不存在: {template}", next_action="把模板放回项目根目录") - out = d / ARTIFACTS_DIR_NAME / (output_name or CONSUMABLE_DOC_FILENAME) - out.parent.mkdir(parents=True, exist_ok=True) - config = _case_config(d) - try: - run_com(fill_consumable_from_template, csv_path, template, out, config=config) - except ImportError: - return fail("缺少 pywin32,无法驱动 Word", next_action="uv sync 安装项目依赖", needs="pywin32") - except Exception as e: - return fail(f"生成出库单失败: {e}", next_action="检查 Word 是否被其他实例占用或有弹窗未关闭", doc=str(out)) - return ok(doc=str(out), filename=out.name, bytes=out.stat().st_size, skipped=False) - - def doc_requisition(case_id: str, exclude: list[str] | None = None, project: str | None = None) -> dict[str, Any]: """生成低值易耗品申购单(Excel,仅普通发票)。 diff --git a/src/pipeline.py b/src/pipeline.py deleted file mode 100644 index 10b06b3..0000000 --- a/src/pipeline.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -报销全流程编排 - -将发票提取 → 差旅/普通信息提取 → 浏览器填报串联为一条管道, -数据在内存中流转,同时生成 CSV 中间产物。 - -发票类型区分: - - 差旅发票(train/hotel):不生成易耗品出库单,走差旅报销流程 - - 普通发票(general):生成易耗品出库单,走普通报销流程 - -数据流变更(2026-06-11): - 在发票提取和匹配完成后立即判断报销类型(差旅/普通), - 差旅调用 LLM 提取 travel_info.json,普通预留 normal_info.json。 - Bot 仅负责接收信息并填报,不再承担信息提取职责。 -""" - -from pathlib import Path -from typing import Any - -from . import get_logger -from .config import load_config -from .core.extraction import extract_invoices -from .pipeline_core import ( - extract_and_cache_normal_info, - extract_and_cache_travel_info, - extract_info_by_type, - is_travel_invoice, - process_invoices, -) - -log = get_logger("pipeline") - - -def run_pipeline( - step: str = "all", - username: str | None = None, - password: str | None = None, - cache_dir: str | None = None, -) -> int: - """执行报销流程 - - Args: - step: all | invoice | submit - username: 覆盖 config.json 中的用户名 - password: 覆盖 config.json 中的密码 - cache_dir: 发票缓存目录(包含 .invoice_cache 子目录) - """ - config = load_config() - if username: - config["username"] = username - if password: - config["password"] = password - - project_dir = Path(__file__).parent.parent - cache_path = Path(cache_dir) if cache_dir else project_dir / "scripts" / "data" - - # -------------------------------------------------- - # Step 1: 发票提取 + 类型判断 + 信息提取 - # -------------------------------------------------- - payment_records: list[dict[str, str]] | None = None - groups: dict[str, list[dict[str, str]]] | None = None - travel_info: dict[str, Any] | None = None - normal_info: dict[str, Any] | None = None - - if step in ("all", "invoice"): - log.info("=" * 60) - log.info("[1/2] 发票提取") - log.info("=" * 60) - - payment_records, applications, groups = extract_invoices(str(cache_path)) - if not payment_records: - log.error("未提取到任何发票数据") - return 1 - - # 使用公共函数处理发票数据 - process_invoices(payment_records, applications, groups, cache_path) - - # 发票提取完成后立即判断类型并提取信息 - travel_info, normal_info = extract_info_by_type(groups, cache_path) - - if step == "invoice": - log.info("[1/2] 发票提取 完成") - return 0 - - # -------------------------------------------------- - # Step 2: 浏览器填报 - # -------------------------------------------------- - if step in ("all", "submit"): - log.info("=" * 60) - log.info("[2/2] 报销提交") - log.info("=" * 60) - - from .infra.browser import run_bot - - if groups is None: - # 从缓存重新分类(仅 submit 阶段需要) - from .pipeline_core import _classify_from_cache - - groups = _classify_from_cache(cache_path) - - if is_travel_invoice(groups): - if travel_info is None: - travel_info = extract_and_cache_travel_info(groups, cache_path) - log.info("检测到纯差旅发票,使用差旅报销模式") - run_bot(config, work_dir=cache_path, travel_info=travel_info) - else: - if normal_info is None: - normal_info = extract_and_cache_normal_info(groups, cache_path) - log.info("检测到普通发票,使用普通报销模式") - run_bot(config, work_dir=cache_path, normal_info=normal_info) - - if step == "submit": - log.info("[2/2] 报销提交 完成") - return 0 - - log.info("=" * 60) - log.info("全流程执行完毕") - log.info("=" * 60) - return 0 diff --git a/src/pipeline_core.py b/src/pipeline_core.py deleted file mode 100644 index ef60717..0000000 --- a/src/pipeline_core.py +++ /dev/null @@ -1,233 +0,0 @@ -""" -管道核心逻辑 - -抽取 pipeline.py(CLI 管道)和 pipeline_web.py(Web 管道)的公共数据流: - 发票分类判断 -> 差旅/普通信息提取 -> 缓存读写 - -两个入口分别传入不同的目录参数,复用此模块。 -""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any, cast - -from . import get_logger -from .core.extraction import ( - CACHE_DIR_NAME, - extract_normal_info, - extract_travel_info, - load_cache, -) -from .infra.documents import ( - classify_invoice_batch, - save_application_json, - save_invoice_csv, -) -from .infra.documents import save_csv as save_payment_csv - -log = get_logger("pipeline_core") - - -def is_travel_invoice(groups: dict[str, list[dict[str, Any]]]) -> bool: - """判断是否为纯差旅发票(有差旅发票且无普通发票)。 - - 注意:系统仅支持「纯差旅」和「普通报销」两种模式。 - 若同时存在差旅发票和普通发票(混合),则视为普通报销模式处理——差旅发票 - 对应的费用仍会在普通报销中按项目填报。若需要严格区分,上游应在发票分类 - 后报错提示用户分开提交。 - """ - return bool(groups.get("travel")) and not bool(groups.get("general")) - - -def save_cache_info(cache_path: Path, info_key: str, info: dict[str, Any]) -> None: - """将提取结果保存到缓存目录 - - Args: - cache_path: 会话目录路径。 - info_key: 缓存键名("travel_info" 或 "normal_info")。 - info: 提取结果字典。 - """ - cache_dir = cache_path / CACHE_DIR_NAME - cache_dir.mkdir(parents=True, exist_ok=True) - with open(cache_dir / f"{info_key}.json", "w", encoding="utf-8") as f: - json.dump(info, f, ensure_ascii=False, indent=2) - log.info("%s 已保存到缓存", info_key) - - -def extract_and_cache_travel_info( - groups: dict[str, list[dict[str, Any]]], - cache_path: Path, -) -> dict[str, Any] | None: - """当存在差旅发票时,调用 LLM 提取差旅信息并缓存。 - - Returns: - 差旅信息字典,非差旅时返回 None。 - """ - if not groups.get("travel"): - return None - - # 检查缓存是否已有 - cache_map = load_cache(cache_path) - travel_info = cache_map.get("travel_info") - if travel_info: - log.info("使用已有差旅信息缓存") - return travel_info # type: ignore[no-any-return] - - log.info("开始提取差旅信息...") - travel_info = extract_travel_info(source_dir=cache_path) - save_cache_info(cache_path, "travel_info", travel_info) - return travel_info - - -def extract_and_cache_normal_info( - groups: dict[str, list[dict[str, Any]]], - cache_path: Path, -) -> dict[str, Any] | None: - """当存在普通发票时,调用 LLM 提取普通报销信息并缓存。 - - Returns: - 普通报销信息字典,非普通时返回 None。 - """ - if not groups.get("general"): - return None - - # 检查缓存是否已有 - cache_map = load_cache(cache_path) - normal_info = cache_map.get("normal_info") - if normal_info: - log.info("使用已有普通发票信息缓存") - return normal_info # type: ignore[no-any-return] - - log.info("开始提取普通发票信息...") - normal_info = extract_normal_info(source_dir=cache_path) - save_cache_info(cache_path, "normal_info", normal_info) - return normal_info - - -def _classify_from_cache(cache_path: Path) -> dict[str, list[dict[str, Any]]]: - """从缓存目录读取发票数据并按类型分组 - - 注意:load_cache 会加载所有 .invoice_cache/*.json,包括 travel_info 和 normal_info - 等提取结果缓存(它们没有 invoice_type 字段),需要显式过滤掉。 - """ - cache_map = load_cache(cache_path) - # 过滤掉非发票的缓存条目:travel_info、normal_info 等提取结果 - invoices = [ - data - for key, data in cache_map.items() - if key not in ("travel_info", "normal_info") - and isinstance(data, dict) - and data.get("invoice_type") not in ("application", "payment", "attachments", "note") - ] - groups = classify_invoice_batch(invoices) - # 从缓存重新收集 attachments 类型 - attachments = [ - data - for key, data in cache_map.items() - if isinstance(data, dict) and data.get("invoice_type") in ("attachments", "note") - ] - groups["attachments"] = attachments - return groups - - -def save_invoice_groups(session_dir: Path, groups: dict[str, list[dict[str, str]]]) -> None: - """保存发票分类结果到目录的 JSON 文件 - - 保存完整发票分组数据(供 is_travel_invoice/extract_and_cache_* 使用), - 同时保留计数字段(供快速统计使用)。 - """ - groups_path = session_dir / "invoice_groups.json" - data = { - "travel": groups.get("travel", []), - "general": groups.get("general", []), - "application": groups.get("application", []), - "attachments": groups.get("attachments", []), - "travel_count": len(groups.get("travel", [])), - "general_count": len(groups.get("general", [])), - "application_count": len(groups.get("application", [])), - "attachments_count": len(groups.get("attachments", [])), - } - with open(groups_path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=2) - - -def load_invoice_groups(session_dir: Path) -> dict[str, Any] | None: - """从目录加载发票分类结果 - - 返回包含完整发票分组数据和计数字段的字典。 - """ - groups_path = session_dir / "invoice_groups.json" - if not groups_path.exists(): - return None - try: - with open(groups_path, encoding="utf-8") as f: - return cast(dict[str, Any] | None, json.load(f)) - except Exception: - return None - - -def process_invoices( - payment_records: list[dict[str, Any]], - applications: list[dict[str, Any]], - groups: dict[str, list[dict[str, Any]]], - output_dir: Path, -) -> dict[str, Any]: - """处理提取的发票数据:保存 CSV、申请单和分类结果 - - Args: - payment_records: 支付记录列表。 - applications: 申请单列表。 - groups: 发票分类结果。 - output_dir: 输出目录。 - - Returns: - 包含发票统计信息的字典。 - """ - # 保存 CSV:支付记录级别(供 bot/出库单使用)和发票级别(供人工参考) - save_payment_csv(payment_records, output_dir / "payment_records.csv") - save_invoice_csv(payment_records, output_dir / "invoice_summary.csv") - - # 出差申请单单独保存 - if applications: - save_application_json(applications, output_dir / "travel_applications.json") - - # 保存分类结果(供后续步骤统一读取) - save_invoice_groups(output_dir, groups) - - # 统计发票总数 - invoice_count = sum(len(inv.get("_matched_invoices", [])) for inv in payment_records) - - log.info( - f"发票分类: 差旅 {len(groups['travel'])} 张, 普通 {len(groups['general'])} 张, 附件 {len(groups.get('attachments', []))} 个" - ) - - return { - "invoice_count": invoice_count, - "travel_count": len(groups["travel"]), - "general_count": len(groups["general"]), - "application_count": len(groups.get("application", [])), - "attachments_count": len(groups.get("attachments", [])), - } - - -def extract_info_by_type( - groups: dict[str, list[dict[str, Any]]], - cache_path: Path, -) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: - """根据发票类型提取差旅或普通报销信息 - - Args: - groups: 发票分类结果。 - cache_path: 缓存目录路径。 - - Returns: - (travel_info, normal_info) 元组,根据发票类型返回对应信息。 - """ - if is_travel_invoice(groups): - travel_info = extract_and_cache_travel_info(groups, cache_path) - return travel_info, None - else: - normal_info = extract_and_cache_normal_info(groups, cache_path) - return None, normal_info diff --git a/src/web/README.md b/src/web/README.md deleted file mode 100644 index bea9705..0000000 --- a/src/web/README.md +++ /dev/null @@ -1,339 +0,0 @@ ---- -last_reviewed: 2026-06-13 ---- - -# src/web 模块设计说明 - -## 设计思路 - -`src/web` 是一个基于 Flask 的轻量级 Web 界面,为财务报销自动化管道提供可视化操作入口。核心设计原则: - -- **会话隔离**:每次上传生成独立 `session_id`,文件、日志、配置、结果各自隔离在 `uploads//` 目录下,避免并发冲突。 -- **异步处理**:耗时的 PDF 提取、LLM 调用在后台线程执行,前端通过 SSE 实时查看日志流,不阻塞 HTTP 连接。 -- **前后端分离最小化**:前端使用原生 JS + Bootstrap 5,不引入构建工具,保持单页应用轻量可维护。 -- **统一文件上传**:2026-06-12 改造,将 PDF 和图片上传入口合并为单一上传区,用户通过一个入口上传所有文件类型。 - -## 变更历史 - -| 日期 | 变更 | -|------|------| -| 2026-06-13 | LLM 流式思考过程展示:后端 SSE 推送 `llm_stream` 事件(start/chunk/end/error),前端聊天气泡实时展示 AI 思考过程 | -| 2026-06-12 | 文件进度实时反馈:后端 SSE 推送 `file_progress` 事件(processing/done/cached/error),前端文件消息实时更新状态 + 展示提取摘要 | -| 2026-06-12 | 配置交互改为逐项引导:config.json 缺失字段时 AI 逐个提示用户通过聊天输入,全部完成后自动开始处理 | -| 2026-06-12 | 聊天窗口精简:移除 SSE 日志流显示,仅保留关键状态消息;config.json 配置不完整时 AI 主动提示缺失字段 | -| 2026-06-12 | 移除开始处理按钮,改为自动触发:config.json 解析完成且配置完整(username/password)且有发票文件时自动开始处理 | -| 2026-06-12 | 文件上传通知改为逐条消息:每个文件单独一条聊天气泡,上传框 flex 居中、固定高度 | -| 2026-06-12 | 文件上传反馈移至聊天窗口:上传/拖拽/同步文件后以聊天气泡通知,上传框固定高度不再显示文件标签 | -| 2026-06-12 | 修复聊天窗口消息覆盖问题:将消息区域与输入区域分离,消息区独立滚动,新增用户文本输入功能 | -| 2026-06-12 | 合并 PDF/图片上传入口,`/api/files` 返回统一文件列表,前端使用 `allFiles` 单一数组管理 | -| 2026-06-12 | 移除配置表单,config.json 通过统一上传入口自动解析,配置存入 `sessionConfig` 对象,前端不再展示配置输入框 | -| 2026-06-12 | 暗色日志窗口替换为 AI 聊天风格窗口,SSE 日志以聊天气泡形式展示,支持打字指示器动画 | - -## 文件结构 - -``` -src/web/ -├── app.py # Flask 应用入口,路由、管道编排、日志收集 -├── sse_handler.py # SSE 日志收集器、日志转义工具 -├── routes.py # 路由定义、SSE 端点 -├── templates/ -│ ├── index.html # PC 端主界面(上传、处理、编辑、提交) -│ └── mobile_upload.html # 移动端上传页面(拍照/相册选择) -└── static/ - ├── css/ - │ └── index.css # 全局样式(上传区、日志面板、可编辑表格) - └── js/ - ├── index.js # 入口:初始化 App 全局状态、绑定事件 - ├── state.js # 全局状态管理(App 对象) - ├── chat.js # 聊天气泡渲染、文件消息、LLM 流式气泡 - ├── agent.js # Agent 事件处理、用户输入管理 - ├── process.js # SSE 连接、事件路由、管道启动 - ├── config.js # 配置解析、逐项引导 - ├── upload.js # 文件上传、拖拽处理 - ├── sync.js # 移动端同步 - └── utils.js # HTML 转义等工具函数 -``` - -## 数据流 - -```mermaid -graph TD - A[用户上传文件] --> B[创建 session] - B --> C[文件写入 uploads//] - C --> D[后台线程执行管道] - D --> E["extract_invoices()"] - E --> F["enrich_with_llm()"] - F --> G["save_csv()"] - G --> H["结果写入 session 目录"] - H --> I["invoice_summary.csv"] - H --> J["invoice_groups.json"] - H --> K["result.json"] - H --> L["session.log"] - E --> M{"判断报销类型"} - M -->|差旅| N["extract_travel_info()"] - M -->|普通| O["extract_normal_info()"] - N --> P["travel_info.json"] - O --> Q["normal_info.json"] - K --> R["前端 SSE 轮询 → 显示完成状态"] - R --> S["前端加载 CSV → 可编辑表格"] - S --> T["用户修改后保存"] - T --> U["用户点击提交"] - U --> V["run_financial_submit()"] - V --> W["bot 自动填报财务系统"] - W --> X["差旅模式: travel_info.json → 填报差旅单 → 上传差旅附件"] - W --> Y["普通模式: normal_info.json → 基本信息 → 录入明细 → 支付信息 → 上传附件"] -``` - -## API 路由 - -| 方法 | 路径 | 功能 | -|------|------|------| -| GET | `/` | 主界面 | -| POST | `/api/session` | 创建会话,返回 session_id | -| POST | `/api/upload/` | 上传 PDF/图片 | -| GET | `/api/files/` | 列出会话文件(返回统一 `files` 列表,含 `name`、`type`、`size` 字段;旧字段 `pdfs`/`images` 保留向后兼容) | -| POST | `/api/agent/process/` | 启动 Agent 管道(后台线程) | -| GET | `/api/logs/` | SSE 日志流 | -| GET | `/api/data/` | 获取发票数据 JSON | -| POST | `/api/save/` | 保存前端编辑的发票数据 | -| GET | `/api/download//` | 下载生成文件 | -| POST | `/api/submit-financial/` | 手动触发财务系统填报 | -| GET | `/mobile/` | 移动端上传页面 | - -## 关键机制 - -### 日志收集 - -`SSELogHandler` 将管道日志写入 `session.log`,SSE 端点通过文件偏移量增量读取,实现前端实时日志展示。日志收集器在管道启动时安装,完成后移除,确保线程安全。 - -### 发票类型分流 - -- **差旅发票**(高铁票/酒店住宿):不生成易耗品出库单,走差旅报销流程 -- **普通发票**:生成易耗品出库单(Word 文档),走普通报销流程 - -`extract_invoices()` 在提取阶段完成分类,结果保存为 `invoice_groups.json`(包含 `travel_count`、`general_count`、`application_count`)。后续步骤(出库单生成、财务填报)统一从该文件读取分类结果,避免重复解析 CSV 和 JSON 字段。 - -### LLM 信息提取(2026-06-11) - -发票提取和匹配完成后,根据类型分流调用 LLM 提取结构化报销信息: - -- **差旅发票**:调用 `extract_travel_info()`,提取出差事由、地点、时间、交通/住宿明细、补助清单、支付方式、附件清单,缓存为 `travel_info.json`。 -- **普通发票**:调用 `extract_normal_info()`,提取报销说明、发票总数、总金额、支付方式、附件清单,缓存为 `normal_info.json`。 - -Bot 填报时优先使用 LLM 提取的信息(`travel_info`/`normal_info`),降级时从缓存加载原始发票数据。 - -### 移动端同步 - -PC 端生成二维码指向 `/mobile/`,手机端上传的文件通过 `syncFiles()` 轮询同步到 PC 端内存中的 `allFiles` 列表,实现跨设备协作。文件来源标记(`__source`)区分本地选择和服务器同步,避免重复。 - -### 配置管理 - -配置分两层:项目级 `config.json` 提供默认值,会话级 `uploads//config.json` 存储当次会话覆盖值。前端通过统一上传入口接收 `config.json`,自动解析到 `sessionConfig` 对象,不再展示配置输入表单。 - -### 聊天气泡消息系统 - -聊天窗口的所有消息通过 **事件文件 + SSE 轮询** 机制传输。后端不直接推送消息,而是将事件追加到 session 目录下的日志文件,SSE 端点以 0.5 秒间隔轮询文件增量,再通过 EventSource 推送到前端。 - -#### 事件文件总览 - -session 目录下有四个事件文件: - -| 文件 | 用途 | 写入方 | 读取方 | -|------|------|--------|--------| -| `llm_stream.log` | LLM 流式输出(思考过程 + 正式回答) | `llm_extractor` 模块 | SSE 端点 | -| `file_events.log` | 文件处理进度 | `extractor` 模块 | SSE 端点 | -| `agent_events.log` | Agent 状态变更、请求补充等 | `orchestrator` 模块 | SSE 端点 | -| `session.log` | 普通 INFO 日志 | `SSELogHandler` | SSE 端点(当前仅保留,前端已不做处理) | - -#### 后端发送事件 - -所有事件文件遵循相同的写入协议:每行一个 JSON 对象,写入后 flush。 - -**`llm_stream.log`** — 由 `_emit_llm_stream(source_dir, phase, ...)` 写入: - -```python -# 开始 LLM 调用(必需) -_emit_llm_stream(source_dir, "start", label="正在分析文件...") - -# 流式文本片段(可选,有内容时发) -_emit_llm_stream(source_dir, "chunk", text="让我来分析...") - -# 思考过程片段(可选,模型支持时发) -_emit_llm_stream(source_dir, "reasoning", text="根据发票信息...") - -# 调用完成(必需) -_emit_llm_stream(source_dir, "end", label="分析完成") - -# 调用失败(异常时发) -_emit_llm_stream(source_dir, "error", error="连接超时") -``` - -**`agent_events.log`** — 由 `_emit_agent_event(session_dir, event_type, ...)` 写入: - -```python -# 状态变更 -_emit_agent_event(session_dir, "agent_state_change", state="extracting", message="正在分析文件...") - -# 请求补充材料 -_emit_agent_event(session_dir, "agent_request_supplement", message="请上传返程车票...") - -# 信息完整,可以提交 -_emit_agent_event(session_dir, "agent_ready") - -# 错误 -_emit_agent_event(session_dir, "agent_error", message="LLM 提取失败") -``` - -**`file_events.log`** — 由 `_emit_file_event(source_dir, ...)` 写入: - -```python -# 文件开始处理 -_emit_file_event(source_dir, filename, "processing") - -# 文件处理完成(带摘要) -_emit_file_event(source_dir, filename, "done", summary={"invoice_number": "...", ...}) - -# 使用缓存 -_emit_file_event(source_dir, filename, "cached") - -# 处理失败 -_emit_file_event(source_dir, filename, "error", error="PDF 解析失败") -``` - -**重要约束:`start` 和 `end` 事件不可省略。** 前端 `llmStreamState` 状态机依赖 `start` 创建气泡 DOM,没有 `start` 时后续的 `chunk` 和 `reasoning` 会因守卫条件直接返回。详见 `.agents/docs/error-experience/2026-06-13-llm_query_text缺少start-end事件导致前端不显示.md`。 - -#### SSE 传输层 - -`/api/logs/` 端点(`routes.py`)的轮询逻辑: - -``` -每 0.5 秒: - 1. 读取 session.log 增量 → yield "data: <日志行>" - 2. 读取 file_events.log 增量 → 逐行 yield "data: " - 3. 读取 llm_stream.log 增量 → 逐行 yield "data: " - 4. 读取 agent_events.log 增量 → 逐行 yield "data: " - 5. 检查 result.json 是否存在 → yield "data: {type: 'done'}" 后退出 -``` - -#### 前端事件路由 - -`process.js` 的 `EventSource` 监听器按 `msg.type` 分发: - -``` -msg.type === 'file_progress' → setFileProcessing / setFileDone / setFileCached / setFileError -msg.type === 'llm_stream' → handleLLMStream() -msg.type 以 'agent_' 开头 → handleAgentEvent() -msg.type === 'done' → 关闭 EventSource,展示结果 -``` - -#### 前端聊天气泡渲染 - -**LLM 流式气泡**(`chat.js`): - -``` -start → _createLLMStreamBubble() - ├─ 创建
- ├─ 创建 label 元素(显示 label 文本) - ├─ 创建
可折叠区域(思考过程) - └─ 创建 textContent 元素(正式回答) - └─ 注册到 llmStreamState - -reasoning → _appendLLMStreamReasoning() - └─ 追加到 reasoningContent.textContent - -chunk → _appendLLMStreamChunk() - └─ 追加到 textContent.textContent - -end → _closeLLMStreamBubble() - ├─ 气泡 class 从 processing 变为 done - └─ 清空 llmStreamState - -error → _errorLLMStreamBubble() - ├─ 气泡 class 变为 error - └─ 清空 llmStreamState -``` - -**Agent 状态气泡**(`agent.js`): - -``` -agent_state_change → _handleAgentStateChange() - └─ 更新最后一条状态消息(不追加新气泡) - -agent_request_supplement → _handleAgentRequestSupplement() - └─ 追加请求补充的气泡 - -agent_ready → _handleAgentReady() - └─ 追加完成状态气泡 - -agent_error → _handleAgentError() - └─ 追加错误气泡 -``` - -**文件进度气泡**(`chat.js`): - -``` -file_progress (processing) → setFileProcessing() → 三点动画 -file_progress (done) → setFileDone() → 提取摘要 -file_progress (cached) → setFileCached() → 缓存标识 -file_progress (error) → setFileError() → 错误信息 -``` - -#### 完整数据流 - -```mermaid -sequenceDiagram - participant Pipe as 后台线程
(管道) - participant Files as 事件文件
(session 目录) - participant SSE as Flask SSE
(routes.py) - participant ES as EventSource
(process.js) - participant Chat as chat.js - participant Agent as agent.js - - Note over Pipe: 启动管道 - Pipe->>Files: 追加 llm_stream start - Pipe->>Files: 追加 agent_events state_change - - Note over SSE: 0.5s 轮询 - SSE->>Files: seek(last_size) + read() - SSE->>ES: yield "data: start" - SSE->>ES: yield "data: state_change" - - ES->>Chat: handleLLMStream({phase:"start"}) - Chat->>Chat: 创建流式气泡 - ES->>Agent: handleAgentEvent({type:"agent_state_change"}) - Agent->>Agent: 显示状态消息 - - Note over Pipe: LLM 流式输出 - Pipe->>Files: 追加 llm_stream reasoning - Pipe->>Files: 追加 llm_stream chunk - - SSE->>Files: seek(last_size) + read() - SSE->>ES: yield "data: reasoning" - SSE->>ES: yield "data: chunk" - - ES->>Chat: handleLLMStream({phase:"reasoning"}) - Chat->>Chat: 追加思考内容 - ES->>Chat: handleLLMStream({phase:"chunk"}) - Chat->>Chat: 追加正式回答 - - Note over Pipe: 完成 - Pipe->>Files: 追加 llm_stream end - Pipe->>Files: 写入 result.json - - SSE->>Files: seek(last_size) + read() - SSE->>ES: yield "data: end" - ES->>Chat: handleLLMStream({phase:"end"}) - Chat->>Chat: 气泡变完成状态 - - SSE->>Files: 检测 result.json - SSE->>ES: yield "data: {type:'done'}" - ES->>ES: 关闭连接 -``` - -#### 添加新消息类型的步骤 - -1. 在对应模块定义 `_emit_xxx()` 函数,写入 session 目录的 JSON 文件 -2. 在 `routes.py` 的 `stream_logs()` 轮询循环中新增对该文件的轮询 -3. 在 `process.js` 的 EventSource 监听器中按 `msg.type` 路由到新处理器 -4. 在 `chat.js` 或 `agent.js` 中实现渲染逻辑 -5. 确保 `start` 和 `end` 事件成对出现(如果是流式气泡) \ No newline at end of file diff --git a/src/web/__init__.py b/src/web/__init__.py deleted file mode 100644 index 6e63b66..0000000 --- a/src/web/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -Web 模块 - -提供财务报销系统的 Web 界面功能。 -""" diff --git a/src/web/app.py b/src/web/app.py deleted file mode 100644 index b1b8396..0000000 --- a/src/web/app.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -财务报销自动化 — Web 界面 - -用户上传 PDF 发票,配置账号信息,自动完成: - 1. 发票提取 2. 浏览器填报(可选) - -发票类型区分: - - 差旅发票(高铁票/酒店住宿):不生成易耗品出库单,走差旅报销流程 - - 普通发票:生成易耗品出库单,走普通报销流程 - -启动: uv run python src/web/app.py -访问: http://localhost:5000 -""" - -import sys -from pathlib import Path - -# 确保项目根目录在 sys.path -PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent -sys.path.insert(0, str(PROJECT_ROOT)) - -from flask import Flask # noqa: E402, I001 - -from src.web import pipeline_web, routes # noqa: E402, I001 -from src.infra.documents import CONSUMABLE_DOC_FILENAME # noqa: E402, I001 - -app = Flask(__name__, template_folder="templates") - -UPLOAD_BASE = PROJECT_ROOT / "src" / "web" / "uploads" - -_app_initialized = False - - -def create_app() -> Flask: - """应用工厂:初始化配置并注册路由""" - global _app_initialized - if _app_initialized: - return app - _app_initialized = True - - # 设置出库单模板路径 - pipeline_web.set_consumable_template(PROJECT_ROOT / CONSUMABLE_DOC_FILENAME) - - # 初始化路由配置 - routes.init_routes(UPLOAD_BASE) - - # 注册 Blueprint - app.register_blueprint(routes.web_bp) - - return app - - -# 直接使用 app 实例(保持向后兼容) -create_app() - -if __name__ == "__main__": - UPLOAD_BASE.mkdir(parents=True, exist_ok=True) - print("启动 Web 服务: http://localhost:5000") - app.run(host="0.0.0.0", port=5000, debug=True, threaded=True, use_reloader=False) diff --git a/src/web/pipeline_web.py b/src/web/pipeline_web.py deleted file mode 100644 index 1b19cc6..0000000 --- a/src/web/pipeline_web.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Web 管道逻辑 - -负责: -- 发票提取管道编排 -- 财务系统填报触发 -- 易耗品出库单生成 -- 发票分类数据持久化 -""" - -import time -from pathlib import Path -from typing import Any -from urllib.parse import quote - -# 延迟导入,避免循环引用 -from src import get_logger # noqa: F401 -from src.infra.documents import ( - CONSUMABLE_DOC_FILENAME, - fill_consumable_from_template, -) -from src.pipeline_core import ( - extract_info_by_type, - load_invoice_groups, - process_invoices, -) - -fill_log = get_logger("fill_consumable_doc") - -# 文件常量 -SESSION_RESULT_FILE = "result.json" -INVOICE_GROUPS_FILE = "invoice_groups.json" - - -def resolve_payment_csv(session_dir: Path) -> Path | None: - """查找支付记录 CSV(payment_records.csv)""" - csv_path = session_dir / "payment_records.csv" - if csv_path.exists(): - return csv_path - for f in session_dir.glob("*.csv"): - if f.name != SESSION_RESULT_FILE: - return f - return None - - -def resolve_invoice_csv(session_dir: Path) -> Path | None: - """查找发票级别 CSV(invoice_summary.csv)""" - csv_path = session_dir / "invoice_summary.csv" - if csv_path.exists(): - return csv_path - for f in session_dir.glob("*.csv"): - if f.name != SESSION_RESULT_FILE: - return f - return None - - -# ================================================================ -# 出库单填写 -# ================================================================ - -# 在模块加载时确定模板路径(由 app.py 传入 PROJECT_ROOT) -_consumable_template: Path | None = None - - -def set_consumable_template(template_path: Path) -> None: - """设置出库单模板路径(由 app.py 在启动时调用)""" - global _consumable_template - _consumable_template = template_path - - -def _try_fill_consumable_doc(session_dir: Path, config: dict[str, Any]) -> dict[str, Any]: - """根据 CSV 填写易耗品出库单,供会话目录下载。 - - 从 invoice_groups.json 读取分类结果,仅当存在普通发票时才生成出库单。 - """ - template = _consumable_template - if template is None or not template.exists(): - fill_log.warning("出库单模板不存在: %s", template) - return {"ok": False, "error": "出库单模板不存在,请将模板放在项目根目录"} - - groups = load_invoice_groups(session_dir) - if groups is None: - return {"ok": False, "error": "未找到发票分类数据,请先处理"} - - if not groups.get("general_count", 0): - fill_log.info("纯差旅发票,跳过易耗品出库单生成") - return {"ok": False, "skipped": True, "error": "差旅发票无需生成易耗品出库单"} - - csv_path = resolve_payment_csv(session_dir) - if csv_path is None: - return {"ok": False, "error": "未找到发票 CSV"} - - out_doc = session_dir / CONSUMABLE_DOC_FILENAME - try: - fill_log.info("开始填写出库单: %s", out_doc.name) - fill_consumable_from_template(csv_path, template, out_doc, config=config) - fill_log.info("出库单填写完成") - return {"ok": True, "doc_filename": CONSUMABLE_DOC_FILENAME} - except ImportError: - fill_log.error("填写出库单需要 pywin32,请执行: pip install pywin32") - return {"ok": False, "error": "服务器未安装 pywin32,无法生成 Word 出库单"} - except Exception as e: - fill_log.exception("填写出库单失败: %s", e) - return {"ok": False, "error": str(e)} - - -def append_doc_download(result: dict[str, Any], session_id: str, doc_fill: dict[str, Any]) -> None: - """将出库单下载信息追加到结果字典""" - if doc_fill.get("ok"): - fn = doc_fill["doc_filename"] - result["doc_url"] = f"/api/download/{session_id}/{quote(fn)}" - result["doc_ok"] = True - elif doc_fill.get("skipped"): - result["doc_ok"] = None - result["doc_skipped"] = True - result["doc_message"] = doc_fill.get("error", "") - else: - result["doc_ok"] = False - result["doc_error"] = doc_fill.get("error", "未知错误") - - -# ================================================================ -# 管道入口 -# ================================================================ - - -def run_pipeline_web(session_dir: Path, config: dict[str, Any] | None) -> dict[str, Any]: - """在 Web 会话目录中执行发票提取,结果写入 session 目录下的文件 - - 注意:不再自动提交财务系统。提交通由 /api/submit-financial/ 触发。 - - 在发票提取和匹配完成后立即判断报销类型: - - 差旅发票:调用 LLM 提取差旅信息并缓存到 travel_info.json - - 普通发票:无需额外提取(normal_info.json 待实现) - """ - from src.core.extraction import extract_invoices - - start = time.time() - - # ---- Step 1: 发票提取 ---- - invoices, applications, groups = extract_invoices(str(session_dir)) - if not invoices: - return {"ok": False, "error": "未提取到任何发票数据"} - - # 使用公共函数处理发票数据 - stats = process_invoices(invoices, applications, groups, session_dir) - - # ---- Step 2: 差旅/普通信息提取 ---- - extract_info_by_type(groups, session_dir) - - elapsed = time.time() - start - result = { - "ok": True, - "elapsed": f"{elapsed:.1f}s", - "invoice_count": stats["invoice_count"], - "csv_url": f"/api/download/{session_dir.name}/invoice_summary.csv", - "travel_count": stats["travel_count"], - "general_count": stats["general_count"], - } - doc_fill = _try_fill_consumable_doc(session_dir, config or {}) - append_doc_download(result, session_dir.name, doc_fill) - return result - - -def run_financial_submit(session_dir: Path, config: dict[str, Any] | None) -> dict[str, Any]: - """执行财务系统填报(从前端确认后调用) - - 从 invoice_groups.json 读取分类结果,根据发票类型选择填报模式: - - 纯差旅发票:差旅报销模式 - - 含普通发票:普通报销模式 - """ - csv_path = session_dir / "payment_records.csv" - if not csv_path.exists(): - return {"ok": False, "error": "未找到发票数据,请先处理"} - - from src.infra.browser import run_bot_web - - groups = load_invoice_groups(session_dir) - if groups: - if groups.get("travel_count", 0) and not groups.get("general_count", 0): - fill_log.info("检测到纯差旅发票,使用差旅报销模式") - else: - fill_log.info("检测到普通发票,使用普通报销模式") - - try: - run_bot_web(config or {}, session_dir) - return {"ok": True} - except Exception as e: - fill_log.error("财务填报失败: %s", e) - return {"ok": False, "error": str(e)} diff --git a/src/web/routes.py b/src/web/routes.py deleted file mode 100644 index 34fe57e..0000000 --- a/src/web/routes.py +++ /dev/null @@ -1,511 +0,0 @@ -""" -Flask 路由定义 - -所有 Web 端点的路由注册,不包含业务逻辑(业务逻辑在 pipeline_web 和 sse_handler 中)。 -使用 Blueprint 模式,支持延迟注册到 Flask app。 -""" - -import csv as csv_module -import json -import threading -import time -import uuid -from pathlib import Path -from typing import Any -from urllib.parse import quote - -from flask import Blueprint, Response, jsonify, render_template, request, stream_with_context - -from src.config import ( - SAFE_CONFIG_KEYS, - SESSION_CONFIG_KEYS, - load_session_config, -) -from src.config import ( - load_config as load_project_config, -) -from src.infra.documents import load_csv, load_invoice_csv - -from . import pipeline_web, sse_handler - -# 在模块加载时确定(由 init_routes 传入) -_UPLOAD_BASE: Path | None = None - -web_bp = Blueprint("web", __name__) - - -def init_routes(upload_base: Path) -> None: - """初始化路由配置,传入上传目录""" - global _UPLOAD_BASE - _UPLOAD_BASE = upload_base - - -# ================================================================ -# 辅助函数 -def _validate_session(session_id: str) -> Path | tuple[Response, int]: - """验证会话 ID 并返回会话目录""" - if _UPLOAD_BASE is None: - return jsonify({"error": "服务未初始化"}), 500 - session_dir = _UPLOAD_BASE / session_id - if not session_dir.exists(): - return jsonify({"error": "会话不存在"}), 404 - return session_dir - - -def _build_web_config(body: dict[str, Any]) -> dict[str, Any]: - """从请求体构建配置""" - config = load_project_config() - for key in SESSION_CONFIG_KEYS: - if body.get(key): - # 基本类型校验:防止非字符串值写入配置 - if not isinstance(body[key], (str, int, float, bool)): - continue - config[key] = body[key] - return config - - -def _run_session_task( - session_dir: Path, - handler: Any, - task_fn: Any, - config: dict[str, Any] | None = None, -) -> None: - """后台任务的通用包装器 - - 执行任务并把结果写进 result.json,供 SSE 端点收尾。 - - Args: - session_dir: 会话目录 - handler: SSE 日志收集器句柄 - task_fn: 业务逻辑回调,接收 (session_dir, config) 返回 result_dict - config: 配置字典(可选) - """ - result = {"ok": False, "error": "未知错误"} - try: - # 清除上一轮残留文件,避免 SSE 连接立即读到旧数据 - for fname in ("llm_stream.log", pipeline_web.SESSION_RESULT_FILE): - try: - (session_dir / fname).unlink(missing_ok=True) - except Exception: - pass - - ret = task_fn(session_dir, config) - if isinstance(ret, dict): - result = ret - - except Exception as e: - result = {"ok": False, "error": str(e)} - finally: - # 统一写入 result.json,SSE 端点检测到后发射 done 事件 - try: - tmp_path = session_dir / (pipeline_web.SESSION_RESULT_FILE + ".tmp") - with open(tmp_path, "w", encoding="utf-8") as f: - json.dump(result, f, ensure_ascii=False) - tmp_path.replace(session_dir / pipeline_web.SESSION_RESULT_FILE) - except Exception: - pass - sse_handler.remove_log_collector(handler) - - -# ================================================================ -# 页面路由 -# ================================================================ - - -@web_bp.route("/") -def index() -> Any: - return render_template("index.html") - - -@web_bp.route("/mobile/") -def mobile_upload(session_id: str) -> Any: - """移动端上传页面""" - if _UPLOAD_BASE is None: - return render_template("mobile_upload.html", error="服务未初始化"), 500 - session_dir = _UPLOAD_BASE / session_id - if not session_dir.exists(): - return render_template("mobile_upload.html", error="会话不存在"), 404 - return render_template("mobile_upload.html", session_id=session_id) - - -# ================================================================ -# 会话管理 -# ================================================================ - - -@web_bp.route("/api/session", methods=["POST"]) -def create_session() -> Any: - """创建上传会话,返回 session_id""" - if _UPLOAD_BASE is None: - return jsonify({"error": "服务未初始化"}), 500 - sid = uuid.uuid4().hex[:12] - session_dir = _UPLOAD_BASE / sid - session_dir.mkdir(parents=True, exist_ok=True) - return jsonify({"session_id": sid}) - - -# ================================================================ -# 文件上传与下载 -# ================================================================ - - -@web_bp.route("/api/upload/", methods=["POST"]) -def upload_file(session_id: str) -> Any: - """上传 PDF 或图片""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - f = request.files.get("file") - if not f or not f.filename: - return jsonify({"error": "未选择文件"}), 400 - - safe_name = Path(f.filename).name - # 安全检查:拒绝包含路径穿越字符的文件名 - if ".." in safe_name or "/" in safe_name or "\\" in safe_name: - return jsonify({"error": "文件名包含非法字符"}), 400 - f.save(str(session_dir / safe_name)) - return jsonify({"ok": True, "filename": safe_name}) - - -@web_bp.route("/api/files/", methods=["GET"]) -def list_files(session_id: str) -> Any: - """列出会话目录中的文件(统一列表)""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - pdf_exts = {".pdf"} - img_exts = {".png", ".jpg", ".jpeg", ".bmp", ".webp"} - - files = [] - for f in sorted(session_dir.iterdir()): - if not f.is_file(): - continue - ext = f.suffix.lower() - if ext in pdf_exts: - files.append({"name": f.name, "type": "pdf", "size": f.stat().st_size}) - elif ext in img_exts: - files.append({"name": f.name, "type": "image", "size": f.stat().st_size}) - - return jsonify( - { - "files": files, - "pdfs": [item["name"] for item in files if item["type"] == "pdf"], - "images": [item["name"] for item in files if item["type"] == "image"], - } - ) - - -@web_bp.route("/api/download//") -def download_file(session_id: str, filename: str) -> Any: - """下载生成的文件""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - safe_name = Path(filename).name - filepath = session_dir / safe_name - if not filepath.exists(): - return jsonify({"error": "文件不存在"}), 404 - - if safe_name.endswith(".doc"): - mimetype = "application/msword" - elif safe_name.endswith(".csv"): - mimetype = "text/csv; charset=utf-8" - else: - mimetype = "application/octet-stream" - - disposition = f"attachment; filename*=UTF-8''{quote(safe_name)}" - return Response( - filepath.read_bytes(), - mimetype=mimetype, - headers={"Content-Disposition": disposition}, - ) - - -@web_bp.route("/api/mobile-upload/", methods=["POST"]) -def mobile_upload_file(session_id: str) -> Any: - """移动端上传图片(复用 PC 上传逻辑)""" - return upload_file(session_id) - - -# ================================================================ -# 配置 -# ================================================================ - - -@web_bp.route("/api/config/", methods=["GET"]) -def get_session_config(session_id: str) -> Any: - """获取当前会话的配置(供前端回填表单) - - 使用白名单过滤会话配置,防止密码等敏感字段被加载到内存。 - password 字段始终返回空字符串——密码由前端用户输入,不持久化。 - 注意:当前为 localhost 服务,密码通过 HTTP 明文传输。如需通过代理暴露服务, - 请启用 HTTPS 或使用反向代理加密。 - """ - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - config = load_project_config() - cfg_path = session_dir / "config.json" - if cfg_path.exists(): - # 仅允许覆盖前端表单字段(不含密码) - with open(cfg_path, encoding="utf-8") as f: - session_cfg = json.load(f) - for key in SAFE_CONFIG_KEYS: - if key in session_cfg: - config[key] = session_cfg[key] - return jsonify( - { - "username": config.get("username", ""), - "password": "", - "default_name": config.get("default_name", ""), - "default_card_no": config.get("default_card_no", ""), - "default_person_id": config.get("default_person_id", ""), - "consumable_storage": config.get("consumable_storage", ""), - } - ) - - -# ================================================================ -# 发票数据处理 -# ================================================================ - - -@web_bp.route("/api/data/", methods=["GET"]) -def get_invoice_data(session_id: str) -> Any: - """读取发票数据并返回 JSON(供前端表格编辑)""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - # 优先读取支付记录 CSV - payment_csv = session_dir / "payment_records.csv" - if payment_csv.exists(): - rows = load_csv(payment_csv) - if rows is not None: - data: list[dict[str, Any]] = [] - for i, row in enumerate(rows): - entry: dict[str, Any] = dict(row) - entry["__row"] = i - data.append(entry) - fields = [k for k in rows[0].keys() if not k.startswith("__")] if rows else [] - return jsonify({"csv_filename": payment_csv.name, "fields": fields, "data": data}) - - # 回退到发票级别 CSV - invoice_csv = session_dir / "invoice_summary.csv" - if invoice_csv.exists(): - rows = load_invoice_csv(invoice_csv) - if rows is not None: - invoice_data: list[dict[str, Any]] = [] - for i, row in enumerate(rows): - entry2: dict[str, Any] = dict(row) - entry2["__row"] = i - invoice_data.append(entry2) - fields = [k for k in rows[0].keys() if not k.startswith("__")] if rows else [] - return jsonify({"csv_filename": invoice_csv.name, "fields": fields, "data": invoice_data}) - - # 最后尝试任意 CSV - csv_files = list(session_dir.glob("*.csv")) - csv_files = [f for f in csv_files if f.name != pipeline_web.SESSION_RESULT_FILE] - if csv_files: - csv_path = csv_files[0] - rows = load_csv(csv_path) - if rows is None: - rows = load_invoice_csv(csv_path) - if rows is not None: - fallback_data: list[dict[str, Any]] = [] - for i, row in enumerate(rows): - entry3: dict[str, Any] = dict(row) - entry3["__row"] = i - fallback_data.append(entry3) - fields = [k for k in rows[0].keys() if not k.startswith("__")] if rows else [] - return jsonify({"csv_filename": csv_path.name, "fields": fields, "data": fallback_data}) - - return jsonify({"error": "未找到发票数据,请先处理"}), 404 - - -@web_bp.route("/api/save/", methods=["POST"]) -def save_invoice_data(session_id: str) -> Any: - """保存前端编辑后的发票数据到 CSV""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - body = request.get_json(silent=True) or {} - data = body.get("data", []) - csv_filename = body.get("csv_filename", "invoice_summary.csv") - - csv_path = session_dir / csv_filename - if not csv_path.exists(): - return jsonify({"error": "CSV 文件不存在"}), 404 - - original_rows = load_csv(csv_path) - if original_rows is None or len(original_rows) == 0: - return jsonify({"error": "无法读取原始 CSV 结构"}), 500 - - fieldnames = list(original_rows[0].keys()) - - # 校验前端传入的 data 字段是否与原始 CSV 列匹配 - if data: - unknown_keys = set(data[0].keys()) - (set(fieldnames) | {"__row"}) - if unknown_keys: - return jsonify({"error": f"数据包含未知字段: {unknown_keys}"}), 400 - - with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: - writer = csv_module.DictWriter(f, fieldnames=fieldnames) - writer.writeheader() - for entry in data: - row = {k: entry.get(k, "") for k in fieldnames} - writer.writerow(row) - - resp: dict[str, str | bool | None] = {"ok": True} - config = load_session_config(session_dir) - doc_fill = pipeline_web._try_fill_consumable_doc(session_dir, config) - if doc_fill.get("ok"): - fn = doc_fill["doc_filename"] - resp["doc_url"] = f"/api/download/{session_id}/{quote(fn)}" - resp["doc_ok"] = True - elif doc_fill.get("skipped"): - resp["doc_ok"] = None - resp["doc_skipped"] = True - else: - resp["doc_ok"] = False - resp["doc_error"] = doc_fill.get("error") or "" - return jsonify(resp) - - -# ================================================================ -# 管道处理 -# ================================================================ - - -@web_bp.route("/api/process/", methods=["POST"]) -def start_process(session_id: str) -> Any: - """启动管道处理(仅发票提取,不自动提交财务系统)""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - body = request.get_json(silent=True) or {} - config = _build_web_config(body) - - with open(session_dir / "config.json", "w", encoding="utf-8") as f: - json.dump(config, f, ensure_ascii=False, indent=2, default=str) - - def _task(sd: Path, cfg: dict[str, Any] | None) -> dict[str, Any]: - return pipeline_web.run_pipeline_web(sd, cfg) - - handler = sse_handler.install_log_collector(session_dir) - threading.Thread( - target=_run_session_task, - kwargs={"session_dir": session_dir, "handler": handler, "task_fn": _task, "config": config}, - daemon=True, - ).start() - return jsonify({"status": "started"}) - - -@web_bp.route("/api/logs/") -def stream_logs(session_id: str) -> Any: - """SSE 日志流""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - def generate() -> Any: - log_file = session_dir / sse_handler.SESSION_LOG_FILE - last_size = 0 - file_events_file = session_dir / "file_events.log" - last_events_size = 0 - last_stream_size = 0 - - start_time = time.time() - timeout = 900 # 略大于 LLM 请求超时 (600s),防止 SSE 先断开 - - while time.time() - start_time < timeout: - # 轮询普通日志 - if log_file.exists(): - current_size = log_file.stat().st_size - if current_size > last_size: - with open(log_file, encoding="utf-8", errors="replace") as f: - f.seek(last_size) - chunk = f.read() - if chunk: - yield f"data: {sse_handler.escape_sse(chunk)}\n\n" - last_size = current_size - - # 轮询文件进度事件 - if file_events_file.exists(): - events_size = file_events_file.stat().st_size - if events_size > last_events_size: - with open(file_events_file, encoding="utf-8", errors="replace") as f: - f.seek(last_events_size) - new_events = f.read() - if new_events: - for line in new_events.strip().split("\n"): - line = line.strip() - if line: - yield f"data: {line}\n\n" - last_events_size = events_size - - # 轮询 LLM 流式事件 - llm_stream_file = session_dir / "llm_stream.log" - if llm_stream_file.exists(): - stream_size = llm_stream_file.stat().st_size - if stream_size > last_stream_size: - with open(llm_stream_file, encoding="utf-8", errors="replace") as f: - f.seek(last_stream_size) - new_chunks = f.read() - if new_chunks: - for line in new_chunks.strip().split("\n"): - line = line.strip() - if line: - yield f"data: {line}\n\n" - last_stream_size = stream_size - - # 检查是否完成 - result_file = session_dir / pipeline_web.SESSION_RESULT_FILE - if result_file.exists(): - with open(result_file, encoding="utf-8") as f: - result = json.load(f) - yield f"data: {sse_handler.escape_sse(json.dumps({'type': 'done', 'result': result}, ensure_ascii=False))}\n\n" - break - - time.sleep(0.5) - - return Response( - stream_with_context(generate()), - mimetype="text/event-stream", - headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"}, - ) - - -@web_bp.route("/api/submit-financial/", methods=["POST"]) -def submit_financial(session_id: str) -> Any: - """手动触发财务系统填报""" - session_dir = _validate_session(session_id) - if isinstance(session_dir, tuple): - return session_dir - - config_path = session_dir / "config.json" - if not config_path.exists(): - return jsonify({"error": "未找到配置,请先配置后处理"}), 400 - - with open(config_path, encoding="utf-8") as f: - config = json.load(f) - - def _task(sd: Path, cfg: dict[str, Any] | None) -> dict[str, Any]: - submit_result = pipeline_web.run_financial_submit(sd, cfg) - if submit_result.get("ok"): - return {"ok": True} - return submit_result - - handler = sse_handler.install_log_collector(session_dir) - threading.Thread( - target=_run_session_task, - kwargs={"session_dir": session_dir, "handler": handler, "task_fn": _task, "config": config}, - daemon=True, - ).start() - return jsonify({"status": "started"}) diff --git a/src/web/sse_handler.py b/src/web/sse_handler.py deleted file mode 100644 index acf85c8..0000000 --- a/src/web/sse_handler.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -SSE 日志流处理 - -负责: -- 日志收集器安装/卸载 -- SSE 数据转义 -""" - -import logging -import threading -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - pass - -SESSION_LOG_FILE = "session.log" - -# 需要监控日志的模块名称列表 -LOG_TARGET_NAMES = [ - "extractor", - "llm_extractor", - "matcher", - "pipeline", - "bot", - "fill_consumable_doc", - "agent", - "validator", -] - - -class SSELogHandler(logging.Handler): - """将日志写入指定文件(线程安全)""" - - def __init__(self, log_path: Path): - super().__init__() - self._lock = threading.Lock() - self._file = open(log_path, "w", encoding="utf-8") - - def emit(self, record: logging.LogRecord) -> None: - try: - msg = self.format(record) + "\n" - with self._lock: - self._file.write(msg) - self._file.flush() - except Exception: - pass - - def close_file(self) -> None: - with self._lock: - try: - self._file.close() - except Exception: - pass - - -def install_log_collector(session_dir: Path) -> SSELogHandler: - """安装日志收集器到各模块""" - log_path = session_dir / SESSION_LOG_FILE - fmt = logging.Formatter( - "%(asctime)s [%(levelname)-5s] %(name)s: %(message)s", - "%Y-%m-%d %H:%M:%S", - ) - handler = SSELogHandler(log_path) - handler.setFormatter(fmt) - handler.setLevel(logging.INFO) - - for name in LOG_TARGET_NAMES: - logger = logging.getLogger(name) - logger.setLevel(logging.INFO) - logger.addHandler(handler) - - return handler - - -def remove_log_collector(handler: SSELogHandler) -> None: - """卸载日志收集器""" - for name in LOG_TARGET_NAMES: - logging.getLogger(name).removeHandler(handler) - handler.close_file() - - -def escape_sse(text: str) -> str: - """SSE 数据转义,同时处理 Windows 行尾 \r\n""" - return text.replace("\r\n", "\n").replace("\r", "\n").replace("\n", "\ndata: ") diff --git a/src/web/static/README.md b/src/web/static/README.md deleted file mode 100644 index 4f2c82c..0000000 --- a/src/web/static/README.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -last_reviewed: 2026-06-12 ---- - -# src/web/static — 静态资源目录 - -存放 Web 界面的 CSS 样式表和 JavaScript 前端逻辑。 - -## 文件结构 - -| 路径 | 说明 | -|------|------| -| `css/index.css` | 全局样式:上传区域、日志面板、可编辑表格、状态徽章 | -| `js/index.js` | 前端逻辑:文件上传、SSE 日志监听、发票数据编辑、配置同步、移动端扫码上传、财务提交 | - -## 技术栈 - -原生 JavaScript + Bootstrap 5,无构建工具,保持单页应用轻量可维护。 - -## 变更记录 - -- **2026-06-12**:修复配置收集流程的异步时序问题 - - `parseConfigFile` 改为返回 Promise,`handleFiles` 和拖拽 `drop` 处理器改为 `async/await`,确保 config.json 解析完成后再执行检查,消息显示顺序正确 - - `sendUserMessage` 修复变量名错误(`pendingConfigKeys` → `pendingConfigKey`),修复了配置收集卡死的问题 - - 拆分 `checkAutoStart` 为两个函数:`checkAutoStart` 仅做静默检查(由 `syncFiles` 轮询调用),`promptMissingConfig` 负责配置提示(由用户主动上传完成后调用),避免轮询提前触发配置提示导致消息乱序 - - 移除所有聊天消息的删除逻辑,聊天窗口保留完整历史(欢迎消息、文件通知、配置交互、处理结果均不删除) \ No newline at end of file diff --git a/src/web/static/css/index.css b/src/web/static/css/index.css deleted file mode 100644 index 9d4aa83..0000000 --- a/src/web/static/css/index.css +++ /dev/null @@ -1,592 +0,0 @@ -body { - background: #f5f7fa; - min-height: 100vh; - margin: 0; - display: flex; - flex-direction: column; -} - -.main-container { - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 16px; - flex: 1; - display: flex; - flex-direction: column; - box-sizing: border-box; -} -.upload-zone { - border: 2px dashed #ccc; border-radius: 12px; padding: 28px; text-align: center; - cursor: pointer; transition: all .2s; background: #fff; height: 100px; - display: flex; flex-direction: column; align-items: center; justify-content: center; -} -.upload-zone:hover, .upload-zone.dragover { border-color: #667eea; background: #f0f2ff; } -.upload-zone.active { border-color: #28a745; background: #f0fff4; } -.upload-zone .icon { - width: 48px; - height: 48px; - margin-bottom: 8px; -} -.upload-zone .icon img { - width: 100%; - height: 100%; -} - -/* 可折叠上传面板(在文档流中,向上推挤聊天窗口) */ -.upload-panel { - margin-top: 12px; - background: #fff; - border: 1px solid #e8e8e8; - border-radius: 12px; - overflow: hidden; - transition: box-shadow 0.2s; -} -.upload-panel-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 16px; - cursor: pointer; - user-select: none; - font-size: 14px; - font-weight: 600; - color: #333; - background: #fafbfc; - transition: background 0.2s; -} -.upload-panel-header:hover { - background: #f0f2ff; -} -.upload-panel-toggle { - font-size: 10px; - color: #999; - transition: transform 0.3s; -} -.upload-panel-toggle.collapsed { - transform: rotate(180deg); -} -.upload-panel-body { - padding: 16px; - transition: max-height 0.3s ease, padding 0.3s ease, opacity 0.3s ease; - max-height: 400px; - opacity: 1; - overflow: hidden; -} -.upload-panel-body.collapsed { - max-height: 0; - padding-top: 0; - padding-bottom: 0; - opacity: 0; -} -.file-tag { display: inline-block; background: #e8f0fe; border-radius: 4px; padding: 2px 10px; margin: 3px; font-size: 13px; } -.file-tag .remove { cursor: pointer; color: #c00; margin-left: 6px; font-weight: bold; } -.chat-container { - background: #fff; - border-radius: 12px; - padding: 16px; - flex: 1 1 0; - min-height: 300px; - overflow: hidden; - border: 1px solid #e8e8e8; - display: flex; - flex-direction: column; - gap: 0; -} - -.chat-messages { - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 12px; - scroll-behavior: smooth; - padding-bottom: 8px; -} - -.chat-input-area { - display: flex; - gap: 8px; - padding-top: 12px; - border-top: 1px solid #e8e8e8; - flex-shrink: 0; -} - -.chat-input-area input { - flex: 1; - border: 1px solid #ddd; - border-radius: 20px; - padding: 8px 16px; - font-size: 13px; - outline: none; - transition: border-color 0.2s; -} - -.chat-input-area input:focus { - border-color: #667eea; -} - -.chat-input-area button { - border-radius: 20px; - padding: 8px 20px; - font-size: 13px; - white-space: nowrap; -} - -.chat-message { - display: flex; - align-items: flex-start; - gap: 10px; - padding: 8px 0; - animation: fadeIn 0.3s ease; -} - -.chat-message.user { - flex-direction: row-reverse; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } -} - -.ai-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - overflow: hidden; -} -.ai-avatar img { - width: 60%; - height: 60%; -} - -.chat-bubble { - max-width: 85%; - padding: 10px 14px; - border-radius: 12px; - font-size: 13px; - line-height: 1.6; - white-space: pre-wrap; - word-break: break-word; -} - -.chat-bubble.system { - background: #f0f2f5; - color: #333; - border-bottom-left-radius: 4px; -} - -.chat-bubble.processing { - background: #e8f0fe; - color: #1a73e8; - border-bottom-left-radius: 4px; -} - -.chat-bubble.success { - background: #e6f4ea; - color: #137333; - border-bottom-left-radius: 4px; -} - -.chat-bubble.error { - background: #fce8e6; - color: #c5221f; - border-bottom-left-radius: 4px; -} - -.chat-bubble.done { - background: #e6f4ea; - color: #137333; - border-bottom-left-radius: 4px; - font-weight: 500; -} - -.chat-bubble.user { - background: #667eea; - color: #fff; - border-bottom-right-radius: 4px; -} - -/* 打字指示器 */ -.typing-indicator { - display: inline-flex; - gap: 4px; - padding: 4px 0; -} - -.typing-indicator span { - width: 6px; - height: 6px; - border-radius: 50%; - background: #667eea; - animation: typing 1.4s infinite ease-in-out; -} - -.typing-indicator span:nth-child(1) { animation-delay: 0s; } -.typing-indicator span:nth-child(2) { animation-delay: 0.2s; } -.typing-indicator span:nth-child(3) { animation-delay: 0.4s; } - -@keyframes typing { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } - 40% { transform: scale(1); opacity: 1; } -} -.section-title { font-size: 15px; font-weight: 600; color: #333; margin-bottom: 12px; } -.btn-process { font-size: 17px; padding: 10px 40px; } -.status-badge { font-size: 13px; } - -/* 可编辑表格 */ -.table-editable { font-size: 13px; } -.table-editable th { position: sticky; top: 0; background: #f8f9fa; z-index: 1; font-weight: 600; white-space: nowrap; } -.table-editable td { vertical-align: middle; } -.table-editable input.form-control { font-size: 13px; padding: 4px 8px; min-width: 80px; } -.table-wrapper { max-height: 500px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 8px; } -.edit-note { font-size: 12px; color: #888; margin-bottom: 8px; } - -/* ================================================================ */ -/* 文件消息样式 */ -/* ================================================================ */ - -.file-bubble { - background: #fafbfc; - border: 1px solid #e8e8e8; - border-bottom-left-radius: 4px; - transition: border-color 0.3s, background 0.3s; -} - -.file-bubble.file-processing { - background: #f0f4ff; - border-color: #667eea; -} - -.file-bubble.file-done { - background: #f0fff4; - border-color: #28a745; -} - -.file-bubble.file-error { - background: #fff5f5; - border-color: #c5221f; -} - -.file-name-row { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.file-icon { - width: 18px; - height: 18px; - flex-shrink: 0; -} -.file-icon img { - width: 100%; - height: 100%; -} - -.file-name { - font-size: 13px; - font-weight: 500; - color: #333; - word-break: break-all; -} - -.file-status { - display: inline-flex; - align-items: center; - margin-left: 4px; -} - -.file-status-icon { - font-size: 14px; -} - -/* 文件消息的三点动画(与文件名同一行) */ -.file-typing-indicator { - display: inline-flex; - gap: 3px; - align-items: center; - margin-left: 2px; -} - -.file-typing-indicator span { - width: 5px; - height: 5px; - border-radius: 50%; - background: #667eea; - animation: fileTyping 1.4s infinite ease-in-out; -} - -.file-typing-indicator span:nth-child(1) { animation-delay: 0s; } -.file-typing-indicator span:nth-child(2) { animation-delay: 0.2s; } -.file-typing-indicator span:nth-child(3) { animation-delay: 0.4s; } - -@keyframes fileTyping { - 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } - 40% { transform: scale(1); opacity: 1; } -} - -/* 文件详情区域 */ -.file-detail { - margin-top: 8px; - padding-top: 8px; - border-top: 1px dashed #ddd; - font-size: 12px; - line-height: 1.8; - color: #555; -} - -.detail-label { - font-weight: 600; - color: #667eea; -} - -.detail-value { - color: #333; -} - -.detail-value.error-text { - color: #c5221f; -} - -/* ================================================================ */ -/* LLM 流式聊天气泡样式 */ -/* ================================================================ */ - -.llm-stream-bubble { - background: #f0f4ff; - border: 1px solid #667eea; - border-bottom-left-radius: 4px; -} - -.llm-stream-bubble.done { - background: #e6f4ea; - border-color: #28a745; -} - -.llm-stream-bubble.error { - background: #fce8e6; - border-color: #c5221f; -} - -.llm-stream-label { - font-size: 12px; - font-weight: 600; - color: #667eea; - margin-bottom: 6px; -} - -.llm-stream-text { - font-size: 13px; - line-height: 1.6; - color: #333; - white-space: pre-wrap; - word-break: break-word; - max-height: 300px; - overflow-y: auto; -} - -/* 可折叠的思考过程区域 */ -.llm-reasoning-section { - margin: 6px 0; - padding: 4px 0; - border-top: 1px dashed #ddd; - border-bottom: 1px dashed #ddd; -} - -.llm-reasoning-summary { - font-size: 12px; - font-weight: 600; - color: #888; - cursor: pointer; - user-select: none; - list-style: none; - display: flex; - align-items: center; - gap: 4px; -} - -.llm-reasoning-summary::-webkit-details-marker { - display: none; -} - -.llm-reasoning-summary::before { - content: '▶'; - font-size: 8px; - display: inline-block; - transition: transform 0.2s; -} - -.llm-reasoning-section[open] > .llm-reasoning-summary::before { - transform: rotate(90deg); -} - -.llm-reasoning-text { - font-size: 12px; - line-height: 1.5; - color: #777; - white-space: pre-wrap; - word-break: break-word; - max-height: 200px; - overflow-y: auto; - padding: 4px 8px; - margin-top: 4px; - background: #fafbfc; - border-radius: 4px; -} - -/* ================================================================ */ -/* Agent 请求面板样式 */ -/* ================================================================ */ - -.agent-request-panel { - background: linear-gradient(135deg, #fff8e1 0%, #fff3cd 100%); - border: 1px solid #ffc107; - border-radius: 12px; - padding: 14px 16px; - margin: 8px 0; - animation: fadeIn 0.3s ease; -} - -.agent-request-header { - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - font-weight: 600; - color: #856404; - margin-bottom: 10px; -} - -.agent-request-icon { - width: 20px; - height: 20px; - flex-shrink: 0; -} -.agent-request-icon img { - width: 100%; - height: 100%; -} - -.agent-request-materials { - font-size: 13px; - color: #856404; - margin-bottom: 12px; - line-height: 1.6; -} - -.material-tag { - display: inline-block; - background: #ffc107; - color: #000; - border-radius: 4px; - padding: 2px 10px; - margin: 2px 4px 2px 0; - font-size: 12px; - font-weight: 500; -} - -.agent-request-actions { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.agent-request-actions .btn { - font-size: 13px; - padding: 6px 16px; -} - -/* ================================================================ */ -/* 提取结果摘要卡片样式 */ -/* ================================================================ */ - -.extraction-summary { - margin-top: 8px; - padding-top: 8px; - border-top: 1px dashed #ccc; -} - -.extraction-summary .summary-section { - margin-bottom: 12px; -} - -.extraction-summary .summary-section:last-child { - margin-bottom: 0; -} - -.extraction-summary .summary-section-title { - font-size: 13px; - font-weight: 700; - color: #333; - margin-bottom: 6px; - padding-bottom: 4px; - border-bottom: 1px solid #eee; -} - -.extraction-summary .summary-row { - display: flex; - gap: 8px; - padding: 2px 0; - font-size: 12px; - line-height: 1.8; -} - -.extraction-summary .summary-label { - color: #888; - min-width: 60px; - flex-shrink: 0; -} - -.extraction-summary .summary-value { - color: #333; - flex: 1; -} - -.extraction-summary .summary-table { - width: 100%; - border-collapse: collapse; - font-size: 12px; - margin-top: 4px; -} - -.extraction-summary .summary-table th { - background: #f5f6f8; - color: #555; - font-weight: 600; - padding: 5px 8px; - text-align: left; - border-bottom: 1px solid #e0e0e0; - white-space: nowrap; -} - -.extraction-summary .summary-table td { - padding: 4px 8px; - border-bottom: 1px solid #f0f0f0; - color: #333; - vertical-align: top; -} - -.extraction-summary .summary-table tbody tr:hover { - background: #fafbfc; -} - -.extraction-summary .summary-attachment-list { - margin: 4px 0 0; - padding-left: 20px; - font-size: 12px; - color: #555; - line-height: 1.8; -} - -.extraction-summary .summary-attachment-item::marker { - color: #999; - font-size: 10px; -} \ No newline at end of file diff --git a/src/web/static/icon/agent.svg b/src/web/static/icon/agent.svg deleted file mode 100644 index 672fbf9..0000000 --- a/src/web/static/icon/agent.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/web/static/icon/file.svg b/src/web/static/icon/file.svg deleted file mode 100644 index 5d98d05..0000000 --- a/src/web/static/icon/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/web/static/icon/folder.svg b/src/web/static/icon/folder.svg deleted file mode 100644 index c1bc751..0000000 --- a/src/web/static/icon/folder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/web/static/icon/gallery.svg b/src/web/static/icon/gallery.svg deleted file mode 100644 index 3a62658..0000000 --- a/src/web/static/icon/gallery.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/web/static/icon/phone.svg b/src/web/static/icon/phone.svg deleted file mode 100644 index 2eb2ad4..0000000 --- a/src/web/static/icon/phone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/web/static/icon/user.svg b/src/web/static/icon/user.svg deleted file mode 100644 index 014bb70..0000000 --- a/src/web/static/icon/user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/web/static/js/README.md b/src/web/static/js/README.md deleted file mode 100644 index 5bf17a9..0000000 --- a/src/web/static/js/README.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -last_reviewed: 2026-06-16 ---- - -# src/web/static/js — 前端逻辑模块 - -## 概述 - -财务报销自动化系统的前端入口脚本,按功能拆分为多个模块文件,通过 `App` 共享状态对象通信。采用原生 JavaScript 编写,通过 HTTP 请求与 Flask 后端通信,使用 SSE 接收处理进度。 - -## 文件结构 - -| 文件 | 职责 | -|------|------| -| `state.js` | 全局状态管理 (`App` 对象,含处理状态机) | -| `utils.js` | 工具函数 (HTML 转义、会话管理) | -| `chat.js` | 聊天窗口消息管理(含文件消息、处理进度、三点动画、LLM 流式气泡) | -| `upload.js` | 文件上传(含拖拽、config.json 解析) | -| `config.js` | 配置收集与校验(基于状态机的自动触发) | -| `process.js` | 发票处理流程与 SSE 监听(含 file_progress、llm_stream 事件处理) | -| `agent.js` | Agent 交互(含 supplement 流程、强制提交、用户文字补充、自动提交) | -| `sync.js` | 移动端同步轮询(状态感知模式) | -| `index.js` | 入口初始化 | - -## 加载顺序 - -``` -state.js → utils.js → chat.js → upload.js → config.js → process.js → sync.js → index.js -``` - -## 模块依赖关系 - -``` -state.js (无依赖) - │ - ├── utils.js ────────────────┐ - │ │ - ├── chat.js ─────────────────┐ │ - │ │ │ - ├── upload.js ──────────────┐ │ │ - │ │ │ │ - ├── config.js ─────────────┐ │ │ │ - │ │ │ │ │ - ├── process.js ───────────┐ │ │ │ │ - │ │ │ │ │ │ - ├── sync.js ──────────┐ │ │ │ │ │ - │ │ │ │ │ │ │ - └── index.js ────────┘ │ │ │ │ │ │ -``` - -## 处理状态机 - -`App.processState` 控制何时触发自动处理,避免重复执行: - -```mermaid -stateDiagram-v2 - [*] --> idle - - idle --> processing: 配置完整 + 有文件 - processing --> done: 处理完成 - processing --> awaiting_supplement: Agent 要求补充材料 - - done --> idle: 有新文件 / forceStart = true - done --> done: 无新文件(保持,不重复) - - awaiting_supplement --> idle: 用户补充文件(只上传新文件) - - idle --> processing: idle + newFilenames > 0(走 supplement 流程) - idle --> awaiting_files: AI 判断文件不足(未来功能) - awaiting_files --> idle: 用户补充文件 - awaiting_files --> processing: 用户说"直接开始" (forceStart = true) -``` - -状态字段说明: - -| 字段 | 类型 | 说明 | -|------|------|------| -| `processState` | string | 当前处理状态:idle / processing / done / awaiting_files / awaiting_supplement | -| `lastProcessedFileCount` | number | 上次处理时的文件数,用于检测是否有新文件 | -| `forceStart` | boolean | 用户强制开始标记(兼容"文件不足也要处理"的场景) | -| `newFilenames` | array | 待补充上传的文件名列表,用于区分首次处理和补充处理 | - -## 核心流程 - -### 1. 文件上传 - -``` -用户上传文件 → addFileMessage() → 注册到 App.fileMessageMap → promptMissingConfig() - → processState 为 done 时重置为 idle(支持增量上传后重新处理) -``` - -### 2. 配置收集 - -``` -promptMissingConfig() → 检查 CONFIG_FIELDS → 有缺失则提示 → 用户输入 → sendUserMessage() → 递归直到完整 -``` - -### 3. 自动触发检查 (checkAutoStart) - -``` -checkAutoStart() → 状态机判断: - - processing → 跳过 - - done + 无新文件 → 跳过(修复重复执行问题) - - done + 有新文件 → 走 supplement 流程(不再走 startProcess) - - idle + newFilenames > 0 → 走 supplement 流程 - - idle + 配置完整 + 有文件 → 走 startProcess(首次处理) - - awaiting_files + forceStart → 触发 -``` - -### 4. 发票处理(首次) - -``` -startProcess() → ensureSession → 上传全部文件 → /api/agent/process → SSE 监听 - → 收到 file_progress(processing) → setFileProcessing()(三点动画) - → 收到 file_progress(done) → setFileDone()(展示提取摘要) - → 收到 file_progress(cached) → setFileCached() - → 收到 file_progress(error) → setFileError() - → 收到 llm_stream(start) → handleLLMStream() 创建流式气泡 - → 收到 llm_stream(chunk) → handleLLMStream() 逐字追加 AI 输出 - → 收到 llm_stream(end) → handleLLMStream() 关闭流式气泡 - → 收到 done → 设置 processState = 'done' + 记录文件数 → 显示汇总结果 -``` - -### 5. 补充文件处理(新增) - -当 Agent 返回 `awaiting_supplement` 状态时,用户补充文件的流程: - -``` -用户上传补充文件 - → handleFiles() 检测 processState === 'awaiting_supplement' - → _uploadNewFiles() 只上传新文件(不再重新上传所有文件) - → App.newFilenames = [新文件名列表] - → App.processState = 'idle' - → syncFiles() 3 秒后触发 checkAutoStart() - → checkAutoStart() 检测到 idle + newFilenames > 0 - → handleSupplementUpload(filenames) - → POST /api/agent/supplement/(后端会重新 extract_invoices,旧文件走缓存) - → 新文件走 LLM 提取,旧文件命中 JSON 缓存直接返回 - → run_agent_round() 重新分析 - → SSE 返回 done → 根据结果进入 awaiting_supplement 或 done 状态 -``` - -**关键区别**:补充文件走 `/api/agent/supplement/` 而非 `/api/agent/process/`,后者在已有 agent_session 时会跳过提取阶段。 - -## 数据流 - -### 配置数据流 - -``` -config.json → parseConfigFile → App.sessionConfig → startProcess → /api/agent/process -``` - -### 发票数据流(首次处理) - -``` -发票文件 → startProcess → /api/upload → /api/agent/process → SSE file_events.log → 文件消息实时更新 -``` - -### 发票数据流(补充文件) - -``` -新发票文件 → _uploadNewFiles() → /api/upload → /api/agent/supplement - → extract_invoices()(旧文件走缓存,新文件走 LLM) - → run_agent_round() → SSE file_events.log → 文件消息实时更新 -``` - -### 文件消息状态机 - -``` -addFileMessage(filename) - → file_progress(processing) → 三点动画(同一行) - → file_progress(done/cached/error) → 详情展开 + 状态图标 -``` - -## 变更记录 - -- **2026-06-16**:修复补充文件重复上传问题 — 新增 `newFilenames` 状态字段,补充文件时只上传新文件并走 `/api/agent/supplement/` 接口。`upload.js` 新增 `_uploadNewFiles()` 辅助函数。`config.js` 的 `checkAutoStart()` 在 `done` 和 `idle` 状态下有 `newFilenames` 时走 supplement 流程而非 `startProcess()` -- **2026-06-16**:新增 LLM 流式思考展示 — `chat.js` 新增 `handleLLMStream` 状态机(start/chunk/end/error 四阶段),`process.js` 的 SSE 监听增加 `llm_stream` 事件分支,AI 思考过程以聊天气泡逐字展示 -- **2026-06-16**:引入处理状态机 — 新增 `processState` / `lastProcessedFileCount` / `forceStart` 状态字段,修复 `syncFiles` 轮询每 3 秒重复触发处理的 bug。`checkAutoStart` 改为基于状态机判断,done 状态下只有检测到新文件才重新触发。为未来 AI 判断文件是否充足预留了 `awaiting_files` 状态和 `forceStart` 标记 -- **2026-06-12**:上传区域可折叠 — 新增 `toggleUploadPanel()` 函数,配合 CSS `max-height` 动画实现面板收起/展开 -- **2026-06-12**:文件消息框对齐到用户侧 — `addFileMessage` 的 wrapper 新增 `user` 类,上传的文件消息显示在右侧(用户侧),与 agent 消息区分 -- **2026-06-12**:配置完成增加互动提示 — `promptMissingConfig` 在配置完整时发送系统消息告知用户即将开始处理发票 -- **2026-06-14**:新增文件进度实时反馈 — 后端 extractor 在每处理一个文件时产生 SSE 进度事件,前端根据文件名定位 DOM 并更新状态(处理中三点动画、完成摘要展示、缓存/错误状态) -- **2026-06-14**:移除发票编辑和财务提交流程 — 删除 table.js、financial.js 及相关 HTML 区域,后续开发不再支持手动编辑发票信息 -- **2026-06-12**:模块拆分重构 — 将 index.js (625 行) 拆分为 10 个独立模块,引入 `App` 共享状态对象 -- **2026-06-12**:修复配置收集流程的异步时序问题 — parseConfigFile 改为 Promise,拆分 checkAutoStart 为静默检查/配置提示两个函数 \ No newline at end of file diff --git a/src/web/static/js/agent.js b/src/web/static/js/agent.js deleted file mode 100644 index b851d34..0000000 --- a/src/web/static/js/agent.js +++ /dev/null @@ -1,295 +0,0 @@ -/** - * Agent 交互模块 - */ -import { App } from './state.js'; -import { escapeHtml } from './utils.js'; -import { addChatMessage, showStatus } from './chat.js'; -import { createSSEConnection, handleDoneResult } from './sse.js'; - -// ================================================================ -// Agent 事件处理 -// ================================================================ - -/** - * 处理 Agent SSE 事件 - * - * @param {Object} msg - SSE 传来的 agent 事件对象 - */ -export function handleAgentEvent(msg) { - switch (msg.type) { - case 'agent_state_change': - _handleAgentStateChange(msg); - break; - case 'agent_request_supplement': - _handleAgentRequestSupplement(msg); - break; - case 'agent_ready': - _handleAgentReady(msg); - break; - case 'agent_error': - _handleAgentError(msg); - break; - case 'agent_supplement_received': - _handleSupplementReceived(msg); - break; - case 'agent_force_submit': - _handleForceSubmit(msg); - break; - case 'agent_max_rounds': - _handleAgentError(msg); - break; - case 'agent_extract_status': - _handleAgentStateChange(msg); - break; - } -} - -/** - * 状态变更通知 — 使用瞬态消息,状态变化时更新而非追加 - */ -function _handleAgentStateChange(msg) { - const stateLabels = { - 'extracting': '正在分析文件...', - 'validating': '正在校验信息完整性...', - 'awaiting_supplement': '等待补充材料', - 'ready': '信息完整,可以提交', - 'submitting': '正在提交...', - }; - - const label = stateLabels[msg.state] || msg.message || '处理中...'; - - if (msg.state === 'ready') { - showStatus(label, 'done'); - } else { - showStatus(label); - } -} - -/** - * 请求补充材料 — 将 suggestion 以消息气泡形式单独展示 - */ -export function _handleAgentRequestSupplement(msg) { - const suggestion = msg.suggestion || '请补充上传相关材料'; - showStatus('信息不完整,请补充材料'); - addChatMessage(suggestion, 'system'); -} - -/** - * 信息完整 — 在消息气泡中提醒用户,最终结果由 done 事件处理 - */ -function _handleAgentReady(msg) { - addChatMessage(msg.message || '信息完整,正在提交到财务系统', 'done'); -} - -/** - * Agent 错误 - */ -function _handleAgentError(msg) { - addChatMessage(msg.message || 'Agent 处理出错', 'error'); - App.isProcessing = false; - App.processState = 'done'; -} - -/** - * 收到补充文件 - */ -function _handleSupplementReceived(msg) { - if (msg.supplement_type === 'text') { - addChatMessage(`已收到补充文字:${msg.text || ''}`, 'done'); - } else { - const fileNames = (msg.files || []).join('、'); - addChatMessage(`已收到补充文件:${fileNames}`, 'done'); - } -} - -/** - * 强制提交 - */ -function _handleForceSubmit(msg) { - showStatus('已跳过校验,开始提交到财务系统'); -} - -// ================================================================ -// Agent 请求面板 -// ================================================================ - -/** - * 显示 Agent 请求补充材料的 UI - */ -export function showAgentRequest(data) { - const existing = document.getElementById('agent-request-panel'); - if (existing) existing.remove(); - - const chatMessages = document.getElementById('chat-messages'); - if (!chatMessages) return; - - const panel = document.createElement('div'); - panel.id = 'agent-request-panel'; - panel.className = 'agent-request-panel'; - - let materialsHtml = ''; - if (data.missing_materials && data.missing_materials.length > 0) { - materialsHtml = ` -
- 需要补充: - ${data.missing_materials.map(m => `${escapeHtml(m)}`).join(' ')} -
- `; - } - - panel.innerHTML = ` -
- - 第 ${data.round || 1} 轮分析 - 信息不完整 -
- ${materialsHtml} -
- - -
- `; - - chatMessages.appendChild(panel); - chatMessages.scrollTop = chatMessages.scrollHeight; -} - -/** - * 移除 Agent 请求面板 - */ -export function hideAgentRequest() { - const panel = document.getElementById('agent-request-panel'); - if (panel) panel.remove(); -} - -// ================================================================ -// 用户文字补充 -// ================================================================ - -/** - * 处理用户通过输入框补充的文字信息 - * - * @param {string} text - 用户输入的文字 - */ -export async function handleUserSupplement(text) { - if (!App.sessionId) return; - - try { - showStatus('正在解析补充信息...'); - - const response = await fetch(`/api/agent/user-supplement/${App.sessionId}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: text }), - }); - - const result = await response.json(); - - if (result.status === 'started') { - showStatus('补充信息已收到,正在重新分析...'); - App.isProcessing = true; - - createSSEConnection(App.sessionId, { - onDone: (result) => { - handleDoneResult(result); - }, - handleFileProgress: false, - handleLLMStream: true, - }); - } else if (result.error) { - addChatMessage(`处理失败:${result.error}`, 'error'); - } - } catch (e) { - addChatMessage(`请求失败:${e.message}`, 'error'); - } -} - -// ================================================================ -// 用户输入处理 -// ================================================================ - -/** - * 处理强制提交 - */ -export async function handleForceSubmit() { - if (!App.sessionId) { - addChatMessage('请先上传文件并处理', 'error'); - return; - } - - if (App.agentEventSource) { - App.agentEventSource.close(); - App.agentEventSource = null; - } - - App.forceSubmitting = true; - - try { - showStatus('正在跳过校验并提交...'); - hideAgentRequest(); - - const response = await fetch(`/api/agent/force-submit/${App.sessionId}`, { - method: 'POST', - }); - - const result = await response.json(); - - if (result.status === 'started') { - showStatus('已跳过校验,开始提交到财务系统'); - App.isProcessing = true; - App.processState = 'submitting'; - - createSSEConnection(App.sessionId, { - onDone: (result) => { - handleDoneResult(result); - }, - handleFileProgress: false, - handleLLMStream: false, - }); - } - } catch (e) { - App.forceSubmitting = false; - addChatMessage(`请求失败:${e.message}`, 'error'); - } -} - -// ================================================================ -// 补充文件处理 -// ================================================================ - -/** - * 处理补充文件上传完成后的重新分析 - * - * @param {Array} filenames - 新上传的文件名列表 - */ -export async function handleSupplementUpload(filenames) { - if (!App.sessionId) return; - - try { - showStatus('收到补充文件,正在重新分析...'); - hideAgentRequest(); - - const response = await fetch(`/api/agent/supplement/${App.sessionId}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ files: filenames }), - }); - - const result = await response.json(); - - if (result.status === 'started') { - App.isProcessing = true; - - createSSEConnection(App.sessionId, { - onDone: (result) => { - handleDoneResult(result); - }, - }); - } - } catch (e) { - addChatMessage(`请求失败:${e.message}`, 'error'); - } -} \ No newline at end of file diff --git a/src/web/static/js/chat.js b/src/web/static/js/chat.js deleted file mode 100644 index 6d518d9..0000000 --- a/src/web/static/js/chat.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 聊天窗口模块入口 - * - * 从 chat/ 子模块重新导出所有公开函数,保持向后兼容。 - * 外部模块通过 import 方式使用,HTML 中的 script 标签直接加载本文件。 - */ - -export { - addChatMessage, - addTypingIndicator, - removeTypingIndicator, - addFileMessage, - setFileProcessing, - setFileDone, - setFileCached, - setFileError, -} from './chat/persistent.js'; - -// 瞬态消息 -export { - showStatus, - clearStatus, -} from './chat/ephemeral.js'; - -// LLM 流式消息 -export { - handleLLMStream, -} from './chat/stream.js'; \ No newline at end of file diff --git a/src/web/static/js/chat/README.md b/src/web/static/js/chat/README.md deleted file mode 100644 index 2d02d14..0000000 --- a/src/web/static/js/chat/README.md +++ /dev/null @@ -1,198 +0,0 @@ -# chat/ 模块说明 - -## 目录 - -- [架构概览](#架构概览) -- [模块职责](#模块职责) -- [消息类型](#消息类型) -- [数据流](#数据流) -- [状态管理](#状态管理) -- [注意事项](#注意事项) - ---- - -## 架构概览 - -``` -chat/ -├── renderer.js # 底层 DOM 渲染工具 -├── persistent.js # 持久消息(永久保留在聊天历史中) -├── ephemeral.js # 瞬态消息(只显示最新一条,新状态覆盖旧状态) -└── stream.js # LLM 流式消息(处理中显示思考过程,结束后固化) - -chat.js # 入口文件,统一 re-export 所有公开函数 -``` - -设计原则:将**持久消息**和**瞬态消息**分离,避免聊天历史被中间状态消息堆积。 - ---- - -## 模块职责 - -### renderer.js - -最底层渲染工具,不维护任何状态。提供: - -- `getChatMessages()` — 获取 `#chat-messages` 容器 -- `scrollToBottom()` — 滚动到底部 -- `createChatBubble(text, type, isUser)` — 创建消息气泡 DOM -- `createSystemBubble(text, type)` — 创建系统气泡 DOM -- `appendMessage(wrapper)` — 将消息追加到容器并滚动 - -### persistent.js - -管理永久保留在聊天历史中的消息: - -- `addChatMessage(text, type, isUser)` — 添加普通聊天消息 -- `addTypingIndicator()` / `removeTypingIndicator()` — 加载三点动画 -- `addFileMessage(filename)` — 添加文件上传消息 -- `setFileProcessing(filename)` — 文件处理中状态 -- `setFileDone(filename, summary)` — 文件处理完成,展开提取摘要 -- `setFileCached(filename)` — 文件使用缓存 -- `setFileError(filename, errorMsg)` — 文件处理错误 - -依赖 `App.fileMessageMap`(定义在 `state.js`)维护文件名到 DOM 元素的映射。 - -### ephemeral.js - -管理瞬态状态消息,同一时刻只显示最新一条: - -- `showStatus(text, type)` — 显示/更新状态消息 -- `clearStatus()` — 清除当前状态消息 - -内部维护 `ephemeralState` 对象记录当前活跃的状态气泡引用。重复调用 `showStatus` 时直接更新已有气泡的文本和样式,不创建新 DOM。 - -### stream.js - -管理 LLM 流式响应的气泡生命周期: - -- `handleLLMStream(msg)` — 根据 SSE 事件阶段分发处理 - -支持的阶段: - -| phase | 说明 | -|-------|------| -| `start` | 创建流式气泡,初始化思考过程区域(默认展开) | -| `reasoning` | 追加思考过程文本 | -| `chunk` | 追加正式回复文本 | -| `end` | 关闭气泡,切换为 `done` 样式,折叠思考过程区域,气泡保留在历史中 | -| `error` | 切换为 `error` 样式,显示错误信息 | - -内部维护 `llmStreamState` 对象记录当前活跃的流式气泡引用。 - ---- - -## 消息类型 - -### 持久消息 - -永久保留在聊天历史中,不会被自动清除: - -- 用户输入的文字 -- 文件上传记录及其处理状态 -- LLM 流式响应的最终结果(`end` 阶段后固化) -- 系统通知(如"信息完整,可以提交") -- 错误消息 - -调用 `addChatMessage()` 或 `addFileMessage()` 创建。 - -### 瞬态消息 - -只显示最新一条,新状态覆盖旧状态: - -- "正在分析文件..." -- "正在校验信息完整性..." -- "请输入登录账号:" -- "配置信息已完整,开始处理发票……" -- "收到补充文件,正在重新分析..." - -调用 `showStatus()` 创建/更新,调用 `clearStatus()` 清除。 - ---- - -## 数据流 - -``` -外部模块 (agent.js / config.js / process.js / upload.js / sync.js) - │ - ├── import { addChatMessage, addFileMessage, ... } from './chat.js' - ├── import { showStatus, clearStatus } from './chat.js' - └── import { handleLLMStream } from './chat.js' - │ - ▼ -chat.js (re-export) - │ - ├── → chat/persistent.js ──→ chat/renderer.js - ├── → chat/ephemeral.js ──→ chat/renderer.js - └── → chat/stream.js ──→ chat/renderer.js -``` - -- 外部模块统一从 `chat.js` 导入函数 -- `chat.js` 只做 re-export,不引入循环依赖 -- 三个子模块通过 `renderer.js` 共享底层 DOM 操作 -- `persistent.js` 额外依赖 `state.js` 的 `App.fileMessageMap` - ---- - -## 状态管理 - -### ephemeralState (ephemeral.js) - -```js -{ - wrapper: HTMLElement | null, // 状态消息的 wrapper 元素 - bubble: HTMLElement | null, // 状态气泡元素 -} -``` - -- 初始为 `null` -- `showStatus` 首次调用时创建并记录引用 -- 后续调用直接更新 `bubble.textContent` 和 `bubble.className` -- `clearStatus` 时移除 DOM 并重置为 `null` - -### llmStreamState (stream.js) - -```js -{ - wrapper: HTMLElement | null, // 流式消息 wrapper - bubble: HTMLElement | null, // 流式气泡 - textContent: HTMLElement | null, // 正式文本容器 - accumulated: string, // 累积的正式文本 - reasoningContent: HTMLElement | null, // 思考过程容器 - reasoningAccumulated: string, // 累积的思考文本 -} -``` - -- `start` 阶段创建并记录引用 -- `chunk` / `reasoning` 阶段追加文本 -- `end` / `error` 阶段重置为 `null`(DOM 保留在历史中) - -### App.fileMessageMap (state.js) - -```js -Map -``` - -- 文件名 → DOM 元素映射 -- `addFileMessage` 创建时写入 -- `setFileProcessing` / `setFileDone` / `setFileCached` / `setFileError` 读取并更新对应文件的状态 - ---- - -## 注意事项 - -1. **不要直接操作 `#chat-messages` 容器**。所有消息创建都通过本模块的 API 进行。 - -2. **瞬态消息和持久消息不要混用**。中间处理状态用 `showStatus`,最终结果用 `addChatMessage`。 - -3. **`showStatus` 不需要手动清除**。调用 `showStatus` 显示新状态时会自动覆盖旧状态;在处理流程结束时,后续的消息或状态会自然覆盖。 - -4. **LLM 流式气泡在 `end` 阶段后变为持久消息**。不需要额外调用 `addChatMessage` 来保留结果。 - -5. **SSE 事件必须包含 `start` 和 `end` 阶段**。缺少 `start` 会导致气泡未创建,缺少 `end` 会导致气泡一直处于处理中状态。详见 `.agents/docs/error-experience/2026-06-13-llm_query_text缺少start-end事件导致前端不显示.md`。 - -6. **`handleLLMStream` 不处理普通日志行**。SSE 的 `message` 事件中,只有 `type === 'llm_stream'` 的事件才会被转发到此模块。 - -7. **文件消息的 DOM 生命周期由 `fileMessageMap` 管理**。文件处理完成后,摘要信息会展开显示在文件气泡下方。 - -8. **所有模块通过 `chat.js` 统一导入**,不要直接从 `chat/` 子目录导入(外部模块层面)。子模块之间的内部导入不受此限制。 \ No newline at end of file diff --git a/src/web/static/js/chat/ephemeral.js b/src/web/static/js/chat/ephemeral.js deleted file mode 100644 index d85a519..0000000 --- a/src/web/static/js/chat/ephemeral.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 瞬态消息管理器 - * - * 瞬态消息只保留最新一条,新状态覆盖旧状态,不会堆积在聊天历史中。 - */ -import { escapeHtml } from '../utils.js'; -import { getChatMessages, scrollToBottom } from './renderer.js'; - -/** - * 当前活跃的瞬态消息 DOM 引用 - */ -let ephemeralState = { - wrapper: null, - bubble: null, -}; - -/** - * 显示瞬态状态消息 - * - * 同一时刻只保留一条状态消息,调用此函数会更新已有消息或创建新消息。 - * - * @param {string} text - 状态文本 - * @param {string} [type='processing'] - 消息类型 - */ -export function showStatus(text, type) { - const chatMessages = getChatMessages(); - if (!chatMessages) return; - - if (ephemeralState.bubble) { - ephemeralState.bubble.textContent = escapeHtml(text); - ephemeralState.bubble.className = 'chat-bubble ' + (type || 'processing'); - scrollToBottom(); - return; - } - - const wrapper = document.createElement('div'); - wrapper.className = 'chat-message'; - wrapper.innerHTML = ` -
-
${escapeHtml(text)}
- `; - chatMessages.appendChild(wrapper); - scrollToBottom(); - - ephemeralState = { - wrapper, - bubble: wrapper.querySelector('.chat-bubble'), - }; -} - -/** - * 清除瞬态状态消息 - */ -export function clearStatus() { - if (ephemeralState.wrapper) { - ephemeralState.wrapper.remove(); - } - ephemeralState = { - wrapper: null, - bubble: null, - }; -} \ No newline at end of file diff --git a/src/web/static/js/chat/persistent.js b/src/web/static/js/chat/persistent.js deleted file mode 100644 index 6362a31..0000000 --- a/src/web/static/js/chat/persistent.js +++ /dev/null @@ -1,246 +0,0 @@ -/** - * 持久消息管理器 - * - * 持久消息永久保留在聊天历史中,包括: - * - 普通聊天消息 - * - 文件上传消息及其状态 - * - 加载指示器 - */ -import { App } from '../state.js'; -import { escapeHtml } from '../utils.js'; -import { createChatBubble, appendMessage, getChatMessages, scrollToBottom } from './renderer.js'; - -// ================================================================ -// 普通聊天消息 -// ================================================================ - -/** - * 添加聊天消息 - * - * @param {string} text - 消息内容 - * @param {string} [type='processing'] - 消息类型 - * @param {boolean} [isUser=false] - 是否为用户消息 - */ -export function addChatMessage(text, type, isUser) { - const wrapper = createChatBubble(text, type, isUser); - appendMessage(wrapper); -} - -// ================================================================ -// 加载指示器 -// ================================================================ - -/** - * 显示加载指示器(三点动画) - */ -export function addTypingIndicator() { - const chatMessages = getChatMessages(); - if (!chatMessages) return; - - const indicator = document.createElement('div'); - indicator.className = 'chat-message'; - indicator.id = 'typing-indicator'; - indicator.innerHTML = ` -
-
-
-
- `; - chatMessages.appendChild(indicator); - scrollToBottom(); -} - -/** - * 移除加载指示器 - */ -export function removeTypingIndicator() { - const indicator = document.getElementById('typing-indicator'); - if (indicator) indicator.remove(); -} - -// ================================================================ -// 文件消息管理 -// ================================================================ - -/** - * 添加文件消息(上传文件时调用) - * - * @param {string} filename - 文件名 - */ -export function addFileMessage(filename) { - const chatMessages = getChatMessages(); - if (!chatMessages) return; - - const wrapper = document.createElement('div'); - wrapper.className = 'chat-message user file-message'; - wrapper.dataset.file = filename; - - const bubble = document.createElement('div'); - bubble.className = 'chat-bubble file-bubble'; - - const nameRow = document.createElement('div'); - nameRow.className = 'file-name-row'; - nameRow.innerHTML = ` - - ${escapeHtml(filename)} - - `; - - const detailRow = document.createElement('div'); - detailRow.className = 'file-detail'; - detailRow.style.display = 'none'; - - bubble.appendChild(nameRow); - bubble.appendChild(detailRow); - wrapper.appendChild(bubble); - chatMessages.appendChild(wrapper); - scrollToBottom(); - - App.fileMessageMap.set(filename, { wrapper, bubble, nameRow, detailRow }); -} - -/** - * 设置文件处理中状态 - * - * @param {string} filename - 文件名 - */ -export function setFileProcessing(filename) { - const entry = App.fileMessageMap.get(filename); - if (!entry) return; - - const { bubble, nameRow, detailRow } = entry; - bubble.classList.remove('file-done', 'file-error'); - bubble.classList.add('file-processing'); - - const statusEl = nameRow.querySelector('.file-status'); - statusEl.style.display = 'inline-flex'; - statusEl.innerHTML = ` - - - - `; - - detailRow.style.display = 'none'; -} - -/** - * 更新文件消息为完成状态 - * - * @param {string} filename - 文件名 - * @param {Object} summary - 提取摘要对象 - */ -export function setFileDone(filename, summary) { - const entry = App.fileMessageMap.get(filename); - if (!entry) return; - - const { bubble, nameRow, detailRow } = entry; - bubble.classList.remove('file-processing'); - bubble.classList.add('file-done'); - - const statusEl = nameRow.querySelector('.file-status'); - statusEl.style.display = 'inline'; - statusEl.innerHTML = 'OK'; - - const labelMap = { - card_date: '刷卡日期', - person_id: '人员编号', - person_name: '人员姓名', - invoice_type_label: '类型', - invoice_number: '发票号码', - invoice_date: '开票日期', - ride_date: '乘车日期', - departure: '出发站', - arrival: '到达站', - seat_class: '座位等级', - train_no: '车次', - total_amount: '金额', - amount: '金额', - seller_name: '销售方', - buyer_name: '购买方', - goods_name: '货物/服务', - remark: '备注', - card_no: '卡号', - card_amount: '刷卡金额', - pay_date: '支付日期', - pay_time: '支付时间', - pay_channel: '支付渠道', - transaction_no: '交易单号', - merchant_name: '商户名称', - order_no: '订单号', - project_name: '项目名称', - purpose: '出差事由', - start_date: '开始日期', - end_date: '结束日期', - person_info: '随行人员', - travel_purpose: '出差事由', - travel_from: '出发地', - travel_to: '目的地', - travel_start: '出发日期', - travel_end: '返回日期', - departure_place: '出发地', - arrival_place: '目的地', - hotel_name: '酒店名称', - checkin_date: '入住日期', - checkout_date: '退房日期', - room_type: '房型', - days: '天数', - attachments: '附件', - subsidy_list: '补助清单', - }; - - let lines = []; - for (const [key, value] of Object.entries(summary)) { - const label = labelMap[key] || key; - const displayValue = escapeHtml(String(value)).replace(/\n/g, '
'); - lines.push(`${label}:${displayValue}`); - } - - detailRow.innerHTML = lines.join('
'); - detailRow.style.display = 'block'; - entry.wrapper.scrollIntoView({ behavior: 'smooth', block: 'center' }); -} - -/** - * 更新文件消息为缓存状态 - * - * @param {string} filename - 文件名 - */ -export function setFileCached(filename) { - const entry = App.fileMessageMap.get(filename); - if (!entry) return; - - const { bubble, nameRow, detailRow } = entry; - bubble.classList.remove('file-processing'); - bubble.classList.add('file-done'); - - const statusEl = nameRow.querySelector('.file-status'); - statusEl.style.display = 'inline'; - statusEl.innerHTML = 'CACHE'; - - detailRow.innerHTML = '状态:使用缓存(已提取)'; - detailRow.style.display = 'block'; - entry.wrapper.scrollIntoView({ behavior: 'smooth', block: 'center' }); -} - -/** - * 更新文件消息为错误状态 - * - * @param {string} filename - 文件名 - * @param {string} [errorMsg] - 错误信息 - */ -export function setFileError(filename, errorMsg) { - const entry = App.fileMessageMap.get(filename); - if (!entry) return; - - const { bubble, nameRow, detailRow } = entry; - bubble.classList.remove('file-processing'); - bubble.classList.add('file-error'); - - const statusEl = nameRow.querySelector('.file-status'); - statusEl.style.display = 'inline'; - statusEl.innerHTML = 'ERR'; - - detailRow.innerHTML = `错误:${escapeHtml(errorMsg || '提取失败')}`; - detailRow.style.display = 'block'; - entry.wrapper.scrollIntoView({ behavior: 'smooth', block: 'center' }); -} \ No newline at end of file diff --git a/src/web/static/js/chat/renderer.js b/src/web/static/js/chat/renderer.js deleted file mode 100644 index 6c65eef..0000000 --- a/src/web/static/js/chat/renderer.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 聊天消息渲染器 - * - * 提供通用的气泡创建、DOM 操作和滚动功能。 - */ -import { escapeHtml } from '../utils.js'; - -/** - * 获取聊天消息容器 - */ -export function getChatMessages() { - return document.getElementById('chat-messages'); -} - -/** - * 滚动聊天容器到底部 - */ -export function scrollToBottom() { - const chatMessages = getChatMessages(); - if (chatMessages) { - chatMessages.scrollTop = chatMessages.scrollHeight; - } -} - -/** - * 创建聊天消息气泡 - * - * @param {string} text - 消息内容 - * @param {string} type - 消息类型(影响气泡样式) - * @param {boolean} isUser - 是否为用户消息 - * @returns {HTMLElement} wrapper 元素 - */ -export function createChatBubble(text, type, isUser) { - const wrapper = document.createElement('div'); - wrapper.className = `chat-message${isUser ? ' user' : ''}`; - wrapper.innerHTML = ` -
-
${escapeHtml(text)}
- `; - return wrapper; -} - -/** - * 创建带系统头像的气泡 - * - * @param {string} text - 消息内容 - * @param {string} type - 消息类型 - * @returns {HTMLElement} wrapper 元素 - */ -export function createSystemBubble(text, type) { - return createChatBubble(text, type, false); -} - -/** - * 将消息追加到聊天容器 - * - * @param {HTMLElement} wrapper - 消息 wrapper 元素 - */ -export function appendMessage(wrapper) { - const chatMessages = getChatMessages(); - if (!chatMessages) return; - chatMessages.appendChild(wrapper); - scrollToBottom(); -} \ No newline at end of file diff --git a/src/web/static/js/chat/stream.js b/src/web/static/js/chat/stream.js deleted file mode 100644 index 1e2e441..0000000 --- a/src/web/static/js/chat/stream.js +++ /dev/null @@ -1,445 +0,0 @@ -/** - * LLM 流式聊天气泡管理器 - * - * 流式消息在处理时显示思考过程和逐字输出,结束后固化为持久消息。 - */ -import { escapeHtml } from '../utils.js'; -import { getChatMessages, scrollToBottom } from './renderer.js'; - -/** - * 判断用户是否在聊天底部附近(阈值 150px),用于流式传输时决定是否自动滚动 - */ -function _isNearBottom() { - const el = getChatMessages(); - if (!el) return true; - return el.scrollHeight - el.scrollTop - el.clientHeight < 150; -} - -/** - * 当前活跃的 LLM 流式气泡状态 - */ -let llmStreamState = { - wrapper: null, - bubble: null, - textContent: null, - accumulated: '', - reasoningContent: null, - reasoningAccumulated: '', -}; - -/** - * 处理 LLM 流式事件 - * - * @param {Object} msg - SSE 传来的 llm_stream 事件对象 - */ -export function handleLLMStream(msg) { - switch (msg.phase) { - case 'start': - _createLLMStreamBubble(msg.label || '正在分析中...'); - break; - case 'reasoning': - _appendLLMStreamReasoning(msg.text || ''); - break; - case 'chunk': - _appendLLMStreamChunk(msg.text || ''); - break; - case 'end': - _closeLLMStreamBubble(msg.label || '分析完成'); - break; - case 'error': - _errorLLMStreamBubble(msg.error || '分析失败'); - break; - } -} - -/** - * 创建 LLM 流式气泡(start 阶段) - */ -function _createLLMStreamBubble(label) { - const chatMessages = getChatMessages(); - if (!chatMessages) return; - - if (llmStreamState.wrapper) { - _closeLLMStreamBubbleSilent(); - } - - const wrapper = document.createElement('div'); - wrapper.className = 'chat-message'; - - const bubble = document.createElement('div'); - bubble.className = 'chat-bubble processing llm-stream-bubble'; - - const labelEl = document.createElement('div'); - labelEl.className = 'llm-stream-label'; - labelEl.textContent = label; - - const reasoningSection = document.createElement('details'); - reasoningSection.className = 'llm-reasoning-section'; - reasoningSection.open = true; - - const reasoningSummary = document.createElement('summary'); - reasoningSummary.className = 'llm-reasoning-summary'; - reasoningSummary.textContent = '思考过程(点击折叠)'; - - const reasoningText = document.createElement('div'); - reasoningText.className = 'llm-reasoning-text'; - reasoningText.textContent = ''; - - reasoningSection.appendChild(reasoningSummary); - reasoningSection.appendChild(reasoningText); - - const textEl = document.createElement('div'); - textEl.className = 'llm-stream-text'; - textEl.textContent = ''; - - bubble.appendChild(labelEl); - bubble.appendChild(reasoningSection); - bubble.appendChild(textEl); - wrapper.appendChild(bubble); - chatMessages.appendChild(wrapper); - scrollToBottom(); - - llmStreamState = { - wrapper, - bubble, - textContent: textEl, - accumulated: '', - reasoningContent: reasoningText, - reasoningAccumulated: '', - }; -} - -/** - * 追加流式文本片段(chunk 阶段) - */ -function _appendLLMStreamChunk(text) { - if (!llmStreamState.textContent) return; - - llmStreamState.accumulated += text; - llmStreamState.textContent.textContent = llmStreamState.accumulated; - if (_isNearBottom()) { - scrollToBottom(); - } -} - -/** - * 追加思考过程片段(reasoning 阶段) - */ -function _appendLLMStreamReasoning(text) { - if (!llmStreamState.reasoningContent) return; - - llmStreamState.reasoningAccumulated += text; - llmStreamState.reasoningContent.textContent = llmStreamState.reasoningAccumulated; - if (_isNearBottom()) { - scrollToBottom(); - } -} - -/** - * 关闭流式气泡(end 阶段)— 气泡保留在聊天历史中作为持久消息。 - * 如果累积内容是提取结果 JSON,则渲染为美观的摘要卡片。 - */ -function _closeLLMStreamBubble(label) { - if (!llmStreamState.bubble) return; - - const accumulated = llmStreamState.accumulated; - - llmStreamState.bubble.classList.remove('processing'); - llmStreamState.bubble.classList.add('done'); - - const labelEl = llmStreamState.bubble.querySelector('.llm-stream-label'); - if (labelEl) { - labelEl.textContent = label; - } - - const reasoningSection = llmStreamState.bubble.querySelector('.llm-reasoning-section'); - if (reasoningSection) { - reasoningSection.open = false; - } - - // 尝试将累积文本解析为提取结果 JSON,渲染为摘要卡片 - const textEl = llmStreamState.bubble.querySelector('.llm-stream-text'); - if (textEl && accumulated) { - const parsed = _tryParseExtractionJson(accumulated); - if (parsed) { - textEl.innerHTML = ''; - textEl.appendChild(_buildExtractionSummary(parsed)); - } - } - - scrollToBottom(); - - llmStreamState = { - wrapper: null, - bubble: null, - textContent: null, - accumulated: '', - reasoningContent: null, - reasoningAccumulated: '', - }; -} - -/** - * 静默关闭流式气泡(不改变样式,用于 start 前清理) - */ -function _closeLLMStreamBubbleSilent() { - if (llmStreamState.wrapper) { - llmStreamState.wrapper.remove(); - } - llmStreamState = { - wrapper: null, - bubble: null, - textContent: null, - accumulated: '', - reasoningContent: null, - reasoningAccumulated: '', - }; -} - -/** - * 错误状态(error 阶段) - */ -function _errorLLMStreamBubble(errorMsg) { - if (!llmStreamState.bubble) return; - - llmStreamState.bubble.classList.remove('processing'); - llmStreamState.bubble.classList.add('error'); - - const labelEl = llmStreamState.bubble.querySelector('.llm-stream-label'); - if (labelEl) { - labelEl.textContent = '分析失败'; - } - - if (llmStreamState.textContent) { - llmStreamState.textContent.textContent = escapeHtml(errorMsg); - } - - llmStreamState = { - wrapper: null, - bubble: null, - textContent: null, - accumulated: '', - reasoningContent: null, - reasoningAccumulated: '', - }; -} - -/** - * 尝试将 LLM 输出文本解析为提取结果 JSON - * 返回解析后的对象,或 null(非提取结果 JSON) - */ -function _tryParseExtractionJson(text) { - try { - const trimmed = text.trim(); - if (!trimmed.startsWith('{')) return null; - const parsed = JSON.parse(trimmed); - if (parsed && typeof parsed === 'object' && (parsed.basic_info || parsed.reimbursement_details)) { - return parsed; - } - return null; - } catch { - return null; - } -} - -/** - * 将提取结果 JSON 渲染为美观的摘要卡片 DOM 元素 - * - * @param {Object} data - 提取结果 JSON - * @returns {HTMLElement} - */ -function _buildExtractionSummary(data) { - const container = document.createElement('div'); - container.className = 'extraction-summary'; - - // 全局状态 - const statusSection = _summarySection('状态'); - const submitOk = data.can_submit; - _summaryRow(statusSection, '可提交', submitOk ? '是' : '否'); - if (data.suggestion) { - _summaryRow(statusSection, '建议', data.suggestion); - } - container.appendChild(statusSection); - - // 判断类型:差旅 vs 普通 - const isTravel = !!(data.basic_info?.travel_purpose || data.reimbursement_details?.transport_fee); - - // 基本信息 - if (data.basic_info) { - const bi = data.basic_info; - const section = _summarySection('基本信息'); - if (isTravel) { - _summaryRow(section, '出差事由', bi.travel_purpose || ''); - _summaryRow(section, '出差地点', bi.travel_location || ''); - const dates = bi.start_date && bi.end_date ? `${bi.start_date} 至 ${bi.end_date}` : (bi.start_date || bi.end_date || ''); - _summaryRow(section, '出差日期', dates); - } else { - _summaryRow(section, '报销说明', bi.reimbursement_description || ''); - } - container.appendChild(section); - } - - // 报销汇总(普通模式) - if (!isTravel && data.reimbursement_details) { - const rd = data.reimbursement_details; - const section = _summarySection('报销汇总'); - _summaryRow(section, '发票总份数', rd.total_invoices != null ? String(rd.total_invoices) : ''); - _summaryRow(section, '总金额', rd.total_amount != null ? `¥${rd.total_amount.toFixed(2)}` : ''); - container.appendChild(section); - } - - // 交通费(差旅模式) - if (isTravel && data.reimbursement_details?.transport_fee?.length) { - const section = _summarySection('交通费'); - const table = _summaryTable(['日期', '出发', '到达', '金额', '备注']); - for (const item of data.reimbursement_details.transport_fee) { - _summaryTableRow(table, [ - item.start_date || '', - item.departure_place || '', - item.arrival_place || '', - item.amount != null ? `¥${item.amount.toFixed(2)}` : '', - item.remark || '', - ]); - } - section.appendChild(table); - container.appendChild(section); - } - - // 住宿费(差旅模式) - if (isTravel && data.reimbursement_details?.hotel_fee?.length) { - const section = _summarySection('住宿费'); - const table = _summaryTable(['入住日期', '离店日期', '天数', '发票金额', '报销金额', '备注']); - for (const item of data.reimbursement_details.hotel_fee) { - _summaryTableRow(table, [ - item.checkin_date || '', - item.checkout_date || '', - item.days != null ? String(item.days) : '', - item.invoice_amount != null ? `¥${item.invoice_amount.toFixed(2)}` : '', - item.reimburse_amount != null ? `¥${item.reimburse_amount.toFixed(2)}` : '', - item.remark || '', - ]); - } - section.appendChild(table); - container.appendChild(section); - } - - // 会议/培训费(差旅模式) - if (isTravel && data.reimbursement_details?.conference_fee?.length) { - const section = _summarySection('会议/培训费'); - const table = _summaryTable(['日期', '金额', '备注']); - for (const item of data.reimbursement_details.conference_fee) { - _summaryTableRow(table, [ - item.start_date || '', - item.amount != null ? `¥${item.amount.toFixed(2)}` : '', - item.remark || '', - ]); - } - section.appendChild(table); - container.appendChild(section); - } - - // 支付记录 - if (data.payment_methods?.length) { - const section = _summarySection('支付记录'); - const table = _summaryTable(['日期', '金额', '商户', '备注']); - for (const item of data.payment_methods) { - _summaryTableRow(table, [ - item.card_date || '', - item.card_amount != null ? `¥${item.card_amount.toFixed(2)}` : '', - item.merchant || '', - item.remark || '', - ]); - } - section.appendChild(table); - container.appendChild(section); - } - - // 补助(差旅模式) - if (isTravel && data.subsidy_list?.length) { - const section = _summarySection('补助'); - const table = _summaryTable(['姓名', '开始日期', '结束日期', '天数']); - for (const item of data.subsidy_list) { - _summaryTableRow(table, [ - item.person_name || '', - item.start_date || '', - item.end_date || '', - item.days != null ? String(item.days) : '', - ]); - } - section.appendChild(table); - container.appendChild(section); - } - - // 附件 - if (data.attachments?.length) { - const section = _summarySection('附件'); - const list = document.createElement('ul'); - list.className = 'summary-attachment-list'; - for (const item of data.attachments) { - const li = document.createElement('li'); - li.className = `summary-attachment-item summary-attachment-${item.attachment_type || 'other'}`; - li.textContent = `${item.attachment_desc || item.filename || ''}`; - list.appendChild(li); - } - section.appendChild(list); - container.appendChild(section); - } - - return container; -} - -/* ---- 提取摘要卡片构建辅助函数 ---- */ - -function _summarySection(title) { - const section = document.createElement('div'); - section.className = 'summary-section'; - const titleEl = document.createElement('div'); - titleEl.className = 'summary-section-title'; - titleEl.textContent = title; - section.appendChild(titleEl); - return section; -} - -function _summaryRow(section, label, value) { - if (!value) return; - const row = document.createElement('div'); - row.className = 'summary-row'; - const labelEl = document.createElement('span'); - labelEl.className = 'summary-label'; - labelEl.textContent = label; - const valueEl = document.createElement('span'); - valueEl.className = 'summary-value'; - valueEl.textContent = value; - row.appendChild(labelEl); - row.appendChild(valueEl); - section.appendChild(row); -} - -function _summaryTable(headers) { - const table = document.createElement('table'); - table.className = 'summary-table'; - const thead = document.createElement('thead'); - const tr = document.createElement('tr'); - for (const h of headers) { - const th = document.createElement('th'); - th.textContent = h; - tr.appendChild(th); - } - thead.appendChild(tr); - table.appendChild(thead); - const tbody = document.createElement('tbody'); - table.appendChild(tbody); - return table; -} - -function _summaryTableRow(table, cells) { - const tbody = table.querySelector('tbody'); - if (!tbody) return; - const tr = document.createElement('tr'); - for (const cell of cells) { - const td = document.createElement('td'); - td.textContent = cell; - tr.appendChild(td); - } - tbody.appendChild(tr); -} \ No newline at end of file diff --git a/src/web/static/js/config.js b/src/web/static/js/config.js deleted file mode 100644 index 4c7f45f..0000000 --- a/src/web/static/js/config.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 配置收集模块 - */ -import { App } from './state.js'; -import { addChatMessage, showStatus } from './chat.js'; -import { handleUserSupplement, handleForceSubmit, handleSupplementUpload } from './agent.js'; -import { startProcess } from './process.js'; - -export const CONFIG_FIELDS = { - username: '登录账号', - password: '登录密码', - default_name: '报销人姓名', - default_card_no: '公务卡号', - default_person_id: '报销人工号', - consumable_storage: '易耗品存放地点', -}; - -/** - * 静默检查:仅当配置完整且有文件时自动开始处理。 - * 注意:上传文件后不再自动触发,需要用户发送"可以开始"等指令。 - */ -export function checkAutoStart() { - if (App.isProcessing) return; - if (App.processState === 'processing') return; - - // done 状态下有新文件时,走 supplement 流程(手动触发) - if (App.processState === 'done' && App.forceStart) { - const hasNewFiles = App.allFiles.length > App.lastProcessedFileCount; - if (!hasNewFiles) return; - App.forceStart = false; - App.isProcessing = true; - App.processState = 'processing'; - if (App.newFilenames.length > 0) { - const filenames = App.newFilenames; - App.newFilenames = []; - handleSupplementUpload(filenames); - } - return; - } - - // awaiting_supplement 状态下不自动处理,等待用户补充文件后手动触发 - if (App.processState === 'awaiting_supplement') { - return; - } - - // idle 状态下有补充文件时,走 supplement 流程(手动触发) - if (App.processState === 'idle' && App.newFilenames.length > 0) { - App.isProcessing = true; - App.processState = 'processing'; - const filenames = App.newFilenames; - App.newFilenames = []; - handleSupplementUpload(filenames); - return; - } - - // 不再自动开始处理,需要用户发送"可以开始"等指令 -} - -/** - * 配置提示:在所有文件加载完成后调用,逐个提示缺失的配置项。 - */ -export function promptMissingConfig() { - if (App.isProcessing) return; - - const allKeys = Object.keys(CONFIG_FIELDS); - const missing = allKeys.filter(k => !App.sessionConfig[k]); - - if (missing.length) { - if (App.pendingConfigKey === null) { - App.pendingConfigKey = missing[0]; - addChatMessage(`请输入${CONFIG_FIELDS[App.pendingConfigKey]}:`, 'system'); - } else if (!missing.includes(App.pendingConfigKey)) { - App.pendingConfigKey = missing[0]; - addChatMessage(`请输入${CONFIG_FIELDS[App.pendingConfigKey]}:`, 'system'); - } - return; - } - - App.pendingConfigKey = null; - - if (App.allFiles.length) { - showStatus('配置信息已完整,请发送"可以开始"或"处理发票"来启动处理流程'); - } -} - -/** - * 用户输入处理入口 - */ -export async function sendUserMessage() { - const input = document.getElementById('chat-input'); - if (!input) return; - const text = input.value.trim(); - if (!text) return; - - // 优先处理配置收集阶段 - if (App.pendingConfigKey) { - App.sessionConfig[App.pendingConfigKey] = text; - addChatMessage(text, 'user', true); - addChatMessage(`${CONFIG_FIELDS[App.pendingConfigKey]} 已设置`, 'system'); - input.value = ''; - promptMissingConfig(); - return; - } - - addChatMessage(text, 'user', true); - input.value = ''; - - // 检查是否是开始处理指令(如"可以开始"、"处理发票"等) - if (text.includes('可以开始') || text.includes('开始处理') || text.includes('处理发票') || text.includes('开始')) { - startProcess(); - return; - } - - // 检查是否是强制提交指令 - if (text.includes('直接提交') || text.includes('强制提交') || text.includes('继续提交')) { - await handleForceSubmit(); - return; - } - - // 检查是否包含文件上传指令 - if (text.includes('上传') || text.includes('补充')) { - document.getElementById('file-input').click(); - return; - } - - // awaiting_supplement 状态下,将用户输入发给后端 LLM 处理 - if (App.processState === 'awaiting_supplement') { - await handleUserSupplement(text); - return; - } - - showStatus('请发送"可以开始"或"处理发票"来启动处理流程'); -} - -export function handleChatKey(event) { - if (event.key === 'Enter') { - sendUserMessage(); - } -} \ No newline at end of file diff --git a/src/web/static/js/index.js b/src/web/static/js/index.js deleted file mode 100644 index a4fc724..0000000 --- a/src/web/static/js/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 入口文件 — ES Module 入口 - * - * 所有模块通过 import 加载,依赖顺序由 import 自动解决。 - * 需要 HTML 内联事件调用的函数挂载到 window。 - */ -import { initDragDrop, toggleUploadPanel, handleFiles } from './upload.js'; -import { sendUserMessage, handleChatKey } from './config.js'; -import { generateQr, startSync } from './sync.js'; -import { handleForceSubmit } from './agent.js'; - -// 将需要 HTML 内联事件调用的函数挂载到 window -window.handleChatKey = handleChatKey; -window.sendUserMessage = sendUserMessage; -window.initDragDrop = initDragDrop; -window.toggleUploadPanel = toggleUploadPanel; -window.handleFiles = handleFiles; -window.handleForceSubmit = handleForceSubmit; - -initDragDrop(); -generateQr(); -startSync(); \ No newline at end of file diff --git a/src/web/static/js/process.js b/src/web/static/js/process.js deleted file mode 100644 index c6cbc9e..0000000 --- a/src/web/static/js/process.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 发票处理模块 - */ -import { App } from './state.js'; -import { ensureSession } from './utils.js'; -import { - addTypingIndicator, - removeTypingIndicator, - addChatMessage, -} from './chat.js'; -import { createSSEConnection, handleDoneResult } from './sse.js'; - -export async function startProcess() { - if (!App.allFiles.length) { - alert('请先上传文件或图片'); - return; - } - - App.isProcessing = true; - App.processState = 'processing'; - - addTypingIndicator(); - - try { - await ensureSession(); - - for (const f of App.allFiles) { - // 跳过服务器同步的文件(已在服务器上) - if (f.__source === 'server') continue; - const fd = new FormData(); - fd.append('file', f); - await fetch(`/api/upload/${App.sessionId}`, { method: 'POST', body: fd }); - } - - removeTypingIndicator(); - addTypingIndicator(); - - const cfg = { - username: App.sessionConfig.username || '', - password: App.sessionConfig.password || '', - default_name: App.sessionConfig.default_name || '', - default_card_no: App.sessionConfig.default_card_no || '', - default_person_id: App.sessionConfig.default_person_id || '', - consumable_storage: App.sessionConfig.consumable_storage || '', - }; - - await fetch(`/api/agent/process/${App.sessionId}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(cfg), - }); - - const es = createSSEConnection(App.sessionId, { - onDone: (result) => { - removeTypingIndicator(); - handleDoneResult(result); - }, - }); - App.agentEventSource = es; - - } catch (e) { - removeTypingIndicator(); - alert('请求失败: ' + (e.message || '未知错误')); - App.isProcessing = false; - App.processState = 'idle'; - addChatMessage(`请求失败:${e.message || '未知错误'}`, 'error'); - } -} \ No newline at end of file diff --git a/src/web/static/js/sse.js b/src/web/static/js/sse.js deleted file mode 100644 index 7c145e2..0000000 --- a/src/web/static/js/sse.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * SSE 连接管理 - * - * 封装 EventSource 的创建、事件注册和 done 事件处理,避免重复代码。 - */ -import { App } from './state.js'; -import { - removeTypingIndicator, - setFileProcessing, - setFileDone, - setFileCached, - setFileError, - handleLLMStream, - addChatMessage, - showStatus, -} from './chat.js'; -import { handleAgentEvent } from './agent.js'; - -/** - * 创建 SSE 连接并返回 EventSource 实例 - * - * @param {string} sessionId - 会话 ID - * @param {Object} options - 选项配置 - * @param {Function} options.onDone - done 事件处理器 - * @param {boolean} options.handleFileProgress - 是否处理文件进度事件 (默认 true) - * @param {boolean} options.handleLLMStream - 是否处理 LLM 流式事件 (默认 true) - * @param {boolean} options.handleAgentEvents - 是否处理 Agent 事件 (默认 true) - * @returns {EventSource} - */ -export function createSSEConnection(sessionId, options = {}) { - const { - onDone, - handleFileProgress = true, - handleLLMStream: handleStream = true, - handleAgentEvents = true, - } = options; - - const es = new EventSource(`/api/logs/${sessionId}`); - - es.addEventListener('message', e => { - try { - const msg = JSON.parse(e.data); - - // 文件进度事件 - if (handleFileProgress && msg.type === 'file_progress') { - const filename = msg.file; - switch (msg.status) { - case 'processing': - setFileProcessing(filename); - break; - case 'done': - setFileDone(filename, msg.summary || {}); - break; - case 'cached': - setFileCached(filename); - break; - case 'error': - setFileError(filename, msg.error); - break; - } - return; - } - - // LLM 流式事件 - if (handleStream && msg.type === 'llm_stream') { - handleLLMStream(msg); - return; - } - - // Agent 事件 - if (handleAgentEvents && msg.type && msg.type.startsWith('agent_')) { - handleAgentEvent(msg); - return; - } - - // 完成事件 - if (msg.type === 'done') { - es.close(); - if (onDone) { - onDone(msg.result); - } - return; - } - } catch (err) { - // 普通日志行,忽略 - } - }); - - es.onerror = () => { - es.close(); - removeTypingIndicator(); - App.isProcessing = false; - App.processState = 'done'; - addChatMessage('连接中断,请刷新页面重试', 'error'); - }; - - return es; -} - -/** - * 处理 done 事件的通用逻辑 (根据 result 内容更新 UI 状态) - * - * @param {Object} result - done 事件的结果对象 - */ -export function handleDoneResult(result) { - App.isProcessing = false; - - if (App.forceSubmitting) { - App.forceSubmitting = false; - App.lastProcessedFileCount = App.allFiles.length; - if (result.ok) { - addChatMessage('提交完成!', 'done'); - } else { - addChatMessage(`提交失败:${result.error || '未知错误'}`, 'error'); - } - return; - } - - if (result.waiting_for_supplement) { - App.processState = 'awaiting_supplement'; - showStatus('信息不完整,请补充上传相关材料'); - addChatMessage( - '您可通过上传区补充文件,或直接输入文字说明补充信息,或输入"直接提交"跳过校验', - 'system' - ); - } else { - App.processState = 'done'; - App.lastProcessedFileCount = App.allFiles.length; - - if (result.ok) { - if (result.agent_ready) { - if (result.submit_ok !== false) { - addChatMessage('信息完整,已自动提交到财务系统', 'done'); - } else { - addChatMessage(`校验通过但提交失败:${result.submit_error || '未知错误'}`, 'error'); - } - } else if (result.invoice_count) { - addChatMessage(`处理完成!共提取 ${result.invoice_count} 张发票数据`, 'done'); - } - } else { - addChatMessage(`处理失败:${result.error || '未知错误'}`, 'error'); - } - } -} \ No newline at end of file diff --git a/src/web/static/js/state.js b/src/web/static/js/state.js deleted file mode 100644 index 1e1c4a1..0000000 --- a/src/web/static/js/state.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 全局状态管理 - * - * 所有模块通过 App 对象共享状态,避免全局变量污染。 - */ -export const App = { - sessionId: null, - allFiles: [], - sessionConfig: {}, - invoiceData: [], - csvFilename: '', - lastDownloadUrls: {}, - isProcessing: false, - pendingConfigKey: null, - fieldsCache: [], - syncTimer: null, - fileMessageMap: new Map(), - processState: 'idle', - newFilenames: [], - lastProcessedFileCount: 0, - forceStart: false, - agentEventSource: null, - forceSubmitting: false, -}; \ No newline at end of file diff --git a/src/web/static/js/sync.js b/src/web/static/js/sync.js deleted file mode 100644 index 6f2bbc1..0000000 --- a/src/web/static/js/sync.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * 移动端同步模块 - */ -import { App } from './state.js'; -import { ensureSession } from './utils.js'; -import { addFileMessage } from './chat.js'; -import { checkAutoStart, promptMissingConfig } from './config.js'; - -export async function generateQr() { - await ensureSession(); - const mobileUrl = window.location.origin + '/mobile/' + App.sessionId; - const box = document.getElementById('qrcode'); - box.innerHTML = ''; - new QRCode(box, { - text: mobileUrl, - width: 120, - height: 120, - colorDark: '#333', - colorLight: '#fff', - }); -} - -export async function startSync() { - if (App.syncTimer) return; - await syncFiles(); - App.syncTimer = setInterval(syncFiles, 3000); -} - -export function stopSync() { - if (App.syncTimer) { clearInterval(App.syncTimer); App.syncTimer = null; } -} - -async function syncFiles() { - if (!App.sessionId) return; - try { - const r = await fetch(`/api/files/${App.sessionId}`); - const d = await r.json(); - const serverFiles = d.files || []; - const serverNames = new Set(serverFiles.map(f => f.name)); - const localNames = new Set(App.allFiles.map(f => f.name)); - const addedFiles = []; - - // 只同步文件名元数据,不下载文件内容(文件已在服务器上) - for (const serverFile of serverFiles) { - if (!localNames.has(serverFile.name)) { - const file = { name: serverFile.name, __source: 'server' }; - App.allFiles.push(file); - addedFiles.push(serverFile.name); - } - } - - for (const f of [...App.allFiles]) { - if (!serverNames.has(f.name) && f.__source !== 'local') { - const idx = App.allFiles.indexOf(f); - if (idx > -1) App.allFiles.splice(idx, 1); - } - } - - if (addedFiles.length) { - addedFiles.forEach(name => addFileMessage(name)); - if (App.processState === 'done') { - App.processState = 'idle'; - App.newFilenames = addedFiles; - } else if (App.processState === 'awaiting_supplement') { - App.processState = 'idle'; - App.newFilenames = addedFiles; - } - } - - if (App.allFiles.length) { - document.getElementById('file-zone').classList.add('active'); - } else { - document.getElementById('file-zone').classList.remove('active'); - } - } catch (e) { /* ignore */ } -} \ No newline at end of file diff --git a/src/web/static/js/upload.js b/src/web/static/js/upload.js deleted file mode 100644 index 8a8135f..0000000 --- a/src/web/static/js/upload.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * 文件上传模块 - */ -import { App } from './state.js'; -import { addFileMessage, addChatMessage } from './chat.js'; -import { promptMissingConfig, checkAutoStart } from './config.js'; -import { handleSupplementUpload, hideAgentRequest } from './agent.js'; -import { ensureSession } from './utils.js'; - -export async function handleFiles(input) { - const files = Array.from(input.files); - const result = await _processFiles(files); - - if (result.hasNewFiles && App.processState === 'done') { - App.processState = 'idle'; - App.newFilenames = result.newFilenames; - } - if (result.hasNewFiles && App.processState === 'awaiting_supplement') { - hideAgentRequest(); - await _uploadNewFiles(result.newFilenames); - App.newFilenames = result.newFilenames; - App.processState = 'idle'; - return; - } - - promptMissingConfig(); - input.value = ''; -} - -/** - * 统一文件处理逻辑(handleFiles 和拖拽共享) - */ -async function _processFiles(files) { - const validExts = /\.(pdf|png|jpe?g|bmp|webp)$/i; - let hasNewFiles = false; - const newFilenames = []; - - for (const f of files) { - if (/\.json$/i.test(f.name)) { - await parseConfigFile(f); - continue; - } - if (!App.allFiles.find(x => x.name === f.name) && validExts.test(f.name)) { - f.__source = 'local'; - App.allFiles.push(f); - document.getElementById('file-zone').classList.add('active'); - addFileMessage(f.name); - hasNewFiles = true; - newFilenames.push(f.name); - } - } - - return { hasNewFiles, newFilenames }; -} - -async function _uploadNewFiles(filenames) { - await ensureSession(); - for (const f of App.allFiles) { - if (filenames.includes(f.name)) { - const fd = new FormData(); - fd.append('file', f); - await fetch(`/api/upload/${App.sessionId}`, { method: 'POST', body: fd }); - } - } -} - -async function parseConfigFile(file) { - return new Promise((resolve) => { - const reader = new FileReader(); - reader.onload = function (e) { - try { - const cfg = JSON.parse(e.target.result); - Object.assign(App.sessionConfig, { - username: cfg.username || '', - password: cfg.password || '', - default_name: cfg.default_name || '', - default_card_no: cfg.default_card_no || '', - default_person_id: cfg.default_person_id || '', - consumable_storage: cfg.consumable_storage || '', - }); - addChatMessage(`config.json 已加载`, 'system'); - resolve(true); - } catch (err) { - console.warn('config.json 解析失败:', err.message); - resolve(false); - } - }; - reader.readAsText(file); - }); -} - -export function initDragDrop() { - const zone = document.getElementById('file-zone'); - if (!zone) return; - - zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); }); - zone.addEventListener('dragleave', () => zone.classList.remove('dragover')); - zone.addEventListener('drop', async e => { - e.preventDefault(); - zone.classList.remove('dragover'); - const droppedFiles = Array.from(e.dataTransfer.files); - const result = await _processFiles(droppedFiles); - - if (result.hasNewFiles && App.processState === 'done') { - App.processState = 'idle'; - } - - // 在 awaiting_supplement 状态下,只上传新文件并走 supplement 流程(手动触发) - if (result.hasNewFiles && App.processState === 'awaiting_supplement') { - hideAgentRequest(); - await _uploadNewFiles(result.newFilenames); - App.newFilenames = result.newFilenames; - App.processState = 'idle'; - return; - } - - promptMissingConfig(); - }); -} - -/** - * 切换上传面板折叠/展开状态 - */ -export function toggleUploadPanel() { - const body = document.getElementById('upload-panel-body'); - const icon = document.getElementById('upload-toggle-icon'); - if (!body || !icon) return; - - body.classList.toggle('collapsed'); - icon.classList.toggle('collapsed'); -} \ No newline at end of file diff --git a/src/web/static/js/utils.js b/src/web/static/js/utils.js deleted file mode 100644 index 27b1c6e..0000000 --- a/src/web/static/js/utils.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * 工具函数模块 - * - * 提供 HTML 转义和会话管理等基础功能。 - */ -import { App } from './state.js'; - -export function escapeHtml(s) { - return s.replace(/\&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - -export async function ensureSession() { - if (App.sessionId) return App.sessionId; - const r = await fetch('/api/session', { method: 'POST' }); - const d = await r.json(); - App.sessionId = d.session_id; - return App.sessionId; -} \ No newline at end of file diff --git a/src/web/templates/README.md b/src/web/templates/README.md deleted file mode 100644 index 5c90a81..0000000 --- a/src/web/templates/README.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -last_reviewed: 2026-06-11 ---- - -# src/web/templates — HTML 模板目录 - -存放 Flask 渲染的 HTML 模板文件。 - -## 模板清单 - -| 文件 | 说明 | -|------|------| -| `index.html` | PC 端主界面:包含文件上传区、配置表单、处理按钮、SSE 日志面板、可编辑发票表格、下载链接、财务提交按钮、移动端二维码 | -| `mobile_upload.html` | 移动端上传页面:支持拍照/相册选择,上传至当前会话 | \ No newline at end of file diff --git a/src/web/templates/index.html b/src/web/templates/index.html deleted file mode 100644 index 859a93d..0000000 --- a/src/web/templates/index.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - -财务报销自动化 - - - - - - -
- -
-
-
-
-
你好!我是财务报销助手。请先上传 config.json 配置文件和发票文件,然后发送"可以开始"来启动处理流程。
-
-
-
- - -
-
- - -
-
- 文件上传 - ▼ -
-
-
-
-
-
-
点击或拖拽上传文件(支持 PDF、图片)
-
- -
-
-
手机扫码上传
-
-

扫码即可拍照上传

-
-
-
-
- -
- - - - - \ No newline at end of file diff --git a/src/web/templates/mobile_upload.html b/src/web/templates/mobile_upload.html deleted file mode 100644 index 109049c..0000000 --- a/src/web/templates/mobile_upload.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - -支付截图上传 - - - - - -{% if error %} -
错误

{{ error }}

-{% else %} - -
-
支付截图上传
-

拍照或从相册选择图片

-
- -
- - - - - - - -
- - -
已上传 ({{ server_count | default(0) }})
-
- -
-{% endif %} - - - - \ No newline at end of file diff --git a/tasks.py b/tasks.py index 356699c..48d0122 100644 --- a/tasks.py +++ b/tasks.py @@ -6,10 +6,14 @@ def run_task(task_name): os.system( "uv run python -m pytest --cov --cov-config=pyproject.toml --cov-report=term-missing" if task_name == "test" - else "uv run ruff check . && uv run ruff format --check . && uv run mypy src/main.py && uv run deptry ." + else "uv run ruff check . && uv run ruff format --check . && uv run python -m mypy src && uv run deptry ." if task_name == "check" - else "uv run python src/main.py" - if task_name == "run" + else "uv run python -m src.mcp.server" + if task_name == "mcp" + else "uv run python -m src.upload.server" + if task_name == "upload" + else "uv run python scripts/mcp_smoke.py" + if task_name == "mcp-smoke" else "uv sync && uv run pre-commit install" if task_name == "install" else "rm -rf .venv __pycache__ .pytest_cache .mypy_cache .ruff_cache" diff --git a/tests/test_extractor.py b/tests/test_extractor.py deleted file mode 100644 index b01d56c..0000000 --- a/tests/test_extractor.py +++ /dev/null @@ -1,332 +0,0 @@ -"""发票提取编排模块单元测试 - -覆盖范围: -- extract_invoices:空目录、提取失败、正常流程、分类结果、申请单分离 -""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -import pytest - -from src import exceptions -from src.core.extraction import extract_invoices - -# 字段键名(与源码中的字符串字面量保持一致) -K_INVOICE_TYPE = "invoice_type" -K_INVOICE_NUMBER = "invoice_number" -K_TOTAL_AMOUNT = "total_amount" -K_ITEM_NAME = "item_name" -K_PERSON_NAME = "person_name" -K_CARD_DATE = "card_date" -K_CARD_NO = "card_no" -K_CARD_AMOUNT = "card_amount" -K_MATCHED_INVOICES = "_matched_invoices" -K_RELATIVE_INVOICE_COUNT = "relative_invoice_count" -K_INVOICE_DETAIL = "invoice_detail" -K_REMARK = "remark" - -# 发票类型 -INVOICE_TYPE_TRAIN = "train" -INVOICE_TYPE_HOTEL = "hotel" -INVOICE_TYPE_GENERAL = "general" -INVOICE_TYPE_PAYMENT = "payment" -DOCUMENT_TYPE_APPLICATION = "application" - -# ------------------------------------------------------------------ -# Fixture helpers -# ------------------------------------------------------------------ - - -def _make_invoice(number: str, amount: float, inv_type: str = INVOICE_TYPE_GENERAL) -> dict[str, Any]: - inv: dict[str, Any] = { - K_INVOICE_NUMBER: number, - K_INVOICE_TYPE: inv_type, - K_TOTAL_AMOUNT: str(amount), - } - if inv_type == INVOICE_TYPE_TRAIN: - inv[K_PERSON_NAME] = f"person{number}" - elif inv_type == INVOICE_TYPE_GENERAL: - inv[K_ITEM_NAME] = f"item{number}" - return inv - - -def _make_application() -> dict[str, Any]: - return { - K_INVOICE_TYPE: DOCUMENT_TYPE_APPLICATION, - "applicant": "张三", - } - - -def _make_card(amount: float) -> dict[str, Any]: - return { - K_INVOICE_TYPE: INVOICE_TYPE_PAYMENT, - K_CARD_DATE: "2026-01-01", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: f"{amount:.2f}", - } - - -# ------------------------------------------------------------------ -# extract_invoices -# ------------------------------------------------------------------ - - -class TestExtractInvoices: - """extract_invoices 编排函数""" - - def test_empty_directory(self, tmp_path: Path, monkeypatch): - monkeypatch.setattr("src.core.extraction.extractor._find_all_files", lambda d: []) - - records, apps, groups = extract_invoices(str(tmp_path)) - assert records == [] - assert apps == [] - assert groups == {"travel": [], "general": [], "application": [], "attachments": []} - - def test_extraction_fails(self, tmp_path: Path, monkeypatch): - pdf = tmp_path / "broken.pdf" - pdf.touch() - - monkeypatch.setattr("src.core.extraction.extractor._find_all_files", lambda d: [pdf]) - monkeypatch.setattr("src.core.extraction.extractor._extract_document", lambda p, c, s: (None, "parse error")) - - with pytest.raises(exceptions.ExtractionError) as exc_info: - extract_invoices(str(tmp_path)) - - assert "broken.pdf" in exc_info.value.failed_files - assert exc_info.value.details["broken.pdf"] == "parse error" - - def test_normal_flow_general_invoices(self, tmp_path: Path, monkeypatch): - pdf1 = tmp_path / "inv1.pdf" - pdf2 = tmp_path / "inv2.pdf" - pdf1.touch() - pdf2.touch() - - inv1 = _make_invoice("INV001", 300.0, INVOICE_TYPE_GENERAL) - inv2 = _make_invoice("INV002", 200.0, INVOICE_TYPE_GENERAL) - - monkeypatch.setattr("src.core.extraction.extractor._find_all_files", lambda d: [pdf1, pdf2]) - - path_to_result = {pdf1: inv1, pdf2: inv2} - - def fake_extract(path, cache_dir, source_dir): - return (path_to_result[path], None) - - monkeypatch.setattr("src.core.extraction.extractor._extract_document", fake_extract) - - def fake_match(invoices, cards): - return [ - { - K_CARD_DATE: "2026-01-01", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "500.00", - K_RELATIVE_INVOICE_COUNT: "2", - K_INVOICE_DETAIL: "", - K_REMARK: "", - K_MATCHED_INVOICES: [inv1, inv2], - } - ] - - monkeypatch.setattr("src.core.extraction.extractor.match_invoices_to_cards", fake_match) - - records, apps, groups = extract_invoices(str(tmp_path)) - - assert len(records) == 1 - assert records[0][K_RELATIVE_INVOICE_COUNT] == "2" - assert len(groups["travel"]) == 0 - assert len(groups["general"]) == 2 - - def test_normal_flow_mixed_invoices(self, tmp_path: Path, monkeypatch): - pdf1 = tmp_path / "train.pdf" - pdf2 = tmp_path / "hotel.pdf" - pdf3 = tmp_path / "general.pdf" - pdf1.touch() - pdf2.touch() - pdf3.touch() - - inv_train = _make_invoice("TRAIN001", 500.0, INVOICE_TYPE_TRAIN) - inv_hotel = _make_invoice("HOTEL001", 800.0, INVOICE_TYPE_HOTEL) - inv_general = _make_invoice("GEN001", 150.0, INVOICE_TYPE_GENERAL) - - monkeypatch.setattr( - "src.core.extraction.extractor._find_all_files", - lambda d: [pdf1, pdf2, pdf3], - ) - - invoices_list = [inv_train, inv_hotel, inv_general] - path_to_result = dict(zip([pdf1, pdf2, pdf3], invoices_list, strict=True)) - - def fake_extract(path, cache_dir, source_dir): - return (path_to_result[path], None) - - monkeypatch.setattr("src.core.extraction.extractor._extract_document", fake_extract) - - def fake_match(invoices, cards): - return [ - { - K_CARD_DATE: "2026-01-01", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "500.00", - K_RELATIVE_INVOICE_COUNT: "1", - K_INVOICE_DETAIL: "", - K_REMARK: "", - K_MATCHED_INVOICES: [inv_train], - }, - { - K_CARD_DATE: "2026-01-02", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "800.00", - K_RELATIVE_INVOICE_COUNT: "1", - K_INVOICE_DETAIL: "", - K_REMARK: "", - K_MATCHED_INVOICES: [inv_hotel], - }, - { - K_CARD_DATE: "", - K_CARD_NO: "", - K_CARD_AMOUNT: "", - K_RELATIVE_INVOICE_COUNT: "1", - K_INVOICE_DETAIL: "", - K_REMARK: "unmatched", - K_MATCHED_INVOICES: [inv_general], - }, - ] - - monkeypatch.setattr("src.core.extraction.extractor.match_invoices_to_cards", fake_match) - - records, apps, groups = extract_invoices(str(tmp_path)) - - assert len(records) == 3 - assert len(groups["travel"]) == 2 - assert len(groups["general"]) == 1 - - travel_numbers = {inv[K_INVOICE_NUMBER] for inv in groups["travel"]} - assert "TRAIN001" in travel_numbers - assert "HOTEL001" in travel_numbers - assert groups["general"][0][K_INVOICE_NUMBER] == "GEN001" - - def test_application_documents_separated(self, tmp_path: Path, monkeypatch): - pdf1 = tmp_path / "invoice.pdf" - pdf2 = tmp_path / "application.pdf" - pdf1.touch() - pdf2.touch() - - inv = _make_invoice("INV001", 300.0, INVOICE_TYPE_GENERAL) - app = _make_application() - - monkeypatch.setattr( - "src.core.extraction.extractor._find_all_files", - lambda d: [pdf1, pdf2], - ) - - path_to_result = {pdf1: inv, pdf2: app} - - def fake_extract(path, cache_dir, source_dir): - return (path_to_result[path], None) - - monkeypatch.setattr("src.core.extraction.extractor._extract_document", fake_extract) - - def fake_match(invoices, cards): - return [ - { - K_CARD_DATE: "2026-01-01", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "300.00", - K_RELATIVE_INVOICE_COUNT: "1", - K_INVOICE_DETAIL: "", - K_REMARK: "", - K_MATCHED_INVOICES: [inv], - } - ] - - monkeypatch.setattr("src.core.extraction.extractor.match_invoices_to_cards", fake_match) - - records, apps, groups = extract_invoices(str(tmp_path)) - - assert len(apps) == 1 - assert apps[0]["applicant"] == "张三" - assert len(groups["application"]) == 1 - - def test_payment_records_included(self, tmp_path: Path, monkeypatch): - pdf1 = tmp_path / "invoice.pdf" - pdf2 = tmp_path / "card.png" - pdf1.touch() - pdf2.touch() - - inv = _make_invoice("INV001", 300.0, INVOICE_TYPE_GENERAL) - card = _make_card(300.0) - - monkeypatch.setattr( - "src.core.extraction.extractor._find_all_files", - lambda d: [pdf1, pdf2], - ) - - path_to_result = {pdf1: inv, pdf2: card} - - def fake_extract(path, cache_dir, source_dir): - return (path_to_result[path], None) - - monkeypatch.setattr("src.core.extraction.extractor._extract_document", fake_extract) - - def fake_match(invoices, cards): - return [ - { - K_CARD_DATE: "2026-01-01", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "300.00", - K_RELATIVE_INVOICE_COUNT: "1", - K_INVOICE_DETAIL: "", - K_REMARK: "", - K_MATCHED_INVOICES: [inv], - } - ] - - monkeypatch.setattr("src.core.extraction.extractor.match_invoices_to_cards", fake_match) - - records, apps, groups = extract_invoices(str(tmp_path)) - - assert len(records) == 1 - assert len(groups["general"]) == 1 - - def test_partial_extraction_failure(self, tmp_path: Path, monkeypatch): - pdf1 = tmp_path / "good.pdf" - pdf2 = tmp_path / "bad.pdf" - pdf1.touch() - pdf2.touch() - - inv = _make_invoice("INV001", 300.0, INVOICE_TYPE_GENERAL) - - monkeypatch.setattr( - "src.core.extraction.extractor._find_all_files", - lambda d: [pdf1, pdf2], - ) - - path_to_result = {pdf1: inv, pdf2: None} - - def fake_extract(path, cache_dir, source_dir): - result = path_to_result[path] - return (result, "parse error") if result is None else (result, None) - - monkeypatch.setattr("src.core.extraction.extractor._extract_document", fake_extract) - - def fake_match(invoices, cards): - return [ - { - K_CARD_DATE: "2026-01-01", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "300.00", - K_RELATIVE_INVOICE_COUNT: "1", - K_INVOICE_DETAIL: "", - K_REMARK: "", - K_MATCHED_INVOICES: [inv], - } - ] - - monkeypatch.setattr("src.core.extraction.extractor.match_invoices_to_cards", fake_match) - - records, apps, groups = extract_invoices(str(tmp_path)) - - assert len(records) == 1 - assert len(groups["general"]) == 1 diff --git a/tests/test_llm_extractor.py b/tests/test_llm_extractor.py deleted file mode 100644 index da6cd24..0000000 --- a/tests/test_llm_extractor.py +++ /dev/null @@ -1,202 +0,0 @@ -"""LLM 信息提取模块单元测试 - -覆盖范围: -- `parse_json_response`:纯 JSON、Markdown 包裹、带前缀、解析失败 -- _image_to_base64:图片转 base64 -- extract_document:成功提取、LLM 失败 -""" - -from __future__ import annotations - -import base64 -import json -from pathlib import Path - -import pytest - -from src.core.extraction import ( - _image_to_base64, - extract_document, - parse_json_response, -) - -# 字段键名(与源码中的字符串字面量保持一致) -K_INVOICE_NUMBER = "invoice_number" -K_TOTAL_AMOUNT = "total_amount" -K_CARD_DATE = "card_date" -K_CARD_NO = "card_no" -K_CARD_AMOUNT = "card_amount" - -# ------------------------------------------------------------------ -# parse_json_response -# ------------------------------------------------------------------ - - -class TestParseJsonResponse: - """JSON 响应解析""" - - def test_pure_json(self): - raw = json.dumps({K_INVOICE_NUMBER: "123456", K_TOTAL_AMOUNT: "100.00"}) - result = parse_json_response(raw) - assert result[K_INVOICE_NUMBER] == "123456" - assert result[K_TOTAL_AMOUNT] == "100.00" - - def test_markdown_json_block(self): - raw = f'```json\n{{"{K_INVOICE_NUMBER}": "789"}}\n```' - result = parse_json_response(raw) - assert result[K_INVOICE_NUMBER] == "789" - - def test_markdown_block_without_lang(self): - raw = '```\n{"key": "value"}\n```' - result = parse_json_response(raw) - assert result["key"] == "value" - - def test_json_prefix(self): - raw = f'json\n{{"{K_INVOICE_NUMBER}": "001"}}' - result = parse_json_response(raw) - assert result[K_INVOICE_NUMBER] == "001" - - def test_json_prefix_with_whitespace(self): - raw = ' json \n{"a": 1}' - result = parse_json_response(raw) - assert result["a"] == 1 - - def test_nested_json(self): - raw = json.dumps({"outer": {"inner": [1, 2, 3]}}) - result = parse_json_response(raw) - assert result["outer"]["inner"] == [1, 2, 3] - - def test_whitespace_around_json(self): - raw = ' \n {"x": 42} \n ' - result = parse_json_response(raw) - assert result["x"] == 42 - - def test_invalid_json_raises(self): - with pytest.raises(ValueError): - parse_json_response("not json at all") - - def test_empty_string_raises(self): - with pytest.raises(ValueError): - parse_json_response("") - - -# ------------------------------------------------------------------ -# _image_to_base64 -# ------------------------------------------------------------------ - - -class TestImageToBase64: - """图片转 base64""" - - def test_png_to_base64(self, tmp_path: Path): - content = b"\x89PNG\r\n\x1a\nfake_png_data" - img_path = tmp_path / "test.png" - img_path.write_bytes(content) - - result = _image_to_base64(img_path) - assert isinstance(result, str) - assert base64.b64decode(result) == content - - def test_jpg_to_base64(self, tmp_path: Path): - content = b"\xff\xd8\xff\xe0fake_jpg_data" - img_path = tmp_path / "test.jpg" - img_path.write_bytes(content) - - result = _image_to_base64(img_path) - assert base64.b64decode(result) == content - - def test_file_not_found_raises(self, tmp_path: Path): - img_path = tmp_path / "nonexistent.png" - with pytest.raises(FileNotFoundError): - _image_to_base64(img_path) - - def test_returns_utf8_string(self, tmp_path: Path): - content = b"test_image_content" - img_path = tmp_path / "test.png" - img_path.write_bytes(content) - - result = _image_to_base64(img_path) - assert type(result) is str - - -# ------------------------------------------------------------------ -# extract_document (mock LLM) -# ------------------------------------------------------------------ - - -class TestExtractDocument: - """图片提取:通过 mock _llm_query_multimodal 避免真实 LLM 调用""" - - def _mock_multimodal(self, monkeypatch, response_text: str): - def fake_query(system_prompt, text, image_b64, max_tokens=4096): - return response_text - - monkeypatch.setattr("src.core.extraction.llm_extractor._llm_query_multimodal", fake_query) - - def test_success(self, tmp_path: Path, monkeypatch): - img_path = tmp_path / "card.png" - img_path.write_bytes(b"fake_image") - - mock_result = { - K_CARD_DATE: "2026-01-10", - K_CARD_NO: "6228480000000000", - K_CARD_AMOUNT: "500.00", - } - self._mock_multimodal(monkeypatch, json.dumps(mock_result)) - - result = extract_document(img_path) - assert result[K_CARD_DATE] == "2026-01-10" - assert result[K_CARD_NO] == "6228480000000000" - assert result[K_CARD_AMOUNT] == "500.00" - - def test_llm_failure_propagates(self, tmp_path: Path, monkeypatch): - img_path = tmp_path / "card.png" - img_path.write_bytes(b"fake_image") - - def fake_query(system_prompt, text, image_b64, max_tokens=4096): - raise RuntimeError("模型不可用") - - monkeypatch.setattr("src.core.extraction.llm_extractor._llm_query_multimodal", fake_query) - with pytest.raises(RuntimeError, match="模型不可用"): - extract_document(img_path) - - def test_file_not_found_propagates(self, tmp_path: Path, monkeypatch): - img_path = tmp_path / "missing.png" - self._mock_multimodal(monkeypatch, "{}") - with pytest.raises(FileNotFoundError): - extract_document(img_path) - - def test_markdown_wrapped_json(self, tmp_path: Path, monkeypatch): - img_path = tmp_path / "card.png" - img_path.write_bytes(b"fake_image") - - mock_result = { - K_CARD_DATE: "2026-02-01", - K_CARD_NO: "6228481111111111", - K_CARD_AMOUNT: "300.00", - } - wrapped = f"```json\n{json.dumps(mock_result)}\n```" - self._mock_multimodal(monkeypatch, wrapped) - - result = extract_document(img_path) - assert result[K_CARD_DATE] == "2026-02-01" - - def test_image_encoded_as_base64(self, tmp_path: Path, monkeypatch): - img_path = tmp_path / "card.png" - expected_content = b"test_image_data" - img_path.write_bytes(expected_content) - - received_b64 = None - - def capture_b64(system_prompt, text, image_b64s, max_tokens=4096): - nonlocal received_b64 - received_b64 = image_b64s - return json.dumps({K_CARD_DATE: "2026-01-01", K_CARD_NO: "0000", K_CARD_AMOUNT: "100"}) - - monkeypatch.setattr("src.core.extraction.llm_extractor._llm_query_multimodal", capture_b64) - extract_document(img_path) - - assert received_b64 is not None - assert isinstance(received_b64, list) - assert len(received_b64) >= 1 - assert base64.b64decode(received_b64[0]) == expected_content diff --git a/uv.lock b/uv.lock index f390ece..5effe28 100644 --- a/uv.lock +++ b/uv.lock @@ -7,137 +7,6 @@ resolution-markers = [ "python_full_version < '3.14'", ] -[[package]] -name = "aiohappyeyeballs" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.14.1" -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/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" }, - { url = "https://files.pythonhosted.org/packages/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" }, - { url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" }, - { url = "https://files.pythonhosted.org/packages/8a/72/a60607cb849faa8af8a356c9329ea2eb6f395d49e82cc82ccba1fd8deb8f/aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2", size = 1766854, upload-time = "2026-06-07T21:06:45.391Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d3/d9fe1c9ec7557ab4d0d82bebaa728c6418f0b93295ec2f4ab015f7710cc7/aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a", size = 1740884, upload-time = "2026-06-07T21:06:47.413Z" }, - { url = "https://files.pythonhosted.org/packages/c1/dc/f2cecfaf9337ba3e63f181500814ff502aa3d00d9c7ec93a9d23d10a27b2/aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264", size = 1810034, upload-time = "2026-06-07T21:06:50.165Z" }, - { url = "https://files.pythonhosted.org/packages/66/d7/2ff65c5e65c0d7476daf7e15c032e0805e36811185b9623e3238ad6c763e/aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842", size = 1904054, upload-time = "2026-06-07T21:06:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/20/9c/d445818389df371f56d141d881153ba23183c4735a03f7356ffb43f7757d/aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c", size = 1790278, upload-time = "2026-06-07T21:06:54.049Z" }, - { url = "https://files.pythonhosted.org/packages/4d/aa/bf04cb4d865fc6101c2229a294ad744973b72e513fdc5a6b791e6983d72a/aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95", size = 1591795, upload-time = "2026-06-07T21:06:55.911Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b4/4dac0038960427ba832f6609dfb4ea5437d7fd80c72001b9e48f834f428b/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199", size = 1728397, upload-time = "2026-06-07T21:06:57.777Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/7cd4e8ad7aa3b75f17d56bb5498dd604a93d4e6eece822ba0568c413fff0/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817", size = 1766504, upload-time = "2026-06-07T21:07:00.009Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/fc01d9fcad0f73fed3f3d361f1f94f975947b50dff82919f6dc2bf4316cc/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a", size = 1777806, upload-time = "2026-06-07T21:07:02.064Z" }, - { url = "https://files.pythonhosted.org/packages/41/09/47e2d090bddcc8fb4ccb4c314aadc32d7c5d9bb55f50f6ad1c92fc15d501/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4", size = 1580707, upload-time = "2026-06-07T21:07:03.942Z" }, - { url = "https://files.pythonhosted.org/packages/3d/36/f1a4ce904ae0b6930cfe9afc96d0896f7ec1a620c400405d63783bb95a9c/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087", size = 1798121, upload-time = "2026-06-07T21:07:05.987Z" }, - { url = "https://files.pythonhosted.org/packages/70/0a/e0075ce9ca0279ee1d4f0c0b85f54fea02ebc83c3007651a72bece658fec/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3", size = 1767580, upload-time = "2026-06-07T21:07:07.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/a0c0a8f327a9c52095cdd8e312391b00d3ed64ab6c72bb5c33d8ec251cf7/aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4", size = 452771, upload-time = "2026-06-07T21:07:09.669Z" }, - { url = "https://files.pythonhosted.org/packages/df/d9/ea367c75f16ac9c6cdc8febb25e8318fa21a2b1bc8d6514d4b2d890bface/aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271", size = 479873, upload-time = "2026-06-07T21:07:11.538Z" }, - { url = "https://files.pythonhosted.org/packages/03/64/8d96784a7851156db8a4c6c3f6f91042fdf39fb15a4cc38c8b3c14833c45/aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847", size = 448073, upload-time = "2026-06-07T21:07:13.637Z" }, - { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, - { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, - { url = "https://files.pythonhosted.org/packages/fe/22/a73ccbf9dbd6e26dda0b24d5fd5db7da92ee3383a79f47677ffb834c5c5b/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd", size = 485841, upload-time = "2026-06-07T21:07:19.555Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b9/57ed8eaf596321c2ad747bd480fb1700dbd7177c60dfc9e4c187f629662e/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe", size = 492088, upload-time = "2026-06-07T21:07:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, - { url = "https://files.pythonhosted.org/packages/75/7f/8cdaa24fc7983865e0915153b96a9ac5bcdd3548d64c5a27d17cecccad2d/aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876", size = 751998, upload-time = "2026-06-07T21:07:25.046Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/01/a2d5f96cd4e74424864d30bc0a7e44d0a12dacdcfa91b5b2d1bd3dca6bf3/aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6", size = 508657, upload-time = "2026-06-07T21:07:29.252Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ed/3c0fb5c500fdd8e7ebc10d1889c04384fffa1a9163eac1356088ca9da1b1/aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96", size = 1757907, upload-time = "2026-06-07T21:07:31.03Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ab/d4c924d9bd5be3050c226612413ce68cb54c70d2c31b661bfc8d9a5b6a70/aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f", size = 1737565, upload-time = "2026-06-07T21:07:33.031Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/37326821ff779084020cdc33224d20b19f42f4183a500ff92022a739eda7/aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296", size = 1799018, upload-time = "2026-06-07T21:07:35.003Z" }, - { url = "https://files.pythonhosted.org/packages/b3/4f/6e947ba73e4ce09070761c05ed3a8ceb7c21f5e46798671d8b2aac0e4626/aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa", size = 1894416, upload-time = "2026-06-07T21:07:36.956Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6e/dbf1d0625dc711fb2851f4f3c3055c39ed58bae92082d8c627dbe6013736/aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451", size = 1783881, upload-time = "2026-06-07T21:07:39.063Z" }, - { url = "https://files.pythonhosted.org/packages/44/c2/5e25098a67268ed369483ae7d1a58bd0a13d03aab860d2a0e4a6eb25b046/aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c", size = 1587572, upload-time = "2026-06-07T21:07:41.058Z" }, - { url = "https://files.pythonhosted.org/packages/2a/bd/cf9cee17e140f942a3de73e658a543aa8fbf35a5fc67a9d2538d52d77f0b/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca", size = 1722137, upload-time = "2026-06-07T21:07:43.014Z" }, - { url = "https://files.pythonhosted.org/packages/89/6d/5684f8c59045c96f81a18cefbc1fbbd79d25b88f1c622f2a5c5c08fcb632/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09", size = 1755953, upload-time = "2026-06-07T21:07:45.933Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/35caf3170f8359760740a7d9aa0fff2e344bef98e1d1186f5a0f6dec17e6/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397", size = 1766479, upload-time = "2026-06-07T21:07:48.047Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a1/b0c61e7a137f0d81de49a82023a6df73c3c16d6fefb0f8e4a93d21639002/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080", size = 1580077, upload-time = "2026-06-07T21:07:50.069Z" }, - { url = "https://files.pythonhosted.org/packages/0b/41/194ea4623693009fcefebef7aef63c141754f153e9cd0d39d3b9e36c175c/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345", size = 1791688, upload-time = "2026-06-07T21:07:52.106Z" }, - { url = "https://files.pythonhosted.org/packages/ba/45/4de841f005cfe1fd63e2a2fe011262c515e2a62aa6994b15947e7d717ac9/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588", size = 1761094, upload-time = "2026-06-07T21:07:54.113Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, - { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, - { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, - { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, - { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, - { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, - { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, - { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, - { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, - { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, - { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, - { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, - { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, - { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, - { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, - { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, - { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, - { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, - { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, - { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, - { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, - { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, - { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, - { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, - { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, -] - -[[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" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, -] - [[package]] name = "annotated-types" version = "0.7.0" @@ -215,12 +84,9 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "flask" }, - { name = "llama-index" }, - { name = "llama-index-llms-openai-like" }, { name = "mcp" }, { name = "playwright" }, { name = "pymupdf" }, - { name = "python-dotenv" }, { name = "pywin32" }, { name = "segno" }, ] @@ -238,12 +104,9 @@ dev = [ [package.metadata] requires-dist = [ { name = "flask", specifier = ">=3.0" }, - { name = "llama-index", specifier = ">=0.12.0" }, - { name = "llama-index-llms-openai-like", specifier = ">=0.7.2" }, { name = "mcp", specifier = ">=2.3.0" }, { name = "playwright", specifier = ">=1.40" }, { name = "pymupdf", specifier = ">=1.24" }, - { name = "python-dotenv", specifier = ">=1.0" }, { name = "pywin32", specifier = ">=306" }, { name = "segno", specifier = ">=1.6.6" }, ] @@ -258,23 +121,6 @@ dev = [ { name = "ruff", specifier = ">=0.9" }, ] -[[package]] -name = "banks" -version = "2.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "filetype" }, - { name = "griffe" }, - { name = "jinja2" }, - { name = "platformdirs" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/51/08fb68d23f4b0f6256fe85dc86e9576941550f890b079352fba719e07b39/banks-2.4.2.tar.gz", hash = "sha256:cda6013bd377ea7b701933578bfb9370fc21ad70bc13cedfc3f5cb2c034ca3dc", size = 188633, upload-time = "2026-04-27T12:15:22.021Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/b6/8dc5477681b782e2f99de703e7a99828883364b9e03a60d3e2c47053d56a/banks-2.4.2-py3-none-any.whl", hash = "sha256:5fe407cc48c101f3e13d1cf732b83b8246003337612f13c0705d2e81f6faffb7", size = 35050, upload-time = "2026-04-27T12:15:20.785Z" }, -] - [[package]] name = "blinker" version = "1.9.0" @@ -284,15 +130,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] -[[package]] -name = "certifi" -version = "2026.5.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, -] - [[package]] name = "cffi" version = "2.1.1" @@ -387,79 +224,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] -[[package]] -name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, -] - [[package]] name = "click" version = "8.4.1" @@ -628,31 +392,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/b6/a1faf3a27ae9405fb34b1713cc73b2d8a26b04d5c561578fa2e6ef3e5bb9/cryptography-50.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:4e81d95e5bafc2d6e34e4bed780e53e4d5b9a2f928573428aa4d35fbec1eb0de", size = 3854095, upload-time = "2026-09-30T15:29:46.782Z" }, ] -[[package]] -name = "dataclasses-json" -version = "0.6.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "marshmallow" }, - { name = "typing-inspect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, -] - -[[package]] -name = "deprecated" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, -] - [[package]] name = "deptry" version = "0.25.1" @@ -676,15 +415,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/7f/cd6b3ac8cf95f2f1c5c7a74ff6452e9098af89a9b56607381f677880641e/deptry-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:6efffd8116fb9d2c45a251382ce4ce1c38dbb17179f581ec9231ed5390f7fc12", size = 1647020, upload-time = "2026-03-18T23:22:23.311Z" }, ] -[[package]] -name = "dirtyjson" -version = "1.0.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782, upload-time = "2022-11-28T23:32:33.319Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197, upload-time = "2022-11-28T23:32:31.219Z" }, -] - [[package]] name = "distlib" version = "0.4.3" @@ -694,15 +424,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, ] -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - [[package]] name = "filelock" version = "3.29.4" @@ -712,15 +433,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" }, ] -[[package]] -name = "filetype" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, -] - [[package]] name = "flask" version = "3.1.3" @@ -738,104 +450,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, ] -[[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.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, -] - [[package]] name = "greenlet" version = "3.5.1" @@ -903,41 +517,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, ] -[[package]] -name = "griffe" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "griffecli" }, - { name = "griffelib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4a/49/eb6d2935e27883af92c930ed40cc4c69bcd32c402be43b8ca4ab20510f67/griffe-2.0.2.tar.gz", hash = "sha256:c5d56326d159f274492e9bf93a9895cec101155d944caa66d0fc4e0c13751b92", size = 293757, upload-time = "2026-03-27T11:34:52.205Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/c0/2bb018eecf9a83c68db9cd9fffd9dab25f102ad30ed869451046e46d1187/griffe-2.0.2-py3-none-any.whl", hash = "sha256:2b31816460aee1996af26050a1fc6927a2e5936486856707f55508e4c9b5960b", size = 5141, upload-time = "2026-03-27T11:34:47.721Z" }, -] - -[[package]] -name = "griffecli" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, - { name = "griffelib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/e0/6a7d661d71bb043656a109b91d84a42b5342752542074ec83b16a6eb97f0/griffecli-2.0.2.tar.gz", hash = "sha256:40a1ad4181fc39685d025e119ae2c5b669acdc1f19b705fb9bf971f4e6f6dffb", size = 56281, upload-time = "2026-03-27T11:34:50.087Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/e8/90d93356c88ac34c20cb5edffca68138df55ca9bbd1a06eccfbcec8fdbe5/griffecli-2.0.2-py3-none-any.whl", hash = "sha256:0d44d39e59afa81e288a3e1c3bf352cc4fa537483326ac06b8bb6a51fd8303a0", size = 9500, upload-time = "2026-03-27T11:34:48.81Z" }, -] - -[[package]] -name = "griffelib" -version = "2.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -947,19 +526,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - [[package]] name = "httpcore2" version = "2.14.0" @@ -973,21 +539,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/06/a8d6a8f90d11b76d9086242b6bb0ee4b503473bfc8be34c2d5b067275c22/httpcore2-2.14.0-py3-none-any.whl", hash = "sha256:4c4f64f4b544afeb778c42c0ffb5941b29e671157e90ed3f6dc787c4bca90cd1", size = 84291, upload-time = "2026-10-18T16:29:19.743Z" }, ] -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - [[package]] name = "httpx2" version = "2.14.0" @@ -1062,87 +613,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] -[[package]] -name = "jiter" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/b5/55f06bb281d92fb3cc86d14e1def2bd908bb77693183e7cb1f5a3c388b0c/jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76", size = 166640, upload-time = "2026-05-19T10:09:48.361Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/53/4f6bddbcde3c71e56d0aa1337ec95950f3d27dd4153e25aadf0feac71751/jiter-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e90a1c315a0226ec822d973817967f9223b7701546c8c2a7913e7ab0926294d", size = 308793, upload-time = "2026-05-19T10:07:35.25Z" }, - { url = "https://files.pythonhosted.org/packages/01/84/c01099b59a285a1ebba64ae93f62bfa036675340fd1b0045ae65890a0442/jiter-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c9004af7c8d67cce7f1aae1026fb55607f4aa600710d08ede3a3ce4aeefe7e0", size = 309570, upload-time = "2026-05-19T10:07:36.919Z" }, - { url = "https://files.pythonhosted.org/packages/58/64/8fb7f9d45bb98190355454cd04dad8d8f27223d6bd52f83af07f637168a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c210f8b35dc6f30aafd4b4365ca89b9d1189f21ab49b8e68fa6322a847aef138", size = 336783, upload-time = "2026-05-19T10:07:38.694Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b6/f5739011d009b3a30f6a53c5240979030ba29ae46a8c67e3a15759f7c37d/jiter-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f30bae8bc1c2d613e28e5af3e8cceb09b742f1c8a8a5f839fb67afaffc03b61", size = 363555, upload-time = "2026-05-19T10:07:40.832Z" }, - { url = "https://files.pythonhosted.org/packages/e5/12/98a9d9f766665e8a3b6252454e17cb0c464606a28cf2fa09399b003345fa/jiter-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e71b6d10cfc284c9bf36bd885e8d44c46f688ce50aa91b5edd90181dea687", size = 452255, upload-time = "2026-05-19T10:07:42.62Z" }, - { url = "https://files.pythonhosted.org/packages/e8/d5/60f972840f79c5e7544fce567c56f1e4e50468f996baba3e78d823dd62a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab068bce62a45aa3e7367eceaffb5dde60b7eb853be8dece45132e3d0ff4879", size = 373559, upload-time = "2026-05-19T10:07:44.201Z" }, - { url = "https://files.pythonhosted.org/packages/ee/cf/d46ef1234ba335aabc2f013210db8e0821a22f5e644a2e9449df199ecc23/jiter-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa248c9eb220197d363f688818dac2fd4b2f0cd7d843ca7105d652034823427d", size = 346055, upload-time = "2026-05-19T10:07:46.005Z" }, - { url = "https://files.pythonhosted.org/packages/f0/63/4d2749d8d54d230bad9b3a6b0d00cc28c6ff6b2fdffc26a8ccf76cc5a974/jiter-0.15.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2a77aadd57cac1682e4401a72724d2796d89a4ba129b1a5812aa94ee480826eb", size = 351406, upload-time = "2026-05-19T10:07:47.855Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b9/9965b990035d8773328e0a8c8b457a87bf2b19f6c4126d9d99296be5d16a/jiter-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ae901f3a55bfafdde31d289590fa25e3245735a2b1e8c7cc15871710a002871", size = 389357, upload-time = "2026-05-19T10:07:49.665Z" }, - { url = "https://files.pythonhosted.org/packages/2d/55/9ddf903deda1413e87fed792f416b7123daee5b8efbad6a202a7421c36a5/jiter-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0b271b462769543716f92d3a4f90527df6ef5ed05ee95ec4137f513e21e1b77", size = 517263, upload-time = "2026-05-19T10:07:51.537Z" }, - { url = "https://files.pythonhosted.org/packages/e8/76/a0c40ad064d3a20a4fde231e35d56e9a01ce82164278180e82d5daf85469/jiter-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fb6a5d26af81fc0f00f9360a891e05cf755e149bba391c4d563adc54812973d", size = 548646, upload-time = "2026-05-19T10:07:53.196Z" }, - { url = "https://files.pythonhosted.org/packages/23/4f/eca9b954942916ba2f453891b8593ab444cd872396fe66a3936616f236f3/jiter-0.15.0-cp312-cp312-win32.whl", hash = "sha256:c2f6bb8b5216ab9e7873bc08b5d7bef2b8abbb578a3069bf1cd14a45d71d771d", size = 206427, upload-time = "2026-05-19T10:07:55.307Z" }, - { url = "https://files.pythonhosted.org/packages/95/bf/8ead82a87495149542748e828d153fd232a512a22c83b02c4815c1a9c7d8/jiter-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:40b2c7e92c44a84d748d21706c68dc6ff8161d80b59c99d774721a0d2317d7c7", size = 197300, upload-time = "2026-05-19T10:07:56.651Z" }, - { url = "https://files.pythonhosted.org/packages/f4/e4/9b8a78fb2d894471bc344e37f1949bdd784bd914d031dba0ba3a40c71dd7/jiter-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:cc0bc345cf2df9d1c00ac443f50d543c1ccfa8b0422cb85b1ab70d681c0b255b", size = 192702, upload-time = "2026-05-19T10:07:58.307Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f4/f708c900ecee41b2025ef8413d5351e5649eb2125c506f6720cc69b06f5c/jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3", size = 307829, upload-time = "2026-05-19T10:07:59.704Z" }, - { url = "https://files.pythonhosted.org/packages/86/59/db537c0949e83668c38481d426b9f2fd5ab758c4ee53a811dd0a510626a0/jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5", size = 308445, upload-time = "2026-05-19T10:08:01.184Z" }, - { url = "https://files.pythonhosted.org/packages/37/38/ea0e13b18c30ef951da0d47d39e7fa9edb82a93a62990ffbd7cea9b622d4/jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279", size = 336181, upload-time = "2026-05-19T10:08:02.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/fc/2303901b16c4ba05865588990a420c0b4156270b44379c20931544a1d962/jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4", size = 362985, upload-time = "2026-05-19T10:08:04.394Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6f/11bace093c52e7d4d26c8e606ccd7ae8c972189622469ec0d9e28161e28b/jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258", size = 453292, upload-time = "2026-05-19T10:08:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/22/db/987f2f086ca4d7a6582eb4ccd513f9b26b42d9e4243a087609a3137a8fc7/jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894", size = 373501, upload-time = "2026-05-19T10:08:07.857Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7c/89fbcabb2739b7a5b8dc959a1b6c5761f6484f5fed3486854b3c789bb1de/jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45", size = 344683, upload-time = "2026-05-19T10:08:09.431Z" }, - { url = "https://files.pythonhosted.org/packages/30/6f/6cca7692e7dddfec6d8d76c54dc97f2af2a41df4ac0674b999df1f09a5f3/jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29", size = 350892, upload-time = "2026-05-19T10:08:11.352Z" }, - { url = "https://files.pythonhosted.org/packages/39/14/0338d6190cb8e6d22e677ab1d4eabd4117f67cca70c54cd04b82ff64e068/jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b", size = 388723, upload-time = "2026-05-19T10:08:12.912Z" }, - { url = "https://files.pythonhosted.org/packages/90/31/cc19f4a1bdb6afb09ce6a2f2615aa8d44d994eba0d8e6105ed1af920e736/jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7", size = 516648, upload-time = "2026-05-19T10:08:14.808Z" }, - { url = "https://files.pythonhosted.org/packages/49/9f/833c541512cd091b63c10c0381973dfe11bc7a503a818c16384417e0c81e/jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712", size = 547382, upload-time = "2026-05-19T10:08:16.927Z" }, - { url = "https://files.pythonhosted.org/packages/d2/11/e7b70e91f90bc4477e8eee9e8a5f7cf3cb41b4525d6394dc98a714eb8f7f/jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c", size = 205845, upload-time = "2026-05-19T10:08:18.401Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/5c20d9ad6f02c493e4023e5d2d09e1c1f15fe2753c9102c544aff068a88e/jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0", size = 196842, upload-time = "2026-05-19T10:08:20.131Z" }, - { url = "https://files.pythonhosted.org/packages/6b/11/1eb400ef248e8c925fd883fbe325daf5e42cd1b0d308539dd332bd4f7ffc/jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba", size = 192212, upload-time = "2026-05-19T10:08:21.807Z" }, - { url = "https://files.pythonhosted.org/packages/8a/60/2fd8d7c79da8acf9b7b277c7616847773779356b92acfc9bb158452174da/jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8", size = 315065, upload-time = "2026-05-19T10:08:23.218Z" }, - { url = "https://files.pythonhosted.org/packages/46/f4/008fb7d65e8ac2abf00811651a661e025c4ba80bbc6f378450384ddd3aed/jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c", size = 339444, upload-time = "2026-05-19T10:08:24.701Z" }, - { url = "https://files.pythonhosted.org/packages/00/55/90b0c7b9c6896c0f2a591dd36d36b71d22e09674bfef178fa03ba3f81499/jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4", size = 347779, upload-time = "2026-05-19T10:08:26.408Z" }, - { url = "https://files.pythonhosted.org/packages/51/6b/69666cec5000fd57734c118437394516c749ae8dbeea9fb66d6fef9c4775/jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b", size = 200395, upload-time = "2026-05-19T10:08:28.055Z" }, - { url = "https://files.pythonhosted.org/packages/39/04/a6aa62cd27e8149b0d28df5561f10f6cceaf7935a9ccf3f1c5a05f9a0cd8/jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7", size = 190516, upload-time = "2026-05-19T10:08:29.35Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d2/079f350ebf7859d081de30aa890f9e3be68516f754f3ba32366ffff4dcee/jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49", size = 308884, upload-time = "2026-05-19T10:08:31.667Z" }, - { url = "https://files.pythonhosted.org/packages/04/4e/a2c30a7f69b48c03b20935d647479106fe932f6e63f75faf53937197e05d/jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86", size = 310028, upload-time = "2026-05-19T10:08:33.304Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/2e7cdfd3cf8ca967be38c48f5cf474d79f089efaf559a40f15984a77ae69/jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f", size = 337485, upload-time = "2026-05-19T10:08:35.259Z" }, - { url = "https://files.pythonhosted.org/packages/9b/11/15a1aa28b120b8ee5b4f1fb894c125046225f09847738bd64233d3b84883/jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e", size = 364223, upload-time = "2026-05-19T10:08:36.694Z" }, - { url = "https://files.pythonhosted.org/packages/b7/25/f442e8af5f3d0dcf47b39e83a0efd9ee45ea946aa6d04625dc3181eae3b6/jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6", size = 456387, upload-time = "2026-05-19T10:08:38.143Z" }, - { url = "https://files.pythonhosted.org/packages/da/f4/37f2d2c9f64f49af7da652ed7532bb5a2372e588e6927c3fdd76f911db65/jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9", size = 374461, upload-time = "2026-05-19T10:08:39.869Z" }, - { url = "https://files.pythonhosted.org/packages/60/28/edcfbbbf0cb15436f36664a8908a0df47ab9006298d4cd937dc08ea932d6/jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c", size = 345924, upload-time = "2026-05-19T10:08:41.668Z" }, - { url = "https://files.pythonhosted.org/packages/47/13/89fba6398dab7f202b7278c4b4aac122399d2c0183971c4a57a3b7088df5/jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd", size = 352283, upload-time = "2026-05-19T10:08:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/1b/da/0f6af8cef2c565a1ab44d970f268c43ccaa72707386ea6388e6fe2b6cd26/jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89", size = 389985, upload-time = "2026-05-19T10:08:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ec/b9cb7d6d29e24ee14910266157d2a279d7a8f60ee0df7fa840882976ba64/jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554", size = 517695, upload-time = "2026-05-19T10:08:46.486Z" }, - { url = "https://files.pythonhosted.org/packages/64/5e/6d1bda880723aae0ad86b4b763f044362448efe31e3e819635d41cb03451/jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a", size = 548868, upload-time = "2026-05-19T10:08:48.026Z" }, - { url = "https://files.pythonhosted.org/packages/0c/72/7de501cf38dcacaf35098796f3a50e0f2e338baba18a58946c618544b809/jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec", size = 206380, upload-time = "2026-05-19T10:08:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/1e/a9/e19addf4b0c1bdce52c6da12351e6bc42c340c45e7c09e2158e46d293ccc/jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558", size = 197687, upload-time = "2026-05-19T10:08:51.088Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c9/776b1db01db25fc6c1d58d1979a37b0a9fe787e5f5b1d062d2eaacb77923/jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866", size = 192571, upload-time = "2026-05-19T10:08:52.451Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f6/45bb4670bacf300fd2c7abadbfb3af376e5f1b6ae75fd9bc069891d15870/jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d", size = 317151, upload-time = "2026-05-19T10:08:53.867Z" }, - { url = "https://files.pythonhosted.org/packages/d7/68/ed635ad5acd7b73e454283083bbb7c8205ad10e88b0d9d7d793b09fe8226/jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6", size = 341243, upload-time = "2026-05-19T10:08:55.383Z" }, - { url = "https://files.pythonhosted.org/packages/5d/db/3ff4176b817b8ea33879e71e13d8bc2b0d481a7ed3fe9e080f333d415c16/jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995", size = 363629, upload-time = "2026-05-19T10:08:56.928Z" }, - { url = "https://files.pythonhosted.org/packages/ab/24/5f8270e0ba9c883582f96f722f8a0b58015c7ce1f8c6d4571cf394e99b6b/jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8", size = 456198, upload-time = "2026-05-19T10:08:58.618Z" }, - { url = "https://files.pythonhosted.org/packages/45/5b/76fc02b0b5c54c3d18c60653156e2f76fde1816f9b4722db68d6ee2c897e/jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5", size = 373710, upload-time = "2026-05-19T10:09:00.151Z" }, - { url = "https://files.pythonhosted.org/packages/c4/52/4310821b0ea9277994d3e1f49fc6a4b34e4800caebacb2c0af81da59a454/jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b", size = 349901, upload-time = "2026-05-19T10:09:01.621Z" }, - { url = "https://files.pythonhosted.org/packages/93/fe/67648c35b3594fba8854ac64cc8a826d8bcd18324bbdb53d77697c60b6ef/jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8", size = 352438, upload-time = "2026-05-19T10:09:03.216Z" }, - { url = "https://files.pythonhosted.org/packages/cb/28/0a1879d07ad6b3e025a2750027363452ced93c2d16d1c9d4b153ffd51c91/jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec", size = 388152, upload-time = "2026-05-19T10:09:04.741Z" }, - { url = "https://files.pythonhosted.org/packages/c1/78/46c6f6b56ba85c90021f4afd72ed42f691f8f84daacb5fe27277070e3858/jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e", size = 517707, upload-time = "2026-05-19T10:09:06.231Z" }, - { url = "https://files.pythonhosted.org/packages/ca/cb/720662d4c88fcad606e826fef5424365527ba43ce4868a479aed8f8c507e/jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5", size = 548241, upload-time = "2026-05-19T10:09:08.093Z" }, - { url = "https://files.pythonhosted.org/packages/60/e3/935b8034fd143f21125c87d51404a9e0e1449186a494405721ff5d1d695e/jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52", size = 207950, upload-time = "2026-05-19T10:09:09.616Z" }, - { url = "https://files.pythonhosted.org/packages/93/59/984fd9ece895953dad3e0880a650e766f5a2da2c5514f0eafdaaabbeb5f9/jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854", size = 200055, upload-time = "2026-05-19T10:09:11.367Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a4/cf8d779feb133a27a2e3bc833bccb9e13aa332cdf820497ebf72c10ce8c3/jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0", size = 191244, upload-time = "2026-05-19T10:09:12.74Z" }, - { url = "https://files.pythonhosted.org/packages/73/38/505941b2b092fd5bbbd60a52a880db1173f1690ae6751bed3af1c9ddcb4e/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:631f13a3d04e97d4e083993b10f4b99530e3a10d953e2eb5e196b7dc7f812ce0", size = 303769, upload-time = "2026-05-19T10:09:42.203Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/a06692b29e77473f286e1ec1f426d3ca44d7b5843be8ad21d7a5f3fcdcc0/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b6c0ffae686c39bf3737be60793783267628783ea42545632c10b291105aee45", size = 305128, upload-time = "2026-05-19T10:09:43.657Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/7270d7ad41d6061a25b950c6bf91d638bd9aacb113200a8c8d57a055fd67/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d54fb5b31dea401a41af3f8a7d2512e9b6a6a005491e6166c7e4ffab9639a9c", size = 340459, upload-time = "2026-05-19T10:09:45.452Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8d/302cb2057b7513327b4d575cff6b1d066ee6431a5357fc3f8867cd684406/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d5d6090cdc1b7c9e780dfb04949a990adb1e301a2fc0bbcee7de4638d33f9a", size = 344469, upload-time = "2026-05-19T10:09:46.864Z" }, -] - -[[package]] -name = "joblib" -version = "1.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, -] - [[package]] name = "jsonschema" version = "4.26.0" @@ -1230,126 +700,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, ] -[[package]] -name = "llama-index" -version = "0.14.22" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "llama-index-embeddings-openai" }, - { name = "llama-index-llms-openai" }, - { name = "nltk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/89/3b6f3318ea2249158daab3ff22777ef5ffa87a63c011659a6cfc55e54c35/llama_index-0.14.22.tar.gz", hash = "sha256:c2c9b31f50d2815abdc191085db4acaf96b7c01851ac66b2e4cc82be8cde589e", size = 8565, upload-time = "2026-05-14T20:22:21.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/fd/f0837c4ce049d8ece7525bbf64564e93e3f16333856c2a0b47fecb58f317/llama_index-0.14.22-py3-none-any.whl", hash = "sha256:14b4bdd799112062e38288eab6aa16643f29d7532505ab174b0b6d5b0817fe94", size = 7115, upload-time = "2026-05-14T20:22:19.611Z" }, -] - -[[package]] -name = "llama-index-core" -version = "0.14.22" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "aiosqlite" }, - { name = "banks" }, - { name = "dataclasses-json" }, - { name = "deprecated" }, - { name = "dirtyjson" }, - { name = "filetype" }, - { name = "fsspec" }, - { name = "httpx" }, - { name = "llama-index-workflows" }, - { name = "nest-asyncio" }, - { name = "networkx" }, - { name = "nltk" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "platformdirs" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "setuptools" }, - { name = "sqlalchemy", extra = ["asyncio"] }, - { name = "tenacity" }, - { name = "tiktoken" }, - { name = "tinytag" }, - { name = "tqdm" }, - { name = "typing-extensions" }, - { name = "typing-inspect" }, - { name = "wrapt" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/7f/94a4b940ef0d069840df0fd6d361a2aa832a2dd73b4cecdf86e8f8c353c8/llama_index_core-0.14.22.tar.gz", hash = "sha256:1384410f89bdbd32349aab444ef4f5c828c338787bc65bd1ffd8e86dfb44ac41", size = 11584786, upload-time = "2026-05-14T20:21:37.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/15/e1a26d8d56aa55fa07587a3e9c7e85294d2df5af6c2229193019bc549ef6/llama_index_core-0.14.22-py3-none-any.whl", hash = "sha256:9cfffde46fd5b7937101e1c0c9bb5c21bd7ff8c8a56937810b87ba3542f31225", size = 11920774, upload-time = "2026-05-14T20:21:40.409Z" }, -] - -[[package]] -name = "llama-index-embeddings-openai" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/52/eb56a4887501651fb17400f7f571c1878109ff698efbe0bbac9165a5603d/llama_index_embeddings_openai-0.6.0.tar.gz", hash = "sha256:eb3e6606be81cb89125073e23c97c0a6119dabb4827adbd14697c2029ad73f29", size = 7629, upload-time = "2026-03-12T20:21:27.234Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/d1/4bb0b80f4057903110060f617ef519197194b3ff5dd6153d850c8f5676fa/llama_index_embeddings_openai-0.6.0-py3-none-any.whl", hash = "sha256:039bb1007ad4267e25ddb89a206dfdab862bfb87d58da4271a3919e4f9df4d61", size = 7666, upload-time = "2026-03-12T20:21:28.079Z" }, -] - -[[package]] -name = "llama-index-instrumentation" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecated" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d0/671b23ccff255c9bce132a84ffd5a6f4541ceefdeab9c1786b08c9722f2e/llama_index_instrumentation-0.5.0.tar.gz", hash = "sha256:eeb724648b25d149de882a5ac9e21c5acb1ce780da214bda2b075341af29ad8e", size = 43831, upload-time = "2026-03-12T20:17:06.742Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/45/6dcaccef44e541ffa138e4b45e33e0d40ab2a7d845338483954fcf77bc75/llama_index_instrumentation-0.5.0-py3-none-any.whl", hash = "sha256:aaab83cddd9dd434278891012d8995f47a3bc7ed1736a371db90965348c56a21", size = 16444, upload-time = "2026-03-12T20:17:05.957Z" }, -] - -[[package]] -name = "llama-index-llms-openai" -version = "0.7.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/85/f07466d0f5c3f1e1f295a60f2d2e9d32163635ba39ae2fda22ce2fbac1c9/llama_index_llms_openai-0.7.9.tar.gz", hash = "sha256:f54a24b717134c86e724007057a06a84394f019d1f01e918b624894e208a86df", size = 27564, upload-time = "2026-05-29T15:32:37.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/c2/cfa341cff00154b58abed3fc559fb43c2531d5dd70eefb75f230704d4c3c/llama_index_llms_openai-0.7.9-py3-none-any.whl", hash = "sha256:0bc8f59faddf8dbc9f90c5576127ab2fa6d368be5078885dc7e611af129b5a79", size = 28648, upload-time = "2026-05-29T15:32:35.997Z" }, -] - -[[package]] -name = "llama-index-llms-openai-like" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-core" }, - { name = "llama-index-llms-openai" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/a3/16410b28d131aa113ada79f856b78cb68a8e92a1e27255ea9c36c27a5dec/llama_index_llms_openai_like-0.7.2.tar.gz", hash = "sha256:ed9ff73f975dce470f98ac61c982151ba78eedfa3fb9b03894bc1d1312b213ff", size = 5389, upload-time = "2026-04-23T23:05:32.525Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/0c/fdddaee5391d915d3d568d2d8dbdb7c95647e65bb94d4ddb31d47cef5daf/llama_index_llms_openai_like-0.7.2-py3-none-any.whl", hash = "sha256:1f45a7b1cec8fb3f5997684327ffe6c19f93e789c2fff35dc5522465850faf0b", size = 6602, upload-time = "2026-04-23T23:05:31.708Z" }, -] - -[[package]] -name = "llama-index-workflows" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llama-index-instrumentation" }, - { name = "pydantic" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/ec/05f3db99a2e6e252e3939e7751cad2fb1322dc6d32f4cf5c795cf7ddcad3/llama_index_workflows-2.20.0.tar.gz", hash = "sha256:df2760fea9e100c97a4e919d255461e344413acac4382d17d8217337806e4772", size = 97410, upload-time = "2026-04-24T14:54:41.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/5f/385231406d777cb4b608fd8ebe3577dbd90962770717181e6b91b44fb1b8/llama_index_workflows-2.20.0-py3-none-any.whl", hash = "sha256:36f6b6ace77f837d9907078aea7e830251afe96a58daecff5ed090c88c55095d", size = 121238, upload-time = "2026-04-24T14:54:40.455Z" }, -] - [[package]] name = "markupsafe" version = "3.0.3" @@ -1413,18 +763,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] -[[package]] -name = "marshmallow" -version = "3.26.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/79/de6c16cc902f4fc372236926b0ce2ab7845268dcc30fb2fbb7f71b418631/marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57", size = 222095, upload-time = "2025-12-22T06:53:53.309Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964, upload-time = "2025-12-22T06:53:51.801Z" }, -] - [[package]] name = "mcp" version = "2.3.0" @@ -1463,105 +801,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" }, ] -[[package]] -name = "multidict" -version = "6.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, - { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, - { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, - { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, - { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, - { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, - { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, - { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, - { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, - { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, - { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, - { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, - { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, - { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, - { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, - { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, - { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, - { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, - { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, - { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, - { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, - { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, - { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, - { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, - { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, - { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, - { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, - { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, - { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, - { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, - { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, - { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, - { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, - { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, -] - [[package]] name = "mypy" version = "2.1.0" @@ -1615,39 +854,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] -[[package]] -name = "nest-asyncio" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, -] - -[[package]] -name = "networkx" -version = "3.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, -] - -[[package]] -name = "nltk" -version = "3.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "joblib" }, - { name = "regex" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, -] - [[package]] name = "nodeenv" version = "1.10.0" @@ -1657,86 +863,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] -[[package]] -name = "numpy" -version = "2.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, - { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, - { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, - { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, - { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, - { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, - { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, - { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, - { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, -] - -[[package]] -name = "openai" -version = "2.41.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/36/4c926a91554483977608951360c18c2e911592785eb87a6437813f6123f7/openai-2.41.1.tar.gz", hash = "sha256:23d617a0432457ad844973bee8f540be9da90894f7c5686852d2d365da058f57", size = 783584, upload-time = "2026-06-10T16:10:37.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/74/925d7b3892927e9804aaf58d374a45dc28e4420ff90e992272b77286343e/openai-2.41.1-py3-none-any.whl", hash = "sha256:a939565f350cb7443cb843b801b88c716ac8024b492fb94ca269d5f6b1bbefd6", size = 1353380, upload-time = "2026-06-10T16:10:35.756Z" }, -] - [[package]] name = "opentelemetry-api" version = "1.45.1" @@ -1767,75 +893,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] -[[package]] -name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, -] - [[package]] name = "platformdirs" version = "4.10.0" @@ -1889,100 +946,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, ] -[[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 = "pycparser" version = "3.11" @@ -2176,15 +1139,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" }, ] -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - [[package]] name = "python-multipart" version = "0.0.32" @@ -2273,109 +1227,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] -[[package]] -name = "regex" -version = "2026.5.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, - { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, - { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, - { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, - { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, - { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, - { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, - { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, - { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, - { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, - { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, - { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, - { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, - { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, - { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, - { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, - { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, - { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, - { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, - { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, - { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, - { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, - { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, - { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, - { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, - { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, - { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, - { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, - { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, - { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, - { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, - { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, - { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, - { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, - { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, - { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, - { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, -] - -[[package]] -name = "requests" -version = "2.34.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, -] - [[package]] name = "requirements-parser" version = "0.13.0" @@ -2530,70 +1381,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/02/12c73fd423eb9577b97fc1924966b929eff7074ae6b2e15dd3d30cb9e4ae/segno-1.6.6-py3-none-any.whl", hash = "sha256:28c7d081ed0cf935e0411293a465efd4d500704072cdb039778a2ab8736190c7", size = 76503, upload-time = "2025-03-12T22:12:48.106Z" }, ] -[[package]] -name = "setuptools" -version = "82.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.50" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/b0/a9d19b43f38f878b1278bca5b00b909f7540d41494396dd2561f9ad0956d/sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb", size = 2159807, upload-time = "2026-05-24T19:27:53.086Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/191dd58a248fd2cfd4780fa82c375c505e4ad98c8b522fa69ec492130d77/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89", size = 3343358, upload-time = "2026-05-24T20:09:29.279Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2b/514fce8a7df81cf5bad7ff7865de7ac0c5776a38cc043475c4703eb7fe8b/sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600", size = 3357994, upload-time = "2026-05-24T20:17:13.495Z" }, - { url = "https://files.pythonhosted.org/packages/35/a6/a0e283f5494f92b0d77e319ff77e437b1ffe4a051ba67c81d53234825475/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e", size = 3289399, upload-time = "2026-05-24T20:09:32.239Z" }, - { url = "https://files.pythonhosted.org/packages/b7/96/1b07325ba71752d6a028b77d07bed1483ad545f794e8b1dc89b3ba3b3c68/sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615", size = 3321216, upload-time = "2026-05-24T20:17:15.581Z" }, - { url = "https://files.pythonhosted.org/packages/ed/8e/bad6ed253e8a99edfc99af02f7173ec48a1d3ed1b9b35a1b8bc1700900cc/sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a", size = 2119194, upload-time = "2026-05-24T19:50:04.943Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2d/314a6690dda4b9cfc571eab1a63cf6fe6e1470aa3759ccda6aa016ee0f5a/sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7", size = 2146186, upload-time = "2026-05-24T19:50:06.74Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" }, - { url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" }, - { url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" }, - { url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" }, - { url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" }, - { url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" }, - { url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" }, - { url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" }, - { url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" }, - { url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" }, - { url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" }, - { url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" }, -] - -[package.optional-dependencies] -asyncio = [ - { name = "greenlet" }, -] - [[package]] name = "sse-starlette" version = "3.5.0" @@ -2620,71 +1407,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/93/181df62699d9b5aca1d87e5cfda131dac87e3cdaebf668662f7206ca3470/starlette-1.9.0-py3-none-any.whl", hash = "sha256:fd3f159936e882d06c1108f84688f7cda2cf85fc6a592a50edbd175e882c2ddc", size = 80335, upload-time = "2026-10-18T15:50:31.526Z" }, ] -[[package]] -name = "tenacity" -version = "9.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, -] - -[[package]] -name = "tiktoken" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/e5/5f3cb2159769d0f4324c0e9e87f9de3c4b1cd45848a96b2eb3566ad5ca77/tiktoken-0.13.0.tar.gz", hash = "sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1", size = 38986, upload-time = "2026-05-15T04:51:27.153Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/8e/144bde4e01df66b34bb865557c7cd754ed08b036217ebd79c9db5e9048a9/tiktoken-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791", size = 1034888, upload-time = "2026-05-15T04:50:31.579Z" }, - { url = "https://files.pythonhosted.org/packages/36/18/d4ac9d20956cdebca04841316660ed584c2fecdc2b81722a28bc7ad3b1e4/tiktoken-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b", size = 982970, upload-time = "2026-05-15T04:50:32.961Z" }, - { url = "https://files.pythonhosted.org/packages/74/ed/6bb8d05b9f731f749fee5c6f5ca63e981143c826a5985877330507bd13b7/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7", size = 1115741, upload-time = "2026-05-15T04:50:34.475Z" }, - { url = "https://files.pythonhosted.org/packages/34/de/2ca96b07a82d972b74fe4b46de055b79c904e45c7eab699354a0bfa697dc/tiktoken-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649", size = 1136523, upload-time = "2026-05-15T04:50:35.782Z" }, - { url = "https://files.pythonhosted.org/packages/ee/dc/9dafec002c2d4424378563cf4cf5c7fb93631d2a55013c8b87554ee4012c/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b", size = 1181954, upload-time = "2026-05-15T04:50:36.99Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d0/1f8578c45b2f24759b46f0b50d31878c63c73e6bf0f2227e10ec5c5408dc/tiktoken-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91", size = 1240069, upload-time = "2026-05-15T04:50:38.221Z" }, - { url = "https://files.pythonhosted.org/packages/aa/90/28d7f154888610aa9237e541986beb62b479df29d193a5a0617dbb1514d0/tiktoken-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41", size = 874748, upload-time = "2026-05-15T04:50:39.587Z" }, - { url = "https://files.pythonhosted.org/packages/9c/83/b096c859c2a47c11731bf2f5885f4028b809dfe2396582883eed9cae372f/tiktoken-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154", size = 1034228, upload-time = "2026-05-15T04:50:40.988Z" }, - { url = "https://files.pythonhosted.org/packages/53/61/c68e123b6d753e3fc2751e9b18e732c9d8bf1e1926762e736eee935d931c/tiktoken-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545", size = 982978, upload-time = "2026-05-15T04:50:42.195Z" }, - { url = "https://files.pythonhosted.org/packages/ef/8b/96cc178cc584e65d363134500f297790b06cd48cdeb1e8fcf7bbe60f4715/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2", size = 1116355, upload-time = "2026-05-15T04:50:43.564Z" }, - { url = "https://files.pythonhosted.org/packages/86/f5/bab735d2c72ea55404b295d02d092644eb5f7cc6205e34d35eb9abfb9ab2/tiktoken-0.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf", size = 1135772, upload-time = "2026-05-15T04:50:44.782Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b9/6de04ebdf904edfaad87788011b3735087a0c9ea671b9027e1e4e965e8c8/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486", size = 1182415, upload-time = "2026-05-15T04:50:46.422Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9c/470a05f3b1caf038f44880e334d47ab674e0c80d514c66b375d14d5afa10/tiktoken-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615", size = 1239879, upload-time = "2026-05-15T04:50:48.052Z" }, - { url = "https://files.pythonhosted.org/packages/42/a6/c1936d16055436cb32e6c6128d68629622e00f4768562f55653752d34768/tiktoken-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7", size = 874829, upload-time = "2026-05-15T04:50:49.202Z" }, - { url = "https://files.pythonhosted.org/packages/d6/07/acb5992c3772b5a36284f742cfb7a5895aa4471d1848ac31464ad50d7fdf/tiktoken-0.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67", size = 1033600, upload-time = "2026-05-15T04:50:50.4Z" }, - { url = "https://files.pythonhosted.org/packages/14/e9/742e9aec30f59b9f161f7ff7cd072e02ea836c9e1c0854a8076dfcd40d5c/tiktoken-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a", size = 982516, upload-time = "2026-05-15T04:50:52.03Z" }, - { url = "https://files.pythonhosted.org/packages/72/74/ca1541b053e7648254d2e4b42a253e1bb4359f2c91a0a8d49228c794e1a0/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d", size = 1115518, upload-time = "2026-05-15T04:50:53.543Z" }, - { url = "https://files.pythonhosted.org/packages/46/e3/93825eaf5a4a504795b787e5d5dea07fbeb3dabf97aa7b450be8bde59c89/tiktoken-0.13.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce", size = 1136867, upload-time = "2026-05-15T04:50:55.191Z" }, - { url = "https://files.pythonhosted.org/packages/8c/46/002b68de6827091d5ae90b048f326e8aad8d953520950e5ce1508879414f/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2", size = 1181826, upload-time = "2026-05-15T04:50:56.296Z" }, - { url = "https://files.pythonhosted.org/packages/db/c6/d393e3185a276505182f7abd93fe714f3c444a2be9180798fa052347504e/tiktoken-0.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f", size = 1239489, upload-time = "2026-05-15T04:50:57.918Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4d/bc07d1f1635d4897a202acc0ae11c2886eaa7325c359ba4741b47bf8e225/tiktoken-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec", size = 873820, upload-time = "2026-05-15T04:50:59.528Z" }, - { url = "https://files.pythonhosted.org/packages/8c/93/0dd6adca026a616c3a92974566b43381eea4b475ce1f36c062b8271a9ac5/tiktoken-0.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaaaef47c2406277181d2086484c317bf7fc433e2d5d03ff94f56b0dcec87471", size = 1034977, upload-time = "2026-05-15T04:51:00.957Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5ec6e6bc5b30bed6d93f7f2162d8f6b32437b3ba27cb527cfe004f6109c9/tiktoken-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca8b310bd93b3772cb1b7922d915446864860f562bdfe4825c63a0aed3fb28cd", size = 983635, upload-time = "2026-05-15T04:51:02.629Z" }, - { url = "https://files.pythonhosted.org/packages/94/b0/c8ae9aff00d625c50659b4513e707a0462c4bf5d4d6cc1b802103225c02e/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:32e0c12305105002c047b3bb1070b0dd9a73b0cb3b2856a8972b810e7a4f5881", size = 1116036, upload-time = "2026-05-15T04:51:04.082Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ac/6a5dddd1d0a6018ecb389bd0353e6b4a515eb4d2286611bd0ace1937b9e1/tiktoken-0.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5ba5fd62507a932d1241346179e3b39bc7bf7408f03c272652d93b3bedf5db24", size = 1135544, upload-time = "2026-05-15T04:51:05.229Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b8/585032b4384b2f7dcdaddcb52865c83a701a420d09e3c2b4a2be1c450c57/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d108bc2d470fc53c8ecd24f2c0fd2b5f98c33e87cdb6aa2e9b8c5dced703d273", size = 1182217, upload-time = "2026-05-15T04:51:06.517Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b6/993ff1ded3958215fd341a847b8e5ffeb5de473f435296870d314fc91ac4/tiktoken-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb99cb5127449f58d0a2d5f5ccfb390d8dbdfd919c221246caaee29d8725ed51", size = 1239404, upload-time = "2026-05-15T04:51:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3d/fef7e06e3b33e7538db0ced734cf9fe23b6832d2ac4990c119c377aec55e/tiktoken-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:115c4f26ffa11caac8b54eea35c2ad38c612c20a48d35dd15d70a02ac6f51f58", size = 918686, upload-time = "2026-05-15T04:51:08.925Z" }, - { url = "https://files.pythonhosted.org/packages/c1/82/a7fc44582bc32ab00de988a2299bf77c077f59068b233109e34b7d6ca7e6/tiktoken-0.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:472527e9132952f2fbf77cd290658bacf003d4d5a3fabc18e5fbd407cbae4d9b", size = 1034454, upload-time = "2026-05-15T04:51:10.035Z" }, - { url = "https://files.pythonhosted.org/packages/37/d0/24d8a890c14f432a05cea669c17bebeaa99f96a7c79523b590f564246411/tiktoken-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e2f67d27c9626cdd25fe33d9313c5cdb3d8d82da646b68d6eb8e7e9c20e6448", size = 982976, upload-time = "2026-05-15T04:51:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/49/b7/2ab43f62788a9266187a9bfc1d3af99ad83e5eaa25fbef168a69cd5ad14f/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2b920b35805cd64585a37c3dc7ce65fba4d2d36016be01e1d7942482ca29093a", size = 1115526, upload-time = "2026-05-15T04:51:12.608Z" }, - { url = "https://files.pythonhosted.org/packages/64/39/1494321ed323ce7a14d88e3cd6cb9058625977df1c6961ddc492bd10a9f3/tiktoken-0.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:493af3aa28a4aaf2e3d2600a2ee717252c9bf5ab38fff94eb5a02db5ab77e5ad", size = 1136466, upload-time = "2026-05-15T04:51:13.926Z" }, - { url = "https://files.pythonhosted.org/packages/96/d9/dfd086aa2d918c563a140720e0ce296cada1634efd2783d5cf51e05f984e/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6644c9c2b5cf3916f5a3641d7d12fdb3f006a7b3d9ff6acdaec44e29ab1ff91e", size = 1181863, upload-time = "2026-05-15T04:51:15.025Z" }, - { url = "https://files.pythonhosted.org/packages/2f/68/a18b4f307086954fdae32714cb4f85562e34f9d34ab206e61f1816aa6018/tiktoken-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cb65b60b9408563676d874a3a4ee573370066f0dc4e29d84e82e989c6517424", size = 1239218, upload-time = "2026-05-15T04:51:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/16/5b/f2aa703a4fc5d2dff73460a7d46cc2f3f44aa0f3dd8eeb20d2a0ecf68862/tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07", size = 918110, upload-time = "2026-05-15T04:51:17.237Z" }, -] - -[[package]] -name = "tinytag" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/59/8a8cb2331e2602b53e4dc06960f57d1387a2b18e7efd24e5f9cb60ea4925/tinytag-2.2.1.tar.gz", hash = "sha256:e6d06610ebe7cd66fd07be2d3b9495914ab32654a5e47657bb8cd44c2484523c", size = 38214, upload-time = "2026-03-15T18:48:01.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/34/d50e338631baaf65ec5396e70085e5de0b52b24b28db1ffbc1c6e82190dc/tinytag-2.2.1-py3-none-any.whl", hash = "sha256:ed8b1e6d25367937e3321e054f4974f9abfde1a3e0a538824c87da377130c2b6", size = 32927, upload-time = "2026-03-15T18:47:59.613Z" }, -] - [[package]] name = "tomli" version = "2.4.1" @@ -2730,18 +1452,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] -[[package]] -name = "tqdm" -version = "4.68.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/05/0d5260f1f1ca784f4a4a0def9cbe6affe587f5b4025328d446c3d67765f4/tqdm-4.68.2.tar.gz", hash = "sha256:89c230e8dbc67c7615c142487111222f878c77427ea09549960f62389e258add", size = 171923, upload-time = "2026-06-09T13:26:42.539Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" }, -] - [[package]] name = "truststore" version = "0.10.5" @@ -2760,19 +1470,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] -[[package]] -name = "typing-inspect" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, -] - [[package]] name = "typing-inspection" version = "0.4.2" @@ -2785,15 +1482,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] -[[package]] -name = "urllib3" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, -] - [[package]] name = "uvicorn" version = "0.54.0" @@ -2833,149 +1521,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd1 wheels = [ { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, ] - -[[package]] -name = "wrapt" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/9f/06263fcd8ad6c405f05a3905fd7a84dd3176eb5ad46e44bccc0cd16348bb/wrapt-2.2.1.tar.gz", hash = "sha256:6744f504375775d7609c82c8d3d94af1c9a6f05586984536905908ba905277b9", size = 127620, upload-time = "2026-05-22T14:49:43.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/0c/bfae7b9401583b6d05938cd16dedc43857d96da2f8a3d50d78cc515bf6ff/wrapt-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ffad790d9d11d8ecf9f17c4bb671a5b4089e4d8b575c46c5129597f41f836b0", size = 81021, upload-time = "2026-05-22T14:48:00.313Z" }, - { url = "https://files.pythonhosted.org/packages/26/58/80f6a6599f933f4caecc1cb3ee88a04faf81e8b9bddbd6109c688dd63e0f/wrapt-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:628f5220c7a904d5fc78f7075c8d7871433eb6d035c94728a22fdf85f193d2a8", size = 81692, upload-time = "2026-05-22T14:48:01.49Z" }, - { url = "https://files.pythonhosted.org/packages/17/93/fb357cc7847c58a8ae790be718903afa81a28d23e642c843dc4129e8a0b2/wrapt-2.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:61acce4257a9883669703c525447c5b4c392edf0f987ae77ec32668440158f0e", size = 169364, upload-time = "2026-05-22T14:48:02.791Z" }, - { url = "https://files.pythonhosted.org/packages/aa/0b/76b601ee309a8bd556af0eecb184394c20b3c49aa9c8e085aa1ffacc2568/wrapt-2.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727ab4244622cd6ad2390f322642090c877d2e83a608d2653a7643ae5368d926", size = 171079, upload-time = "2026-05-22T14:48:04.22Z" }, - { url = "https://files.pythonhosted.org/packages/cd/87/ee3f32d5658e3e26d3e0e457922b47a36dd3bfbdfee7f97bb3e802344a66/wrapt-2.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03df9ebed4c73ab93fa8c07e3d41d818dfca1852b15731a3de59457b27814624", size = 160205, upload-time = "2026-05-22T14:48:05.553Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d0/ae2fd64277a67f5d7bffcf2d05eea1e476263fb2a072baf0b0129ab85984/wrapt-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0d9ff006f420b2ec8296aa56ade43ea7da3e997e85769f0aafc5e0661aacb710", size = 168922, upload-time = "2026-05-22T14:48:07.132Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f3/2d541a060c5bbafb9400bca4917e4d78bfd1f239f404782c86831a8f6b29/wrapt-2.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:844c858fc3bb7eacc0ba8efa904935d16aac6a4470948ad1e7e55c9f5a2a665f", size = 158388, upload-time = "2026-05-22T14:48:08.629Z" }, - { url = "https://files.pythonhosted.org/packages/1d/68/8d92c8800c57e93cb116ae9e9d6cbafc34fade5ee9f9107b6f203fb4dc35/wrapt-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87bacdaf225117a342a20d9c03438d701c02112f6e3f351ce9b7f32354f14797", size = 167682, upload-time = "2026-05-22T14:48:10.042Z" }, - { url = "https://files.pythonhosted.org/packages/30/72/83ea3790ea352439442349388e29ff07b76e0686265f9088bbb505d1608d/wrapt-2.2.1-cp312-cp312-win32.whl", hash = "sha256:2f8c90c8afde51969487be4e1343ae049b268854877d415c2510baf833775052", size = 77857, upload-time = "2026-05-22T14:48:11.782Z" }, - { url = "https://files.pythonhosted.org/packages/ef/cb/99450668dd3502d62a54a1c8aa56e44f34cb8c1261b381cfe2e7926c3b75/wrapt-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ce32763ac31ce94fe9aada947e479b1975012bff166da409b4b9e4e376cf7e5", size = 80825, upload-time = "2026-05-22T14:48:13.046Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3a/87512881be64e743f9ee4c66f4cbe8e884974bef2a5989af71f999653ac7/wrapt-2.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d1b4d0e0c2119587a31f5c029abd547e0c81d93b89d394566fe1588659eb579", size = 79087, upload-time = "2026-05-22T14:48:14.323Z" }, - { url = "https://files.pythonhosted.org/packages/88/d1/a1b08f8f4fac8cbb156fa51cf64ee2c7f7f74f9875ba3cf70b3c58368694/wrapt-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d2beb1c7cab10603aecdc42f8edd6ff013f9a32e4543474e38e6b77ce9975aeb", size = 80831, upload-time = "2026-05-22T14:48:15.598Z" }, - { url = "https://files.pythonhosted.org/packages/54/ce/57890814991446a845e09b3445ce8b694f27eb0577004f2c2a36a9772ed4/wrapt-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0cb7e4dd71f4c32e5e84843cd3c4cd65dda034314004bbe1d7f99af2426ab80", size = 81375, upload-time = "2026-05-22T14:48:17.071Z" }, - { url = "https://files.pythonhosted.org/packages/38/65/08d7a6c76ac4493bdb668205ee9c1de1bd5daca61717c3e9aa49b4c01499/wrapt-2.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95821352042722cd9f1108874579a47989d0a7e12a37d87d2fc4af20fd99ab8a", size = 167417, upload-time = "2026-05-22T14:48:18.303Z" }, - { url = "https://files.pythonhosted.org/packages/62/ce/f1ccbee7a1bfe5cdc6b3da6bab4b45713d628b9294da32a39f563d648140/wrapt-2.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abd621552ede77c4c69be7fac44ba911225b0c812b6ba604e5964cf98085b474", size = 166948, upload-time = "2026-05-22T14:48:19.768Z" }, - { url = "https://files.pythonhosted.org/packages/86/2a/f85d48d1cd4869aee6704028d257d740a47c1c467b457ce396b4b5b55d07/wrapt-2.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e3677c7146ce694874941ba82b57092cc4875445aadf29d72807351023105143", size = 158148, upload-time = "2026-05-22T14:48:21.96Z" }, - { url = "https://files.pythonhosted.org/packages/fe/5c/93939ad11d4a12358ab1aab219a2ef5efa5612e0db6b9fc65af8af1a891b/wrapt-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9a5934eaea872e17936b5f45501eba5ab0bce9a74122e172b663d7c28c459c4a", size = 165905, upload-time = "2026-05-22T14:48:23.373Z" }, - { url = "https://files.pythonhosted.org/packages/e0/22/b8c2aa89862ff58605934d7abf4b70e6a5a1c33df96656f49035ccdf1c8a/wrapt-2.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f5b9daf6b629fce418e0cc3dd0436eac045188fa35deadb7a7f3941d5b8203f9", size = 156712, upload-time = "2026-05-22T14:48:24.767Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/bf00a7b02239c12bb02ddcc3c0b971bfcc36e578c5a44f1ccfef5b458545/wrapt-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f53ac9f3ef573326d009ed809beff4efcac6451931c2b8132586da4b9e53ff31", size = 166560, upload-time = "2026-05-22T14:48:26.83Z" }, - { url = "https://files.pythonhosted.org/packages/fe/93/6390ca9c5b787683cef588d04f57c8d41b9a2323b5597a65f18638c90ef2/wrapt-2.2.1-cp313-cp313-win32.whl", hash = "sha256:1ffa9cfd4bdb581539951b14ae661ff20ed0c3599b3e911a131ee0ec5ac11337", size = 77817, upload-time = "2026-05-22T14:48:28.221Z" }, - { url = "https://files.pythonhosted.org/packages/97/73/ce10f0e71c0cfaa1a65faadb8efd4852028b3bb9ba28932b8889df769d38/wrapt-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:368eac1e20fd0bb03dd3cc42bf9887154c3861b60989389ccb5fac032617d215", size = 80736, upload-time = "2026-05-22T14:48:30.139Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4c/89f4a6818fafbbd840330e4fa3873073e1bfc166133a64cac7f8fde7a5e3/wrapt-2.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:c754dafdf5aaf0b401b644a90a30046929a0dd1a536e0ff0ec959a59155d9c7f", size = 79099, upload-time = "2026-05-22T14:48:31.405Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f2/9a8741c46f8c208ac0a45b25ba170bcb4fb72a2781d5fb97dbd7b6be73cb/wrapt-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ed928d0fda15fc0adc8d13305c8b3c0f2fba5b0669950c9e6d019d9162a3b3e8", size = 82802, upload-time = "2026-05-22T14:48:33.307Z" }, - { url = "https://files.pythonhosted.org/packages/9c/0d/e9c855716a3705eef1416456bdf062b60620726fdc59428ff670fc3c60dc/wrapt-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fafb4e739e43544d12cb4abd1605fd4683b6ca6a9ad682b7fd8f4d21973eafa8", size = 83329, upload-time = "2026-05-22T14:48:34.593Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d6/a88f1c13112b7831adac75cea65d8310e0d696d570c8961844c90a57b865/wrapt-2.2.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:74d6a0c31472fe5d814917266b9f46495d7c61ed890af08b468acea92fb89a8d", size = 202937, upload-time = "2026-05-22T14:48:35.859Z" }, - { url = "https://files.pythonhosted.org/packages/42/65/e29d54aef06a4d898a5b8a25589a0b3769bde454f922fad8f6f89fbfb650/wrapt-2.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab5be648d5a0b86b7438864f8df3c705a65cef35a2fd3e5561e3e203167e0f27", size = 209997, upload-time = "2026-05-22T14:48:38.153Z" }, - { url = "https://files.pythonhosted.org/packages/2a/91/e4454263516cf0e12640912fbca9a83654e424f0a6ddb79f5cd7ce14bf33/wrapt-2.2.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d8f204c8e3a8bf9ece17e0a83d137fd807440977f8a5e762d59306795011440", size = 194856, upload-time = "2026-05-22T14:48:39.69Z" }, - { url = "https://files.pythonhosted.org/packages/de/d0/fe0ee202286afdf4a7f77dd29f195703145764d572aec209c5086e57d924/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d047f6498c973874ba08ac3f97c69a2c4b2211c8de6f4c205f75cb1c9522596e", size = 205654, upload-time = "2026-05-22T14:48:43.456Z" }, - { url = "https://files.pythonhosted.org/packages/23/b6/87d860dfc6460c246af70b1fd5c8b76df77571b42a493459423ded94fd7d/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:7a4fdb9326aab4a5a477a1640e5ad786a8495901009d7e7b038371edd23a9d2b", size = 192206, upload-time = "2026-05-22T14:48:44.858Z" }, - { url = "https://files.pythonhosted.org/packages/df/46/3eea8cde077d985f239a38c0257087b8064fd9ee9b1a99e282d2c86da4ef/wrapt-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c8cc5094b08abeae52da9c73c8a32003623be691a5193df2f4e3eac3d557c394", size = 198428, upload-time = "2026-05-22T14:48:46.319Z" }, - { url = "https://files.pythonhosted.org/packages/18/dc/b927ee9c7fc67adc3a5658f246a0d275425eb840ba36e7b702e70f18bde8/wrapt-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:9907a4402ab6db12b7077a0ea5d7a4d028ecb22c8eee2b53527080d347cd1562", size = 79448, upload-time = "2026-05-22T14:48:47.901Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b3/fd30b473fe498c70e6b9a5f328b8d3fbaf1b8c3c481465f59724bba8eb70/wrapt-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:5590d63f5243251641cf543009b4c9314a79d0598fdb8a8e4cfc918494536c53", size = 83021, upload-time = "2026-05-22T14:48:49.201Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f3/96c39153a8737a6e9aa85adef254ac4195bea3f2d24efc60472ccc3c9e2e/wrapt-2.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c318a64b53d97b841d7b5e637517e50a27be64bc695128422953d4b21710954e", size = 80295, upload-time = "2026-05-22T14:48:50.479Z" }, - { url = "https://files.pythonhosted.org/packages/0a/a3/11d7f34ebbf3231bc907a3e6d5ee051b14d034c1bc7b65a97d5cc00516df/wrapt-2.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f56a647e4eaf5f0ca40330fb070f566bdf9f7b0db89a1af20d71c28dcd7a0ab", size = 80879, upload-time = "2026-05-22T14:48:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/13/3c/b74cfd984cef560b900fb1a727af20352d89e1f06bf2e1114dd3f00f5f5a/wrapt-2.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:64b7deeda4b70408e382328d8bbe52a256fe9bc63ae3db86d804608367e5422c", size = 81462, upload-time = "2026-05-22T14:48:53.18Z" }, - { url = "https://files.pythonhosted.org/packages/15/a3/7c8f704b8dc07dfe0a5d01c2edbfd88317aa8e5e3fa7c743eb7a085ae767/wrapt-2.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9cf53ba90717db2e292401de290776c498d4bbfb0d4a559ca2895db8b9dcb5c", size = 167251, upload-time = "2026-05-22T14:48:54.562Z" }, - { url = "https://files.pythonhosted.org/packages/80/85/a34d1888d97247da6c2ff6118c3a721c73ed8cc4dd198c00208bb73b6f80/wrapt-2.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf3638274ab9d9b724c9baa0b4c04e132cd6faefb78b4dd3dd1a02a4bdaad41e", size = 166316, upload-time = "2026-05-22T14:48:56.065Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d7/72ffaeb01eebc704afe3fb99e840480f4bda45f0fa66e3381b6a39251c8f/wrapt-2.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aed9658797d0b45d6c49adcfc6b41f66e6f2d0c6de3ec79e16cf4b1855df240f", size = 157952, upload-time = "2026-05-22T14:48:57.924Z" }, - { url = "https://files.pythonhosted.org/packages/24/5b/36f5d6b024e4edfdd90b140742d11ebcf7836daf5c9daf326c55c24db412/wrapt-2.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1d676ee388bc42a04d56dd7deb5605244dac2e35cc2fadbb43c9fa25bbd93508", size = 166130, upload-time = "2026-05-22T14:48:59.384Z" }, - { url = "https://files.pythonhosted.org/packages/81/06/9296d9e97bfdef5483dfcc859d57b095b257144b2bc5300ab521e06f4bc7/wrapt-2.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e395f7bc31851ef9b612050368cb446e9bc14cd7454b025018980349caf25ae5", size = 156604, upload-time = "2026-05-22T14:49:00.921Z" }, - { url = "https://files.pythonhosted.org/packages/53/37/16953929ed6776175720e58fc966e779926d8d71e2c7b2273230590ca71f/wrapt-2.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f1845c2a8cc1180ccccfa45785dd06f562730d19ef75be180334254012b6283", size = 166007, upload-time = "2026-05-22T14:49:02.332Z" }, - { url = "https://files.pythonhosted.org/packages/b9/73/20ee58c0612dae7c31131a7095345812ed2c7b389019e175f68cde34e5b4/wrapt-2.2.1-cp314-cp314-win32.whl", hash = "sha256:436addbc4bb4fc0a88c702577f51195d7d73683a7f3e0e5b253d8404d7847243", size = 78327, upload-time = "2026-05-22T14:49:03.722Z" }, - { url = "https://files.pythonhosted.org/packages/22/b3/ef7c3295d02e0448a71c639a36a057f46d524d057c9486291a7a3039e65c/wrapt-2.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:50972a1d974ea07725a7f6b1cec5f8759008afd030a0024843ebe7d52de47f2b", size = 81144, upload-time = "2026-05-22T14:49:05.093Z" }, - { url = "https://files.pythonhosted.org/packages/ac/dc/7bdf336953f99f4ceb0a584bb8870e42c8f26f93ea10c87834dad62f1668/wrapt-2.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:1c9934ea5d92957e3cd0adbc0845539dccfd62710ebe16195a8c66c53954db36", size = 79569, upload-time = "2026-05-22T14:49:06.413Z" }, - { url = "https://files.pythonhosted.org/packages/6a/6d/6dfae80150ff1919c356d1dd528f049bcdfaae29b4d284bc957e022caef4/wrapt-2.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17de18fc12cea55b8a9587314cb830573e37fb33b247a7515696350863714188", size = 82892, upload-time = "2026-05-22T14:49:07.925Z" }, - { url = "https://files.pythonhosted.org/packages/82/7b/4e34766a7d7804ffce9e71befe47e9b3225dc350c49c94493c4ab39fd3a5/wrapt-2.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9dec1aca52dddde7df94818310fa2fe79739c8f385b2014c4cb1035f5508199", size = 83333, upload-time = "2026-05-22T14:49:09.257Z" }, - { url = "https://files.pythonhosted.org/packages/9d/57/0b34db3e8de44ccfece62d7b337abd1631dd810f5adc5f3db571727836b5/wrapt-2.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:69f2e9244542cb34dd59c7f073445b9e54ad9f3fce8d93606c368a1b499fc413", size = 202899, upload-time = "2026-05-22T14:49:10.572Z" }, - { url = "https://files.pythonhosted.org/packages/e5/45/ac0c459f154b99d92789a6cba7ca727185b83513b986f8ec7fe2aacddcbf/wrapt-2.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d83966dc7f4f45e8b97b5933685ac2e6e67fc0e19246ea314bceb9a8970c956", size = 209986, upload-time = "2026-05-22T14:49:12.229Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e4/77e37ff33ad018fa81ade52c25fa327b80b56f81d734279a63614fcb4cbc/wrapt-2.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78b0aa6bfb7be8deed0ab23e7aa028cc5210c29bc2d32a04d52b50e517a7307e", size = 194893, upload-time = "2026-05-22T14:49:14.139Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9d/7ea651d1ab032fc5fa222fbec91d0f8a1397f6ae04ebb93fa7219aa921d7/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:05d5cb74d1b232ec8cfa130a8f900708699ff2491d97b8f85a4cdc5996294b85", size = 205636, upload-time = "2026-05-22T14:49:15.714Z" }, - { url = "https://files.pythonhosted.org/packages/09/af/8e88031a701275b9085c54e64bc88c0b1cd55c77eadd400691c371cd76c4/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f6518b94edb9150452e9aba08027d4cc293433753ec1fbefb4629a21cbc74181", size = 192267, upload-time = "2026-05-22T14:49:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a8/e657ca876b06710194f243d81c4b0896ade646e244bdbec2d87c8c56a8bd/wrapt-2.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed55af48b3eb28f43228ca2306788892bcb629eb2b5c4876e2a3659872c2f17a", size = 198378, upload-time = "2026-05-22T14:49:18.785Z" }, - { url = "https://files.pythonhosted.org/packages/c8/59/822efe4ea722a3961331bfa35b7d90937790d2c20f0616de1997ccc3aebd/wrapt-2.2.1-cp314-cp314t-win32.whl", hash = "sha256:2e08688ab16525897da6589d56d0aebaf417bbe91c2d8e3b96203b1efa596e85", size = 80226, upload-time = "2026-05-22T14:49:20.264Z" }, - { url = "https://files.pythonhosted.org/packages/ab/31/2a7dc5f6abb2fca0b6e1610e120419f603650aceb4f1d3ac4cae0354e162/wrapt-2.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fd0135d34387f5fd087d9be368ea77ea89cf2451dc1cd1c622d35021bcb3ab50", size = 83835, upload-time = "2026-05-22T14:49:21.634Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c0/782b86e28d1ceebeb74cccea12d2cd3d2ba0bd68e3dec20b1bc5873f6127/wrapt-2.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:f70db64e8266d7c45d3b735f2e08eeb434b5e03da9a479ae42b2e2e486a21a00", size = 80722, upload-time = "2026-05-22T14:49:23.59Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/29ac9daf11a86c22a8c38cd9236c62928ccae83f7ceb06bd3b0467cf9d05/wrapt-2.2.1-py3-none-any.whl", hash = "sha256:3aafea2975caef8ca49400640dde02cc7426e798f24870ed01f490bc3cffd32f", size = 61000, upload-time = "2026-05-22T14:49:41.593Z" }, -] - -[[package]] -name = "yarl" -version = "1.24.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, - { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, - { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, - { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, - { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, - { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, - { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, - { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, - { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, - { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, - { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, - { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, - { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, - { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, - { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, - { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, - { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, - { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, - { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, - { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, - { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, - { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, - { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, - { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, - { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, - { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, - { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, - { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, - { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, - { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, - { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, - { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, - { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, - { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, - { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, - { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, - { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, - { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, - { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, - { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, -]