From 009626b4150713ddbdde47d4cefd028077809075 Mon Sep 17 00:00:00 2001 From: ActivePeter <1020401660@qq.com> Date: Fri, 3 Jul 2026 14:06:08 +0800 Subject: [PATCH 1/2] feat(benchmarks): add TeleFuser AIPerf video benchmark support --- .../baseline/diffusers_wan_i2v/README.md | 74 ++ .../diffusers_wan_i2v/benchmark_contract.yaml | 40 + .../configs/video_generation_compare.yaml | 54 ++ .../configs/video_generation_e2e.yaml | 54 ++ .../diffusers_wan_i2v/scripts/run_service.sh | 11 + .../scripts/run_video_bench.sh | 23 + .../baseline/diffusers_wan_i2v/service.py | 473 ++++++++++ benchmarks/telefuser_aiperf/README.md | 110 +++ .../telefuser_aiperf/benchmark_contract.yaml | 40 + .../configs/video_generation_e2e.yaml | 53 ++ .../configs/video_generation_quick.yaml | 32 + .../configs/video_generation_rate.yaml | 38 + ...deo_generation_wan21_i2v_480p_compare.yaml | 53 ++ .../telefuser_aiperf/data/video_prompts.jsonl | 5 + .../scripts/run_stream_bench.py | 872 ++++++++++++++++++ .../scripts/run_stream_bench.sh | 42 + .../scripts/run_video_bench.sh | 28 + docs/en/benchmark_aiperf.md | 105 +++ docs/en/index.md | 1 + docs/zh/benchmark_aiperf.md | 605 ++++++++++++ docs/zh/benchmark_aiperf_design.md | 684 ++++++++++++++ docs/zh/index.md | 2 + examples/lingbot/stream_lingbot_world_fast.py | 9 +- .../wan21_14b_image_to_video_480p_service.py | 177 ++++ mkdocs.yml | 4 + pyproject.toml | 6 + telefuser/models/__init__.py | 30 +- .../pipelines/lingbot_world_fast/pipeline.py | 6 +- telefuser/service/api/openai/adapter.py | 13 +- telefuser/service/core/pipeline_runner.py | 54 +- telefuser/service/main.py | 2 + telefuser/webrtc_ice.py | 67 ++ tests/unit/openai/test_adapter.py | 15 + tests/unit/service/test_pipeline_runner.py | 31 + 34 files changed, 3782 insertions(+), 31 deletions(-) create mode 100644 benchmarks/baseline/diffusers_wan_i2v/README.md create mode 100644 benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml create mode 100644 benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml create mode 100644 benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_e2e.yaml create mode 100644 benchmarks/baseline/diffusers_wan_i2v/scripts/run_service.sh create mode 100644 benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh create mode 100644 benchmarks/baseline/diffusers_wan_i2v/service.py create mode 100644 benchmarks/telefuser_aiperf/README.md create mode 100644 benchmarks/telefuser_aiperf/benchmark_contract.yaml create mode 100644 benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml create mode 100644 benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml create mode 100644 benchmarks/telefuser_aiperf/configs/video_generation_rate.yaml create mode 100644 benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml create mode 100644 benchmarks/telefuser_aiperf/data/video_prompts.jsonl create mode 100644 benchmarks/telefuser_aiperf/scripts/run_stream_bench.py create mode 100644 benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh create mode 100755 benchmarks/telefuser_aiperf/scripts/run_video_bench.sh create mode 100644 docs/en/benchmark_aiperf.md create mode 100644 docs/zh/benchmark_aiperf.md create mode 100644 docs/zh/benchmark_aiperf_design.md create mode 100644 examples/wan_video/wan21_14b_image_to_video_480p_service.py create mode 100644 telefuser/webrtc_ice.py create mode 100644 tests/unit/service/test_pipeline_runner.py diff --git a/benchmarks/baseline/diffusers_wan_i2v/README.md b/benchmarks/baseline/diffusers_wan_i2v/README.md new file mode 100644 index 0000000..def692a --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/README.md @@ -0,0 +1,74 @@ +# Diffusers Wan2.1 I2V Baseline + +This directory contains a standalone benchmark baseline for the official +`Wan-AI/Wan2.1-I2V-14B-480P-Diffusers` pipeline. + +The goal is to compare: + +- the same model +- the same I2V workload +- the same async `/v1/videos` HTTP semantics +- while changing only the inference framework + +Current fixed workload: + +- model: `Wan-AI/Wan2.1-I2V-14B-480P-Diffusers` +- resolution: `480p` +- effective size: `832x480` +- frames: `81` +- denoising steps: `40` +- guidance scale: `5.0` +- output fps: `16` + +## Layout + +- `service.py`: standalone FastAPI server +- `configs/`: AIPerf configs for the baseline +- `scripts/run_service.sh`: launch helper +- `scripts/run_video_bench.sh`: benchmark helper + +## Start The Service + +```bash +python3 benchmarks/baseline/diffusers_wan_i2v/service.py +``` + +Or: + +```bash +bash benchmarks/baseline/diffusers_wan_i2v/scripts/run_service.sh +``` + +Default address: + +- `http://127.0.0.1:8010` + +Health check: + +```bash +curl http://127.0.0.1:8010/v1/service/health +``` + +## Run AIPerf + +Install the vendored AIPerf first: + +```bash +pip install -e ./benchmarks/aiperf +``` + +Then run: + +```bash +bash benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh +``` + +This reuses the same prompt dataset as the TeleFuser benchmark: + +- `benchmarks/telefuser_aiperf/data/video_prompts.jsonl` + +## Notes + +- The service supports both `multipart/form-data` and JSON requests. +- `reference_url` is treated as a local filesystem path in the current benchmark dataset. +- Remote HTTP image URLs are intentionally rejected in this first baseline to keep the benchmark focused on inference rather than network fetch behavior. diff --git a/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml b/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml new file mode 100644 index 0000000..b206817 --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml @@ -0,0 +1,40 @@ +contract_version: v1 +name: diffusers_wan_i2v +mode: batch_video +implementation: diffusers +model_family: wan_video +model: Wan2.1-I2V-14B-480P-Diffusers +supported_tasks: + - i2v +transport: http +endpoint: + path: /v1/videos + protocol: openai_video_generation +request_encoding: + content_type: multipart/form-data + media_inputs: + first_image_path: input_reference + parameters: + prompt: prompt + negative_prompt: negative_prompt + seed: seed + size: size + seconds: seconds +result_delivery: + polling_path: /v1/videos/{id} + content_path: /v1/videos/{id}/content +workload: + resolution: 832x480 + frames: 81 + inference_steps: 40 + fps: 16 + guidance_scale: 5.0 +metrics: + - request_latency + - completion_latency + - download_latency + - request_throughput + - success_rate +artifacts: + config: benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml + dataset: benchmarks/telefuser_aiperf/data/video_prompts.jsonl diff --git a/benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml b/benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml new file mode 100644 index 0000000..bba583b --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=../../../aiperf/src/aiperf/config/schema/aiperf-config.schema.json + +schemaVersion: "2.0" + +randomSeed: 42 + +benchmark: + model: diffusers-wan21-i2v-480p + + endpoint: + url: ${DIFFUSERS_WAN_AIPERF_URL:http://127.0.0.1:8010} + type: video_generation + timeout: 7200.0 + downloadVideoContent: true + extra: + size: ${DIFFUSERS_WAN_AIPERF_SIZE:480p} + seconds: ${DIFFUSERS_WAN_AIPERF_SECONDS:5} + seed: ${DIFFUSERS_WAN_AIPERF_SEED:42} + model: ${DIFFUSERS_WAN_AIPERF_MODEL:Wan-AI/Wan2.1-I2V-14B-480P-Diffusers} + + tokenizer: + name: builtin + + dataset: + type: file + path: ./benchmarks/telefuser_aiperf/data/video_prompts.jsonl + format: single_turn + sampling: sequential + + warmup: + type: concurrency + concurrency: 1 + requests: 1 + excludeFromResults: true + + profiling: + type: concurrency + concurrency: ${DIFFUSERS_WAN_AIPERF_CONCURRENCY:1} + requests: ${DIFFUSERS_WAN_AIPERF_REQUESTS:2} + duration: ${DIFFUSERS_WAN_AIPERF_DURATION:7200} + gracePeriod: ${DIFFUSERS_WAN_AIPERF_GRACE_PERIOD:900} + + artifacts: + dir: ./artifacts/diffusers_wan_i2v/wan21_i2v_480p_compare + summary: [json] + records: [jsonl] + showTraceTiming: true + trace: true + + serverMetrics: + enabled: ${DIFFUSERS_WAN_AIPERF_SERVER_METRICS:false} + urls: + - ${DIFFUSERS_WAN_AIPERF_METRICS_URL:http://127.0.0.1:8010/metrics} + formats: [json, csv] diff --git a/benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_e2e.yaml b/benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_e2e.yaml new file mode 100644 index 0000000..1bc971d --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_e2e.yaml @@ -0,0 +1,54 @@ +# yaml-language-server: $schema=../../aiperf/src/aiperf/config/schema/aiperf-config.schema.json + +schemaVersion: "2.0" + +randomSeed: 42 + +benchmark: + model: diffusers-wan21-i2v-480p + + endpoint: + url: ${DIFFUSERS_WAN_AIPERF_URL:http://127.0.0.1:8010} + type: video_generation + timeout: 7200.0 + downloadVideoContent: true + extra: + size: ${DIFFUSERS_WAN_AIPERF_SIZE:480p} + seconds: ${DIFFUSERS_WAN_AIPERF_SECONDS:5} + seed: ${DIFFUSERS_WAN_AIPERF_SEED:42} + model: ${DIFFUSERS_WAN_AIPERF_MODEL:Wan-AI/Wan2.1-I2V-14B-480P-Diffusers} + + tokenizer: + name: builtin + + dataset: + type: file + path: ./benchmarks/telefuser_aiperf/data/video_prompts.jsonl + format: single_turn + sampling: sequential + + warmup: + type: concurrency + concurrency: 1 + requests: 1 + excludeFromResults: true + + profiling: + type: concurrency + concurrency: ${DIFFUSERS_WAN_AIPERF_CONCURRENCY:2} + requests: ${DIFFUSERS_WAN_AIPERF_REQUESTS:6} + duration: ${DIFFUSERS_WAN_AIPERF_DURATION:7200} + gracePeriod: ${DIFFUSERS_WAN_AIPERF_GRACE_PERIOD:900} + + artifacts: + dir: ./artifacts/diffusers_wan_i2v/video_e2e + summary: [json] + records: [jsonl] + showTraceTiming: true + trace: true + + serverMetrics: + enabled: ${DIFFUSERS_WAN_AIPERF_SERVER_METRICS:true} + urls: + - ${DIFFUSERS_WAN_AIPERF_METRICS_URL:http://127.0.0.1:8010/metrics} + formats: [json, csv] diff --git a/benchmarks/baseline/diffusers_wan_i2v/scripts/run_service.sh b/benchmarks/baseline/diffusers_wan_i2v/scripts/run_service.sh new file mode 100644 index 0000000..e760bc3 --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/scripts/run_service.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "${ROOT_DIR}" + +PYTHON_BIN="${PYTHON_BIN:-python3}" +SERVICE_HOST="${DIFFUSERS_WAN_I2V_HOST:-127.0.0.1}" +SERVICE_PORT="${DIFFUSERS_WAN_I2V_PORT:-8010}" + +exec "${PYTHON_BIN}" benchmarks/baseline/diffusers_wan_i2v/service.py diff --git a/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh b/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh new file mode 100644 index 0000000..16a19f2 --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "${ROOT_DIR}" + +CONFIG_PATH="${1:-benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_e2e.yaml}" +SERVER_URL="${DIFFUSERS_WAN_AIPERF_URL:-http://127.0.0.1:8010}" +HEALTH_URL="${DIFFUSERS_WAN_AIPERF_HEALTH_URL:-${SERVER_URL}/v1/service/health}" +AIPERF_BIN="${AIPERF_BIN:-aiperf}" + +if ! command -v "${AIPERF_BIN}" >/dev/null 2>&1; then + echo "aiperf is not installed. Set AIPERF_BIN or run: pip install -e ./benchmarks/aiperf" >&2 + exit 1 +fi + +if command -v curl >/dev/null 2>&1; then + echo "Checking baseline service health: ${HEALTH_URL}" + curl --fail --silent --show-error "${HEALTH_URL}" >/dev/null +fi + +echo "Running AIPerf with config: ${CONFIG_PATH}" +"${AIPERF_BIN}" profile --config "${CONFIG_PATH}" diff --git a/benchmarks/baseline/diffusers_wan_i2v/service.py b/benchmarks/baseline/diffusers_wan_i2v/service.py new file mode 100644 index 0000000..f8a3421 --- /dev/null +++ b/benchmarks/baseline/diffusers_wan_i2v/service.py @@ -0,0 +1,473 @@ +from __future__ import annotations + +import base64 +import binascii +import io +import os +import threading +import time +import uuid +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import torch +import uvicorn +from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile +from fastapi.responses import FileResponse, JSONResponse, Response +from PIL import Image + +from diffusers import AutoencoderKLWan, WanImageToVideoPipeline +from diffusers.utils import export_to_video +from transformers import CLIPVisionModel + +DEFAULT_MODEL_ID = "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers" +DEFAULT_OUTPUT_DIR = "artifacts/diffusers_wan_i2v" +DEFAULT_HOST = "127.0.0.1" +DEFAULT_PORT = 8010 +DEFAULT_GUIDANCE_SCALE = 5.0 +DEFAULT_NUM_FRAMES = 81 +DEFAULT_NUM_INFERENCE_STEPS = 40 +DEFAULT_TARGET_FPS = 16 +DEFAULT_HEIGHT = 480 +DEFAULT_WIDTH = 832 +DEFAULT_TIMEOUT_SECONDS = 7200.0 +VIDEO_STATUS_QUEUED = "queued" +VIDEO_STATUS_GENERATING = "generating" +VIDEO_STATUS_COMPLETED = "completed" +VIDEO_STATUS_FAILED = "failed" +VIDEO_STATUS_CANCELLED = "cancelled" + + +def _coerce_env_int(name: str, default: int) -> int: + value = os.environ.get(name) + if value is None or value == "": + return default + return int(value) + + +def _coerce_env_float(name: str, default: float) -> float: + value = os.environ.get(name) + if value is None or value == "": + return default + return float(value) + + +@dataclass +class ServiceConfig: + model_id: str = os.environ.get("DIFFUSERS_WAN_I2V_MODEL_ID", DEFAULT_MODEL_ID) + output_dir: Path = Path(os.environ.get("DIFFUSERS_WAN_I2V_OUTPUT_DIR", DEFAULT_OUTPUT_DIR)) + host: str = os.environ.get("DIFFUSERS_WAN_I2V_HOST", DEFAULT_HOST) + port: int = _coerce_env_int("DIFFUSERS_WAN_I2V_PORT", DEFAULT_PORT) + torch_dtype: torch.dtype = torch.bfloat16 + device: str = os.environ.get("DIFFUSERS_WAN_I2V_DEVICE", "cuda") + guidance_scale: float = _coerce_env_float("DIFFUSERS_WAN_I2V_GUIDANCE_SCALE", DEFAULT_GUIDANCE_SCALE) + num_frames: int = _coerce_env_int("DIFFUSERS_WAN_I2V_NUM_FRAMES", DEFAULT_NUM_FRAMES) + num_inference_steps: int = _coerce_env_int( + "DIFFUSERS_WAN_I2V_NUM_INFERENCE_STEPS", DEFAULT_NUM_INFERENCE_STEPS + ) + target_fps: int = _coerce_env_int("DIFFUSERS_WAN_I2V_TARGET_FPS", DEFAULT_TARGET_FPS) + width: int = _coerce_env_int("DIFFUSERS_WAN_I2V_WIDTH", DEFAULT_WIDTH) + height: int = _coerce_env_int("DIFFUSERS_WAN_I2V_HEIGHT", DEFAULT_HEIGHT) + timeout_seconds: float = _coerce_env_float("DIFFUSERS_WAN_I2V_TIMEOUT_SECONDS", DEFAULT_TIMEOUT_SECONDS) + + +@dataclass +class JobRecord: + job_id: str + prompt: str + model: str + size: str + seconds: int + status: str = VIDEO_STATUS_QUEUED + progress: int = 0 + created_at: int = field(default_factory=lambda: int(time.time())) + completed_at: int | None = None + output_path: str | None = None + error: dict[str, Any] | None = None + inference_time_s: float | None = None + peak_memory_mb: float | None = None + + def to_response(self, base_url: str) -> dict[str, Any]: + url = None + if self.status == VIDEO_STATUS_COMPLETED: + url = f"{base_url}/v1/videos/{self.job_id}/content" + return { + "id": self.job_id, + "object": "video", + "model": self.model, + "status": self.status, + "progress": self.progress, + "created_at": self.created_at, + "size": self.size, + "seconds": str(self.seconds), + "quality": "standard", + "url": url, + "completed_at": self.completed_at, + "error": self.error, + "file_path": self.output_path, + "inference_time_s": self.inference_time_s, + "peak_memory_mb": self.peak_memory_mb, + } + + +class DiffusersWanService: + """Standalone async HTTP service for the official Diffusers Wan2.1 I2V 480P pipeline.""" + + def __init__(self, config: ServiceConfig) -> None: + self.config = config + self.output_dir = config.output_dir + self.input_dir = self.output_dir / "inputs" + self.video_dir = self.output_dir / "videos" + self.output_dir.mkdir(parents=True, exist_ok=True) + self.input_dir.mkdir(parents=True, exist_ok=True) + self.video_dir.mkdir(parents=True, exist_ok=True) + self._jobs: dict[str, JobRecord] = {} + self._lock = threading.RLock() + self._pipeline = self._load_pipeline() + + def _load_pipeline(self) -> WanImageToVideoPipeline: + image_encoder = CLIPVisionModel.from_pretrained( + self.config.model_id, + subfolder="image_encoder", + torch_dtype=torch.float32, + ) + vae = AutoencoderKLWan.from_pretrained( + self.config.model_id, + subfolder="vae", + torch_dtype=torch.float32, + ) + pipe = WanImageToVideoPipeline.from_pretrained( + self.config.model_id, + vae=vae, + image_encoder=image_encoder, + torch_dtype=self.config.torch_dtype, + ) + pipe.to(self.config.device) + return pipe + + def create_job(self, *, prompt: str, model: str, size: str, seconds: int) -> JobRecord: + job_id = str(uuid.uuid4()) + job = JobRecord( + job_id=job_id, + prompt=prompt, + model=model or self.config.model_id, + size=size, + seconds=seconds, + ) + with self._lock: + self._jobs[job_id] = job + return job + + def get_job(self, job_id: str) -> JobRecord | None: + with self._lock: + return self._jobs.get(job_id) + + def list_jobs(self) -> list[JobRecord]: + with self._lock: + return list(self._jobs.values()) + + def cancel_job(self, job_id: str) -> JobRecord | None: + with self._lock: + job = self._jobs.get(job_id) + if job is None: + return None + if job.status in {VIDEO_STATUS_COMPLETED, VIDEO_STATUS_FAILED, VIDEO_STATUS_CANCELLED}: + return job + job.status = VIDEO_STATUS_CANCELLED + job.progress = 0 + job.completed_at = int(time.time()) + job.error = {"message": "Job cancelled by user"} + return job + + def _resolve_image_from_reference( + self, + *, + input_reference_file: UploadFile | None, + input_reference_value: str | None, + reference_url: str | None, + job_id: str, + ) -> Image.Image: + if input_reference_file is not None and input_reference_file.filename: + content = input_reference_file.file.read() + if not content: + raise HTTPException(status_code=400, detail="Uploaded input_reference is empty") + image = Image.open(io.BytesIO(content)).convert("RGB") + suffix = Path(input_reference_file.filename).suffix or ".png" + (self.input_dir / f"{job_id}{suffix}").write_bytes(content) + return image + + ref = input_reference_value or reference_url + if not ref: + raise HTTPException(status_code=400, detail="I2V benchmark service requires input_reference or reference_url") + + if ref.startswith("http://") or ref.startswith("https://"): + raise HTTPException( + status_code=400, + detail="Remote HTTP reference_url is not supported by this baseline service; use a local path or upload", + ) + + if ref.startswith("data:"): + try: + _, b64_data = ref.split(",", 1) + image_bytes = base64.b64decode(b64_data, validate=True) + except (ValueError, binascii.Error) as exc: + raise HTTPException(status_code=400, detail=f"Malformed data URL for input reference: {exc}") from exc + image = Image.open(io.BytesIO(image_bytes)).convert("RGB") + (self.input_dir / f"{job_id}.png").write_bytes(image_bytes) + return image + + path = Path(ref) + if not path.exists() or not path.is_file(): + raise HTTPException(status_code=400, detail=f"Reference image not found: {ref}") + return Image.open(path).convert("RGB") + + def _run_job(self, job_id: str, image: Image.Image, negative_prompt: str | None, seed: int | None) -> None: + job = self.get_job(job_id) + if job is None: + return + + with self._lock: + if job.status == VIDEO_STATUS_CANCELLED: + return + job.status = VIDEO_STATUS_GENERATING + job.progress = 5 + + output_path = self.video_dir / f"{job_id}.mp4" + start = time.perf_counter() + peak_memory_mb: float | None = None + + try: + if torch.cuda.is_available(): + torch.cuda.reset_peak_memory_stats() + + generator = None + if seed is not None: + generator = torch.Generator(device=self.config.device).manual_seed(int(seed)) + + result = self._pipeline( + image=image, + prompt=job.prompt, + negative_prompt=negative_prompt, + height=self.config.height, + width=self.config.width, + num_frames=self.config.num_frames, + num_inference_steps=self.config.num_inference_steps, + guidance_scale=self.config.guidance_scale, + generator=generator, + ) + frames = result.frames[0] + export_to_video(frames, str(output_path), fps=self.config.target_fps) + + elapsed = time.perf_counter() - start + if torch.cuda.is_available(): + peak_memory_mb = torch.cuda.max_memory_allocated() / (1024 * 1024) + + with self._lock: + if job.status == VIDEO_STATUS_CANCELLED: + output_path.unlink(missing_ok=True) + return + job.status = VIDEO_STATUS_COMPLETED + job.progress = 100 + job.completed_at = int(time.time()) + job.output_path = str(output_path) + job.inference_time_s = elapsed + job.peak_memory_mb = peak_memory_mb + except Exception as exc: + with self._lock: + job.status = VIDEO_STATUS_FAILED + job.progress = 0 + job.completed_at = int(time.time()) + job.error = {"message": str(exc)} + finally: + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + def submit_job( + self, + *, + prompt: str, + model: str | None, + size: str | None, + seconds: int | None, + seed: int | None, + negative_prompt: str | None, + input_reference_file: UploadFile | None, + input_reference_value: str | None, + reference_url: str | None, + ) -> JobRecord: + job = self.create_job( + prompt=prompt, + model=model or self.config.model_id, + size=size or f"{self.config.width}x{self.config.height}", + seconds=seconds or max(1, self.config.num_frames // self.config.target_fps), + ) + image = self._resolve_image_from_reference( + input_reference_file=input_reference_file, + input_reference_value=input_reference_value, + reference_url=reference_url, + job_id=job.job_id, + ) + worker = threading.Thread( + target=self._run_job, + args=(job.job_id, image, negative_prompt, seed), + daemon=True, + name=f"diffusers-wan-job-{job.job_id}", + ) + worker.start() + return job + + +service_config = ServiceConfig() +service = DiffusersWanService(service_config) +app = FastAPI( + title="Diffusers Wan2.1 I2V Benchmark Service", + description="Standalone async HTTP baseline for Wan2.1-I2V-14B-480P using official Diffusers.", + version="0.1.0", +) + + +@app.get("/v1/service/health") +async def health() -> dict[str, Any]: + return { + "status": "healthy", + "service": "diffusers-wan-i2v", + "model_id": service_config.model_id, + "device": service_config.device, + "workload": { + "width": service_config.width, + "height": service_config.height, + "num_frames": service_config.num_frames, + "num_inference_steps": service_config.num_inference_steps, + "guidance_scale": service_config.guidance_scale, + "fps": service_config.target_fps, + }, + } + + +@app.get("/metrics") +async def metrics() -> Response: + lines = [ + "# HELP diffusers_wan_jobs_total Number of tracked jobs", + "# TYPE diffusers_wan_jobs_total gauge", + f"diffusers_wan_jobs_total {len(service.list_jobs())}", + ] + completed = sum(1 for job in service.list_jobs() if job.status == VIDEO_STATUS_COMPLETED) + failed = sum(1 for job in service.list_jobs() if job.status == VIDEO_STATUS_FAILED) + lines.extend( + [ + "# HELP diffusers_wan_jobs_completed_total Number of completed jobs", + "# TYPE diffusers_wan_jobs_completed_total counter", + f"diffusers_wan_jobs_completed_total {completed}", + "# HELP diffusers_wan_jobs_failed_total Number of failed jobs", + "# TYPE diffusers_wan_jobs_failed_total counter", + f"diffusers_wan_jobs_failed_total {failed}", + ] + ) + return Response("\n".join(lines) + "\n", media_type="text/plain; version=0.0.4") + + +@app.post("/v1/videos") +async def create_video( + request: Request, + prompt: str | None = Form(None), + input_reference: UploadFile | None = File(None), + reference_url: str | None = Form(None), + model: str | None = Form(None), + seconds: int | None = Form(None), + size: str | None = Form(None), + seed: int | None = Form(None), + negative_prompt: str | None = Form(None), +) -> JSONResponse: + content_type = request.headers.get("content-type", "").lower() + + if "application/json" in content_type: + body = await request.json() + prompt_value = body.get("prompt") + input_reference_value = body.get("input_reference") + reference_url_value = body.get("reference_url") + model_value = body.get("model") + seconds_value = body.get("seconds") + size_value = body.get("size") + seed_value = body.get("seed") + negative_prompt_value = body.get("negative_prompt") + upload = None + else: + prompt_value = prompt + input_reference_value = None + reference_url_value = reference_url + model_value = model + seconds_value = seconds + size_value = size + seed_value = seed + negative_prompt_value = negative_prompt + upload = input_reference + + if not prompt_value: + raise HTTPException(status_code=422, detail="prompt is required") + + job = service.submit_job( + prompt=prompt_value, + model=model_value, + size=size_value, + seconds=seconds_value, + seed=seed_value, + negative_prompt=negative_prompt_value, + input_reference_file=upload, + input_reference_value=input_reference_value, + reference_url=reference_url_value, + ) + base_url = str(request.base_url).rstrip("/") + return JSONResponse(job.to_response(base_url)) + + +@app.get("/v1/videos") +async def list_videos(request: Request) -> dict[str, Any]: + base_url = str(request.base_url).rstrip("/") + jobs = sorted(service.list_jobs(), key=lambda item: item.created_at, reverse=True) + return { + "object": "list", + "data": [job.to_response(base_url) for job in jobs], + "has_more": False, + "first_id": jobs[0].job_id if jobs else None, + "last_id": jobs[-1].job_id if jobs else None, + } + + +@app.get("/v1/videos/{video_id}") +async def retrieve_video(video_id: str, request: Request) -> dict[str, Any]: + job = service.get_job(video_id) + if job is None: + raise HTTPException(status_code=404, detail=f"Video {video_id} not found") + base_url = str(request.base_url).rstrip("/") + return job.to_response(base_url) + + +@app.delete("/v1/videos/{video_id}") +async def delete_video(video_id: str, request: Request) -> dict[str, Any]: + job = service.cancel_job(video_id) + if job is None: + raise HTTPException(status_code=404, detail=f"Video {video_id} not found") + base_url = str(request.base_url).rstrip("/") + return job.to_response(base_url) + + +@app.get("/v1/videos/{video_id}/content") +async def get_video_content(video_id: str) -> FileResponse: + job = service.get_job(video_id) + if job is None: + raise HTTPException(status_code=404, detail=f"Video {video_id} not found") + if job.status != VIDEO_STATUS_COMPLETED or not job.output_path: + raise HTTPException(status_code=400, detail=f"Video {video_id} is not ready") + path = Path(job.output_path) + if not path.exists(): + raise HTTPException(status_code=404, detail=f"Video content not found: {job.output_path}") + return FileResponse(path, media_type="video/mp4", filename=path.name) + + +def main() -> None: + uvicorn.run(app, host=service_config.host, port=service_config.port, log_level="warning") + + +if __name__ == "__main__": + main() diff --git a/benchmarks/telefuser_aiperf/README.md b/benchmarks/telefuser_aiperf/README.md new file mode 100644 index 0000000..795ecd7 --- /dev/null +++ b/benchmarks/telefuser_aiperf/README.md @@ -0,0 +1,110 @@ +# TeleFuser AIPerf Benchmarks + +This directory contains TeleFuser-specific benchmark assets for running end-to-end video serving tests with AIPerf. + +Current scope: +- `telefuser serve` batch video generation over the OpenAI-compatible `/v1/videos` API +- `telefuser stream-serve` WebRTC end-to-end session benchmarking +- End-to-end latency, throughput, HTTP trace, frame-level latency, and optional server-metrics scraping + +## Layout + +- `configs/`: AIPerf YAML configs for batch video serving and JSON configs for stream benchmarking +- `data/`: Prompt files and control traces +- `scripts/`: Helper scripts for launching batch and stream benchmarks + +## Prerequisites + +1. Install TeleFuser and start a batch video server. +2. Install AIPerf from the vendored clone under `benchmarks/aiperf`. + +Example TeleFuser startup: + +```bash +telefuser serve \ + examples/wan_video/wan21_14b_image_to_video_h100.py \ + --port 8000 \ + --task i2v +``` + +Example AIPerf install: + +```bash +pip install -e ./benchmarks/aiperf +``` + +## Quick Start + +### Batch Video + +Run a small benchmark against TeleFuser `/v1/videos`: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh +``` + +This uses: +- `configs/video_generation_quick.yaml` +- file-backed prompts from `data/video_prompts.jsonl` +- TeleFuser server at `http://127.0.0.1:8000` + +### Stream Serve + +Start a TeleFuser stream server first: + +```bash +telefuser stream-serve \ + examples/lingbot/stream_lingbot_world_fast.py \ + -p 8088 \ + --skip-validation +``` + +Then run the WebRTC benchmark: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh +``` + +This uses: +- `configs/stream_lingbot_world_fast_quick.json` +- control trace from `data/stream_lingbot_controls.json` +- TeleFuser stream server at `http://127.0.0.1:8088` + +If no explicit ICE allowlist is provided, `run_stream_bench.py` will auto-detect a non-loopback local IP for WebRTC host candidate gathering. Only pin `--ice-host-ip` or `TELEFUSER_WEBRTC_ICE_HOST_IPS` when you need a specific address. + +The script records: +- SDP offer / answer RTT +- WebRTC connect latency +- first-frame latency +- steady-state stream FPS +- control acknowledge latency +- control-to-next-frame latency +- the helper raises the open-file limit to `8192` by default; override with `TELEFUSER_BENCH_NOFILE_LIMIT` if needed + +## Config Notes + +TeleFuser and AIPerf line up well for batch video serving: +- TeleFuser exposes `/v1/videos`, `/v1/videos/{id}`, and `/v1/videos/{id}/content` +- AIPerf `video_generation` already speaks the same async submit/poll/download flow + +The provided configs intentionally: +- use `endpoint.type: video_generation` +- enable `downloadVideoContent` only in the full E2E variant +- point `serverMetrics.urls` at `/v1/service/metrics` only when metrics scraping is desired +- do not enable AIPerf's built-in readiness probe, because TeleFuser batch video serving does not expose a stable `/v1/models` or chat-style readiness path for `video_generation` + +Instead, the helper script checks: + +```bash +curl http://127.0.0.1:8000/v1/service/health +``` + +## Stream Scope Notes + +The current stream benchmark is a TeleFuser-specific harness layered next to AIPerf, not inside AIPerf core yet. + +Reasons: +- `telefuser stream-serve` is WebRTC session based, not plain HTTP request / response +- world-model streaming needs frame-level and control-loop metrics +- current `LingBotWorldFastService` only allows one active session at a time, so the harness supports concurrency parameters but this pipeline is effectively single-session today +- `python3 scripts/remote_bench_sync.py stream-bootstrap` will auto-detect the usable model zoo root and create a repo-local `model_zoo` symlink on the remote host, so the default relative path works without extra manual env setup diff --git a/benchmarks/telefuser_aiperf/benchmark_contract.yaml b/benchmarks/telefuser_aiperf/benchmark_contract.yaml new file mode 100644 index 0000000..ef0110f --- /dev/null +++ b/benchmarks/telefuser_aiperf/benchmark_contract.yaml @@ -0,0 +1,40 @@ +contract_version: v1 +name: telefuser_aiperf_video +mode: batch_video +implementation: telefuser +model_family: wan_video +model: Wan2.1-I2V-14B-480P +supported_tasks: + - i2v +transport: http +endpoint: + path: /v1/videos + protocol: openai_video_generation +request_encoding: + content_type: multipart/form-data + media_inputs: + first_image_path: input_reference + parameters: + prompt: prompt + negative_prompt: negative_prompt + seed: seed + size: size + seconds: seconds +result_delivery: + polling_path: /v1/videos/{id} + content_path: /v1/videos/{id}/content +workload: + resolution: 832x480 + frames: 81 + inference_steps: 40 + fps: 16 + guidance_scale: 5.0 +metrics: + - request_latency + - completion_latency + - download_latency + - request_throughput + - success_rate +artifacts: + config: benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml + dataset: benchmarks/telefuser_aiperf/data/video_prompts.jsonl diff --git a/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml b/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml new file mode 100644 index 0000000..be4d057 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml @@ -0,0 +1,53 @@ +# yaml-language-server: $schema=../../aiperf/src/aiperf/config/schema/aiperf-config.schema.json + +schemaVersion: "2.0" + +randomSeed: 42 + +benchmark: + model: telefuser-video + + endpoint: + url: ${TELEFUSER_AIPERF_URL:http://127.0.0.1:8000} + type: video_generation + timeout: 3600.0 + downloadVideoContent: false + extra: + size: ${TELEFUSER_AIPERF_SIZE:1280x720} + seconds: ${TELEFUSER_AIPERF_SECONDS:4} + seed: ${TELEFUSER_AIPERF_SEED:1024} + + tokenizer: + name: builtin + + dataset: + type: file + path: ./benchmarks/telefuser_aiperf/data/video_prompts.jsonl + format: single_turn + sampling: sequential + + warmup: + type: concurrency + concurrency: 1 + requests: 1 + excludeFromResults: true + + profiling: + type: concurrency + concurrency: ${TELEFUSER_AIPERF_CONCURRENCY:2} + requests: ${TELEFUSER_AIPERF_REQUESTS:6} + duration: ${TELEFUSER_AIPERF_DURATION:7200} + gracePeriod: ${TELEFUSER_AIPERF_GRACE_PERIOD:900} + + artifacts: + dir: ./artifacts/telefuser_aiperf/video_e2e + summary: [json] + records: [jsonl] + showTraceTiming: true + trace: true + + serverMetrics: + enabled: ${TELEFUSER_AIPERF_SERVER_METRICS:true} + urls: + - ${TELEFUSER_AIPERF_METRICS_URL:http://127.0.0.1:8000/metrics} + formats: [json, csv] diff --git a/benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml b/benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml new file mode 100644 index 0000000..1d6057c --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=../../aiperf/src/aiperf/config/schema/aiperf-config.schema.json + +schemaVersion: "2.0" + +randomSeed: 42 + +benchmark: + model: telefuser-video + + endpoint: + url: ${TELEFUSER_AIPERF_URL:http://127.0.0.1:8000} + type: video_generation + timeout: 1800.0 + + tokenizer: + name: builtin + + dataset: + type: file + path: ./benchmarks/telefuser_aiperf/data/video_prompts.jsonl + format: single_turn + sampling: sequential + + profiling: + type: concurrency + concurrency: 1 + requests: 1 + + artifacts: + dir: ./artifacts/telefuser_aiperf/video_quick + summary: [json] + records: [jsonl] diff --git a/benchmarks/telefuser_aiperf/configs/video_generation_rate.yaml b/benchmarks/telefuser_aiperf/configs/video_generation_rate.yaml new file mode 100644 index 0000000..d6623f1 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/video_generation_rate.yaml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=../../aiperf/src/aiperf/config/schema/aiperf-config.schema.json + +schemaVersion: "2.0" + +randomSeed: 42 + +benchmark: + model: telefuser-video + + endpoint: + url: ${TELEFUSER_AIPERF_URL:http://127.0.0.1:8000} + type: video_generation + timeout: 3600.0 + extra: + size: ${TELEFUSER_AIPERF_SIZE:1024x576} + seconds: ${TELEFUSER_AIPERF_SECONDS:4} + + tokenizer: + name: builtin + + dataset: + type: file + path: ./benchmarks/telefuser_aiperf/data/video_prompts.jsonl + format: single_turn + sampling: sequential + + profiling: + type: poisson + rate: ${TELEFUSER_AIPERF_RATE:0.2} + concurrency: ${TELEFUSER_AIPERF_MAX_CONCURRENCY:4} + requests: ${TELEFUSER_AIPERF_REQUESTS:10} + gracePeriod: 300 + + artifacts: + dir: ./artifacts/telefuser_aiperf/video_rate + summary: [json] + records: [jsonl] + showTraceTiming: true diff --git a/benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml b/benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml new file mode 100644 index 0000000..d0b5f47 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml @@ -0,0 +1,53 @@ +# yaml-language-server: $schema=../../aiperf/src/aiperf/config/schema/aiperf-config.schema.json + +schemaVersion: "2.0" + +randomSeed: 42 + +benchmark: + model: telefuser-wan21-i2v-480p + + endpoint: + url: ${TELEFUSER_AIPERF_URL:http://127.0.0.1:8000} + type: video_generation + timeout: 7200.0 + downloadVideoContent: true + extra: + size: ${TELEFUSER_AIPERF_SIZE:480p} + seconds: ${TELEFUSER_AIPERF_SECONDS:5} + seed: ${TELEFUSER_AIPERF_SEED:42} + + tokenizer: + name: builtin + + dataset: + type: file + path: ./benchmarks/telefuser_aiperf/data/video_prompts.jsonl + format: single_turn + sampling: sequential + + warmup: + type: concurrency + concurrency: 1 + requests: 1 + excludeFromResults: true + + profiling: + type: concurrency + concurrency: ${TELEFUSER_AIPERF_CONCURRENCY:1} + requests: ${TELEFUSER_AIPERF_REQUESTS:2} + duration: ${TELEFUSER_AIPERF_DURATION:7200} + gracePeriod: ${TELEFUSER_AIPERF_GRACE_PERIOD:900} + + artifacts: + dir: ./artifacts/telefuser_aiperf/wan21_i2v_480p_compare + summary: [json] + records: [jsonl] + showTraceTiming: true + trace: true + + serverMetrics: + enabled: ${TELEFUSER_AIPERF_SERVER_METRICS:false} + urls: + - ${TELEFUSER_AIPERF_METRICS_URL:http://127.0.0.1:8000/v1/service/metrics} + formats: [json, csv] diff --git a/benchmarks/telefuser_aiperf/data/video_prompts.jsonl b/benchmarks/telefuser_aiperf/data/video_prompts.jsonl new file mode 100644 index 0000000..c080bf1 --- /dev/null +++ b/benchmarks/telefuser_aiperf/data/video_prompts.jsonl @@ -0,0 +1,5 @@ +{"text": "The character turns slightly toward the camera and breathes naturally, cinematic motion, consistent identity", "image": "/workspace/TeleFuser/examples/data/101235-video-720_0.png"} +{"text": "The subject begins walking forward with subtle parallax in the background, natural lighting, smooth motion", "image": "/workspace/TeleFuser/examples/data/101235-video-720_0.png"} +{"text": "A gentle push-in camera move with soft environmental motion, realistic dynamics, no sudden cuts", "image": "/workspace/TeleFuser/examples/data/101235-video-720_0.png"} +{"text": "The scene gains light wind and small secondary motion while preserving composition and subject details", "image": "/workspace/TeleFuser/examples/data/101235-video-720_0.png"} +{"text": "A short cinematic continuation with stable structure, coherent motion, and clean temporal consistency", "image": "/workspace/TeleFuser/examples/data/101235-video-720_0.png"} diff --git a/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py new file mode 100644 index 0000000..6b3bda8 --- /dev/null +++ b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py @@ -0,0 +1,872 @@ +#!/usr/bin/env python3 +"""Run an end-to-end WebRTC stream benchmark against ``telefuser stream-serve``. + +This benchmark is intentionally separate from the vendored AIPerf core because +TeleFuser world-model streaming uses WebRTC session lifecycles instead of plain +HTTP request/response flows. +""" + +from __future__ import annotations + +import argparse +import asyncio +import json +import os +import subprocess +import sys +import time +import urllib.error +import urllib.request +import uuid +from collections import deque +from dataclasses import asdict, dataclass, field +from pathlib import Path +from typing import Any + +try: + from aiortc import RTCConfiguration, RTCIceServer, RTCPeerConnection, RTCSessionDescription +except ImportError as exc: # pragma: no cover - runtime dependency check + RTCConfiguration = None + RTCIceServer = None + RTCPeerConnection = None + RTCSessionDescription = None + AIORTC_IMPORT_ERROR = exc +else: # pragma: no cover - exercised only when dependency exists + AIORTC_IMPORT_ERROR = None + +from telefuser.webrtc_ice import configure_ice_host_addresses + + +DEFAULT_CONTROL_TRACE = [ + {"delay_s": 1.0, "message": {"type": "control", "key": "ArrowUp", "action": "press"}}, + {"delay_s": 1.8, "message": {"type": "control", "key": "ArrowUp", "action": "release"}}, + {"delay_s": 2.8, "message": {"type": "control", "key": "ArrowLeft", "action": "press"}}, + {"delay_s": 3.6, "message": {"type": "control", "key": "ArrowLeft", "action": "release"}}, + {"delay_s": 4.6, "message": {"type": "control", "key": "ArrowRight", "action": "press"}}, + {"delay_s": 5.4, "message": {"type": "control", "key": "ArrowRight", "action": "release"}}, +] + + +@dataclass +class BenchConfig: + server_url: str = "http://127.0.0.1:8088" + offer_path: str = "/v1/stream/webrtc/offer" + delete_path_template: str = "/v1/stream/webrtc/{session_id}" + mode: str = "bidirectional" + task: str = "bidirectional" + prompt: str = "walk forward through the scene" + image_path: str | None = None + fps: int = 16 + session_count: int = 1 + warmup_sessions: int = 0 + session_duration_s: float = 12.0 + stagger_s: float = 0.0 + connect_timeout_s: float = 30.0 + frame_timeout_s: float = 60.0 + ice_gather_timeout_s: float = 5.0 + shutdown_timeout_s: float = 5.0 + receive_audio: bool = False + control_trace_path: str | None = None + ice_host_ips: list[str] | None = None + request_extra: dict[str, Any] = field(default_factory=dict) + artifacts_dir: str = "artifacts/telefuser_aiperf/stream_bench" + turn_url: str | None = None + turn_username: str | None = None + turn_credential: str | None = None + force_turn_relay: bool = False + print_events: bool = False + + +@dataclass +class ControlEventResult: + index: int + scheduled_delay_s: float + message: dict[str, Any] + sent_offset_s: float + ack_latency_ms: float | None = None + next_frame_latency_ms: float | None = None + + +@dataclass +class SessionResult: + logical_session_index: int + phase: str + mode: str + planned_session_id: str + session_id: str + success: bool = False + error: str | None = None + offer_rtt_ms: float | None = None + connected_latency_ms: float | None = None + first_frame_latency_ms: float | None = None + first_metadata_latency_ms: float | None = None + session_runtime_s: float | None = None + frames_received: int = 0 + metadata_messages: int = 0 + status_messages: int = 0 + done_received: bool = False + stream_fps: float | None = None + last_status_stage: str | None = None + control_events: list[ControlEventResult] = field(default_factory=list) + artifacts_event_file: str | None = None + + +def _parse_json_value(raw: str) -> Any: + try: + return json.loads(raw) + except json.JSONDecodeError: + return raw + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--config", help="Optional JSON config file") + parser.add_argument("--server-url", default=argparse.SUPPRESS, help="TeleFuser stream server base URL") + parser.add_argument("--mode", choices=("server_push", "bidirectional"), default=argparse.SUPPRESS) + parser.add_argument("--task", default=argparse.SUPPRESS, help="Request task field sent in the WebRTC offer body") + parser.add_argument("--prompt", default=argparse.SUPPRESS, help="Prompt sent in the initial offer body") + parser.add_argument("--image-path", default=argparse.SUPPRESS, help="Image path visible to the server process") + parser.add_argument("--fps", type=int, default=argparse.SUPPRESS, help="Requested stream FPS") + parser.add_argument("--session-count", type=int, default=argparse.SUPPRESS, help="Number of profiled sessions") + parser.add_argument("--warmup-sessions", type=int, default=argparse.SUPPRESS, help="Warmup sessions before profile") + parser.add_argument("--session-duration-s", type=float, default=argparse.SUPPRESS) + parser.add_argument("--stagger-s", type=float, default=argparse.SUPPRESS) + parser.add_argument("--connect-timeout-s", type=float, default=argparse.SUPPRESS) + parser.add_argument("--frame-timeout-s", type=float, default=argparse.SUPPRESS) + parser.add_argument("--ice-gather-timeout-s", type=float, default=argparse.SUPPRESS) + parser.add_argument("--shutdown-timeout-s", type=float, default=argparse.SUPPRESS) + parser.add_argument("--control-trace-path", default=argparse.SUPPRESS, help="JSON file with timed control events") + parser.add_argument( + "--ice-host-ip", + action="append", + default=argparse.SUPPRESS, + help="Allowlisted ICE host IP for candidate gathering. Repeat to allow multiple addresses.", + ) + parser.add_argument("--request-extra-json", default=argparse.SUPPRESS, help="Inline JSON merged into request body") + parser.add_argument("--artifacts-dir", default=argparse.SUPPRESS, help="Artifact root directory") + parser.add_argument("--turn-url", default=argparse.SUPPRESS, help="TURN server URL for WebRTC ICE") + parser.add_argument("--turn-username", default=argparse.SUPPRESS) + parser.add_argument("--turn-credential", default=argparse.SUPPRESS) + parser.add_argument("--receive-audio", action=argparse.BooleanOptionalAction, default=argparse.SUPPRESS) + parser.add_argument("--force-turn-relay", action=argparse.BooleanOptionalAction, default=argparse.SUPPRESS) + parser.add_argument("--print-events", action=argparse.BooleanOptionalAction, default=argparse.SUPPRESS) + return parser + + +def _load_config(args: argparse.Namespace) -> BenchConfig: + merged: dict[str, Any] = asdict(BenchConfig()) + if args.config: + file_data = json.loads(Path(args.config).read_text(encoding="utf-8")) + if not isinstance(file_data, dict): + raise ValueError(f"Config file must contain a JSON object: {args.config}") + merged.update(file_data) + + cli_values = vars(args).copy() + cli_values.pop("config", None) + request_extra_json = cli_values.pop("request_extra_json", None) + if request_extra_json is not None: + cli_values["request_extra"] = _parse_json_value(request_extra_json) + if "ice_host_ip" in cli_values: + cli_values["ice_host_ips"] = cli_values.pop("ice_host_ip") + + merged.update(cli_values) + request_extra = merged.get("request_extra") or {} + if not isinstance(request_extra, dict): + raise ValueError("request_extra must be a JSON object") + merged["request_extra"] = request_extra + return BenchConfig(**merged) + + +def _detect_default_ice_host_ips() -> list[str] | None: + """Return a small list of non-loopback host IPs for local ICE gathering.""" + + try: + result = subprocess.run( + ["hostname", "-I"], + check=True, + capture_output=True, + text=True, + ) + except Exception: + return None + + ips: list[str] = [] + for raw_ip in result.stdout.split(): + ip = raw_ip.strip() + if not ip or ip.startswith("127.") or ip == "::1": + continue + if ip not in ips: + ips.append(ip) + + return ips or None + + +def _build_rtc_configuration(config: BenchConfig): + if RTCConfiguration is None or RTCIceServer is None: + raise RuntimeError( + "aiortc is required for stream benchmarking. Install TeleFuser with `pip install -e \".[webrtc]\"`." + ) from AIORTC_IMPORT_ERROR + + ice_servers: list[Any] = [] + if config.turn_url: + ice_server_kwargs = {"urls": config.turn_url} + if config.turn_username: + ice_server_kwargs["username"] = config.turn_username + if config.turn_credential: + ice_server_kwargs["credential"] = config.turn_credential + ice_servers.append(RTCIceServer(**ice_server_kwargs)) + # aiortc 1.14.0 stalls on this benchmark's recvonly video + DataChannel + # flow when MAX_BUNDLE is forced. Use the default bundle policy instead. + return RTCConfiguration(iceServers=ice_servers or None) + + +async def _http_json_request(url: str, *, method: str, timeout_s: float, payload: dict | None = None) -> dict: + headers = {"Content-Type": "application/json"} + data = None if payload is None else json.dumps(payload).encode("utf-8") + request = urllib.request.Request(url=url, data=data, headers=headers, method=method) + + def _run() -> dict: + try: + with urllib.request.urlopen(request, timeout=timeout_s) as response: + body = response.read() + return json.loads(body.decode("utf-8")) if body else {} + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"HTTP {exc.code} {exc.reason}: {body}") from exc + except urllib.error.URLError as exc: + raise RuntimeError(f"Failed to reach {url}: {exc.reason}") from exc + + return await asyncio.to_thread(_run) + + +async def _wait_for_ice_complete(peer_connection, timeout_s: float) -> bool: + deadline = time.perf_counter() + timeout_s + while time.perf_counter() < deadline: + if peer_connection.iceGatheringState == "complete": + return True + await asyncio.sleep(0.05) + return peer_connection.iceGatheringState == "complete" + + +def _load_control_trace(path: str | None, mode: str) -> list[dict[str, Any]]: + if mode != "bidirectional": + return [] + if path is None: + return DEFAULT_CONTROL_TRACE + + payload = json.loads(Path(path).read_text(encoding="utf-8")) + events = payload.get("events", payload) if isinstance(payload, dict) else payload + if not isinstance(events, list): + raise ValueError(f"Control trace must be a list or {{\"events\": [...]}}: {path}") + normalised: list[dict[str, Any]] = [] + for idx, event in enumerate(events): + if not isinstance(event, dict): + raise ValueError(f"Control trace event #{idx} must be a JSON object") + delay_s = float(event.get("delay_s", 0.0)) + message = event.get("message") + if not isinstance(message, dict): + raise ValueError(f"Control trace event #{idx} is missing object field `message`") + normalised.append({"delay_s": delay_s, "message": message}) + return sorted(normalised, key=lambda item: item["delay_s"]) + + +def _percentile(values: list[float], percentile: float) -> float: + if not values: + raise ValueError("percentile() requires at least one value") + ordered = sorted(values) + if len(ordered) == 1: + return ordered[0] + position = (len(ordered) - 1) * percentile + lower = int(position) + upper = min(lower + 1, len(ordered) - 1) + fraction = position - lower + return ordered[lower] + (ordered[upper] - ordered[lower]) * fraction + + +def _summarise(values: list[float]) -> dict[str, float] | None: + if not values: + return None + return { + "count": float(len(values)), + "min": min(values), + "mean": sum(values) / len(values), + "p50": _percentile(values, 0.50), + "p90": _percentile(values, 0.90), + "p99": _percentile(values, 0.99), + "max": max(values), + } + + +class StreamBenchSession: + """Single WebRTC session benchmark runner.""" + + def __init__( + self, + *, + config: BenchConfig, + logical_session_index: int, + phase: str, + artifacts_dir: Path, + rtc_configuration, + control_trace: list[dict[str, Any]], + ) -> None: + self.config = config + self.logical_session_index = logical_session_index + self.phase = phase + self.artifacts_dir = artifacts_dir + self.rtc_configuration = rtc_configuration + self.control_trace = control_trace + self.planned_session_id = f"{phase}-{logical_session_index:03d}-{uuid.uuid4().hex[:10]}" + self.session_id = self.planned_session_id + self.result = SessionResult( + logical_session_index=logical_session_index, + phase=phase, + mode=config.mode, + planned_session_id=self.planned_session_id, + session_id=self.session_id, + ) + self._events: list[dict[str, Any]] = [] + self._pc = None + self._data_channel = None + self._session_started_at = 0.0 + self._offer_sent_at = 0.0 + self._first_frame_at: float | None = None + self._last_frame_at: float | None = None + self._first_metadata_at: float | None = None + self._connected_at: float | None = None + self._connected_event = asyncio.Event() + self._done_event = asyncio.Event() + self._state_monitor_task: asyncio.Task | None = None + self._track_tasks: list[asyncio.Task] = [] + self._control_sender_task: asyncio.Task | None = None + self._pending_control_ack_indices: deque[int] = deque() + self._pending_control_frame_indices: deque[int] = deque() + + def _record_event(self, event_type: str, **payload: Any) -> None: + event = { + "event": event_type, + "session_id": self.session_id, + "logical_session_index": self.logical_session_index, + "phase": self.phase, + "timestamp": time.time(), + } + event.update(payload) + self._events.append(event) + if self.config.print_events: + print(json.dumps(event, ensure_ascii=False)) + + def _build_request_body(self) -> dict[str, Any]: + request_options = dict(self.config.request_extra) + request_fps = int(request_options.get("fps", self.config.fps)) + body = { + "session_id": self.planned_session_id, + "sdp": self._pc.localDescription.sdp, + "type": self._pc.localDescription.type, + "task": self.config.task, + "prompt": self.config.prompt, + "fps": request_fps, + "config": dict(request_options), + } + body.update(request_options) + if self.config.image_path: + body["image_path"] = self.config.image_path + return body + + def _current_state_snapshot(self) -> dict[str, str]: + if self._pc is None: + return {} + return { + "signaling_state": self._pc.signalingState, + "ice_gathering_state": self._pc.iceGatheringState, + "ice_connection_state": self._pc.iceConnectionState, + "connection_state": self._pc.connectionState, + } + + async def _monitor_peer_states(self) -> None: + """Record state transitions even when aiortc callbacks stay quiet.""" + + last_snapshot: dict[str, str] | None = None + while not self._done_event.is_set(): + if self._pc is None: + return + snapshot = self._current_state_snapshot() + if snapshot and snapshot != last_snapshot: + self._record_event("peer_state_snapshot", **snapshot) + last_snapshot = snapshot + if snapshot.get("connection_state") == "connected": + return + await asyncio.sleep(0.5) + + async def _consume_video_track(self, track) -> None: + try: + while True: + await track.recv() + now = time.perf_counter() + self.result.frames_received += 1 + if self._first_frame_at is None: + self._first_frame_at = now + self.result.first_frame_latency_ms = (now - self._session_started_at) * 1000.0 + self._record_event("first_frame") + self._last_frame_at = now + if self._pending_control_frame_indices: + control_index = self._pending_control_frame_indices.popleft() + control_event = self.result.control_events[control_index] + if control_event.next_frame_latency_ms is None: + control_event.next_frame_latency_ms = (now - self._session_started_at) * 1000.0 - ( + control_event.sent_offset_s * 1000.0 + ) + except Exception as exc: + self._record_event("video_track_ended", detail=str(exc)) + + async def _consume_audio_track(self, track) -> None: + try: + while True: + await track.recv() + except Exception as exc: + self._record_event("audio_track_ended", detail=str(exc)) + + def _handle_datachannel_message(self, raw_message: str) -> None: + now = time.perf_counter() + self.result.metadata_messages += 1 + if self._first_metadata_at is None: + self._first_metadata_at = now + self.result.first_metadata_latency_ms = (now - self._session_started_at) * 1000.0 + + try: + payload = json.loads(raw_message) + except json.JSONDecodeError: + self._record_event("datachannel_message_invalid", raw_message=raw_message) + return + + message_type = payload.get("type") + if message_type == "done": + self.result.done_received = True + self._done_event.set() + self._record_event("done_message", payload=payload) + return + + data = payload.get("data") if isinstance(payload.get("data"), dict) else payload + stage = data.get("stage") + if stage is not None: + self.result.status_messages += 1 + self.result.last_status_stage = str(stage) + if self._pending_control_ack_indices and stage in {"control_state", "applying_direction_control"}: + control_index = self._pending_control_ack_indices.popleft() + control_event = self.result.control_events[control_index] + if control_event.ack_latency_ms is None: + control_event.ack_latency_ms = (now - self._session_started_at) * 1000.0 - ( + control_event.sent_offset_s * 1000.0 + ) + self._record_event("datachannel_message", payload=payload) + + async def _send_control_trace(self) -> None: + if self._data_channel is None: + return + + trace_started_at = time.perf_counter() + for event_index, entry in enumerate(self.control_trace): + deadline = trace_started_at + float(entry["delay_s"]) + delay = deadline - time.perf_counter() + if delay > 0: + await asyncio.sleep(delay) + if self._data_channel.readyState != "open": + self._record_event("control_trace_aborted", reason=f"datachannel_state={self._data_channel.readyState}") + return + message = dict(entry["message"]) + payload = json.dumps(message) + sent_at = time.perf_counter() + control_event = ControlEventResult( + index=event_index, + scheduled_delay_s=float(entry["delay_s"]), + message=message, + sent_offset_s=sent_at - self._session_started_at, + ) + self.result.control_events.append(control_event) + self._pending_control_ack_indices.append(event_index) + self._pending_control_frame_indices.append(event_index) + self._data_channel.send(payload) + self._record_event("control_sent", payload=message) + + def _register_peer_callbacks(self) -> None: + @self._pc.on("signalingstatechange") + async def _on_signalingstatechange() -> None: + self._record_event("signaling_state", state=self._pc.signalingState) + + @self._pc.on("icegatheringstatechange") + async def _on_icegatheringstatechange() -> None: + self._record_event("ice_gathering_state", state=self._pc.iceGatheringState) + + @self._pc.on("iceconnectionstatechange") + async def _on_iceconnectionstatechange() -> None: + state = self._pc.iceConnectionState + self._record_event("ice_connection_state", state=state) + if state in {"failed", "closed", "disconnected"}: + self._done_event.set() + + @self._pc.on("connectionstatechange") + async def _on_connectionstatechange() -> None: + state = self._pc.connectionState + self._record_event("connection_state", state=state) + if state == "connected" and self._connected_at is None: + self._connected_at = time.perf_counter() + self.result.connected_latency_ms = (self._connected_at - self._session_started_at) * 1000.0 + self._connected_event.set() + if state in {"failed", "closed", "disconnected"}: + self._done_event.set() + + @self._pc.on("track") + def _on_track(track) -> None: + self._record_event("remote_track", kind=track.kind) + if track.kind == "video": + self._track_tasks.append(asyncio.create_task(self._consume_video_track(track))) + elif track.kind == "audio": + self._track_tasks.append(asyncio.create_task(self._consume_audio_track(track))) + + def _create_bidirectional_channel(self) -> None: + self._data_channel = self._pc.createDataChannel("telefuser") + + @self._data_channel.on("open") + def _on_open() -> None: + self._record_event("datachannel_open") + if self._control_sender_task is None and self.control_trace: + self._control_sender_task = asyncio.create_task(self._send_control_trace()) + + @self._data_channel.on("message") + def _on_message(message) -> None: + if isinstance(message, bytes): + message = message.decode("utf-8", errors="replace") + self._handle_datachannel_message(str(message)) + + @self._data_channel.on("close") + def _on_close() -> None: + self._record_event("datachannel_close") + self._done_event.set() + + async def run(self) -> SessionResult: + self._session_started_at = time.perf_counter() + self._record_event("session_start", mode=self.config.mode) + + self._pc = RTCPeerConnection(configuration=self.rtc_configuration or RTCConfiguration()) + self._register_peer_callbacks() + self._state_monitor_task = asyncio.create_task(self._monitor_peer_states()) + + if self.config.mode == "bidirectional": + self._create_bidirectional_channel() + + self._pc.addTransceiver("video", direction="recvonly") + if self.config.receive_audio: + self._pc.addTransceiver("audio", direction="recvonly") + + try: + offer = await self._pc.createOffer() + await self._pc.setLocalDescription(offer) + ice_complete = await _wait_for_ice_complete(self._pc, self.config.ice_gather_timeout_s) + self._record_event("ice_gathering_complete", complete=ice_complete) + + request_body = self._build_request_body() + self._record_event("offer_request", body=request_body) + self._offer_sent_at = time.perf_counter() + answer = await _http_json_request( + f"{self.config.server_url.rstrip('/')}{self.config.offer_path}", + method="POST", + timeout_s=self.config.connect_timeout_s, + payload=request_body, + ) + answered_at = time.perf_counter() + self.result.offer_rtt_ms = (answered_at - self._offer_sent_at) * 1000.0 + self.session_id = str(answer.get("session_id", self.session_id)) + self.result.session_id = self.session_id + self._record_event("offer_answer", answer=answer) + await self._pc.setRemoteDescription(RTCSessionDescription(sdp=answer["sdp"], type=answer["type"])) + self._record_event("post_answer_state", **self._current_state_snapshot()) + + try: + await asyncio.wait_for(self._connected_event.wait(), timeout=self.config.connect_timeout_s) + except asyncio.TimeoutError: + raise TimeoutError( + f"WebRTC connection did not reach connected state within {self.config.connect_timeout_s:.1f}s" + ) + + try: + await asyncio.wait_for(self._done_event.wait(), timeout=self.config.session_duration_s) + except asyncio.TimeoutError: + self._record_event("session_duration_elapsed", duration_s=self.config.session_duration_s) + + if self._first_frame_at is None: + wait_remaining = self.config.frame_timeout_s + while self._first_frame_at is None and wait_remaining > 0: + started_wait = time.perf_counter() + try: + await asyncio.wait_for(self._done_event.wait(), timeout=min(wait_remaining, 0.25)) + except asyncio.TimeoutError: + pass + wait_remaining -= time.perf_counter() - started_wait + if self._first_frame_at is not None: + break + + if self._first_frame_at is not None: + self.result.success = True + if self._last_frame_at is not None and self._last_frame_at > self._first_frame_at: + self.result.stream_fps = (self.result.frames_received - 1) / (self._last_frame_at - self._first_frame_at) + elif self.result.error is None: + self.result.error = "No video frame received before session shutdown" + except Exception as exc: + self.result.error = str(exc) + self._record_event("session_error", error=str(exc)) + finally: + await self._shutdown() + finished_at = time.perf_counter() + self.result.session_runtime_s = finished_at - self._session_started_at + self._write_event_artifact() + return self.result + + async def _shutdown(self) -> None: + if self._data_channel is not None and self._data_channel.readyState == "open": + try: + self._data_channel.send(json.dumps({"type": "stop"})) + self._record_event("stop_sent") + except Exception as exc: + self._record_event("stop_send_failed", error=str(exc)) + + delete_url = f"{self.config.server_url.rstrip('/')}{self.config.delete_path_template.format(session_id=self.session_id)}" + try: + await _http_json_request(delete_url, method="DELETE", timeout_s=self.config.shutdown_timeout_s) + self._record_event("session_delete") + except Exception as exc: + self._record_event("session_delete_failed", error=str(exc)) + + if self._control_sender_task is not None and not self._control_sender_task.done(): + self._control_sender_task.cancel() + try: + await self._control_sender_task + except asyncio.CancelledError: + pass + except Exception: + pass + + if self._state_monitor_task is not None and not self._state_monitor_task.done(): + self._state_monitor_task.cancel() + try: + await self._state_monitor_task + except asyncio.CancelledError: + pass + except Exception: + pass + + for task in self._track_tasks: + if not task.done(): + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + except Exception: + pass + + if self._pc is not None: + try: + await self._pc.close() + except Exception as exc: + self._record_event("peer_close_failed", error=str(exc)) + + def _write_event_artifact(self) -> None: + events_dir = self.artifacts_dir / "events" + events_dir.mkdir(parents=True, exist_ok=True) + event_file = events_dir / f"{self.phase}_{self.logical_session_index:03d}_{self.session_id}.jsonl" + with event_file.open("w", encoding="utf-8") as handle: + for event in self._events: + handle.write(json.dumps(event, ensure_ascii=False) + "\n") + self.result.artifacts_event_file = str(event_file) + + +async def _run_phase( + *, + config: BenchConfig, + phase: str, + session_count: int, + artifacts_dir: Path, + rtc_configuration, + control_trace: list[dict[str, Any]], +) -> list[SessionResult]: + sessions: list[StreamBenchSession] = [] + tasks: list[asyncio.Task] = [] + for index in range(session_count): + session = StreamBenchSession( + config=config, + logical_session_index=index, + phase=phase, + artifacts_dir=artifacts_dir, + rtc_configuration=rtc_configuration, + control_trace=control_trace, + ) + sessions.append(session) + + async def _start_session(current_session: StreamBenchSession, delay_s: float) -> SessionResult: + if delay_s > 0: + await asyncio.sleep(delay_s) + return await current_session.run() + + tasks.append(asyncio.create_task(_start_session(session, config.stagger_s * index))) + return list(await asyncio.gather(*tasks)) + + +def _result_to_dict(result: SessionResult) -> dict[str, Any]: + payload = asdict(result) + payload["control_events"] = [asdict(event) for event in result.control_events] + return payload + + +def _build_summary( + *, + config: BenchConfig, + warmup_results: list[SessionResult], + profile_results: list[SessionResult], + started_at_iso: str, +) -> dict[str, Any]: + successful_profile_results = [result for result in profile_results if result.success] + all_control_events = [ + control_event + for result in profile_results + for control_event in result.control_events + if control_event.ack_latency_ms is not None or control_event.next_frame_latency_ms is not None + ] + summary = { + "started_at_utc": started_at_iso, + "config": asdict(config), + "warmup": { + "attempted_sessions": len(warmup_results), + "successful_sessions": sum(1 for result in warmup_results if result.success), + "failed_sessions": sum(1 for result in warmup_results if not result.success), + }, + "profile": { + "attempted_sessions": len(profile_results), + "successful_sessions": len(successful_profile_results), + "failed_sessions": len(profile_results) - len(successful_profile_results), + "success_rate": (len(successful_profile_results) / len(profile_results)) if profile_results else 0.0, + "metrics": { + "offer_rtt_ms": _summarise([result.offer_rtt_ms for result in profile_results if result.offer_rtt_ms]), + "connected_latency_ms": _summarise( + [result.connected_latency_ms for result in profile_results if result.connected_latency_ms] + ), + "first_frame_latency_ms": _summarise( + [result.first_frame_latency_ms for result in profile_results if result.first_frame_latency_ms] + ), + "first_metadata_latency_ms": _summarise( + [ + result.first_metadata_latency_ms + for result in profile_results + if result.first_metadata_latency_ms is not None + ] + ), + "stream_fps": _summarise([result.stream_fps for result in profile_results if result.stream_fps]), + "session_runtime_s": _summarise( + [result.session_runtime_s for result in profile_results if result.session_runtime_s] + ), + "frames_received": _summarise([float(result.frames_received) for result in profile_results]), + "control_ack_latency_ms": _summarise( + [event.ack_latency_ms for event in all_control_events if event.ack_latency_ms is not None] + ), + "control_to_next_frame_latency_ms": _summarise( + [ + event.next_frame_latency_ms + for event in all_control_events + if event.next_frame_latency_ms is not None + ] + ), + }, + }, + } + return summary + + +def _print_console_summary(summary: dict[str, Any]) -> None: + profile = summary["profile"] + metrics = profile["metrics"] + print( + f"Profile sessions: {profile['successful_sessions']}/{profile['attempted_sessions']} succeeded " + f"(success_rate={profile['success_rate']:.2%})" + ) + for metric_name in ( + "offer_rtt_ms", + "connected_latency_ms", + "first_frame_latency_ms", + "stream_fps", + "control_ack_latency_ms", + "control_to_next_frame_latency_ms", + ): + metric = metrics.get(metric_name) + if metric is None: + print(f"{metric_name}: n/a") + continue + print( + f"{metric_name}: count={int(metric['count'])} " + f"mean={metric['mean']:.2f} p50={metric['p50']:.2f} p90={metric['p90']:.2f} max={metric['max']:.2f}" + ) + + +async def _run_benchmark(config: BenchConfig) -> dict[str, Any]: + if RTCPeerConnection is None: + raise RuntimeError( + "aiortc is required for stream benchmarking. Install TeleFuser with `pip install -e \".[webrtc]\"`." + ) from AIORTC_IMPORT_ERROR + + resolved_ice_host_ips = config.ice_host_ips + if not resolved_ice_host_ips: + resolved_ice_host_ips = _detect_default_ice_host_ips() + config.ice_host_ips = resolved_ice_host_ips + configure_ice_host_addresses(resolved_ice_host_ips) + started_at_iso = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + control_trace = _load_control_trace(config.control_trace_path, config.mode) + artifacts_root = Path(config.artifacts_dir) / time.strftime("%Y%m%d_%H%M%S", time.gmtime()) + artifacts_root.mkdir(parents=True, exist_ok=True) + rtc_configuration = _build_rtc_configuration(config) + + warmup_results = await _run_phase( + config=config, + phase="warmup", + session_count=config.warmup_sessions, + artifacts_dir=artifacts_root, + rtc_configuration=rtc_configuration, + control_trace=control_trace, + ) + profile_results = await _run_phase( + config=config, + phase="profile", + session_count=config.session_count, + artifacts_dir=artifacts_root, + rtc_configuration=rtc_configuration, + control_trace=control_trace, + ) + + summary = _build_summary( + config=config, + warmup_results=warmup_results, + profile_results=profile_results, + started_at_iso=started_at_iso, + ) + summary_path = artifacts_root / "summary.json" + sessions_path = artifacts_root / "sessions.jsonl" + summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + with sessions_path.open("w", encoding="utf-8") as handle: + for result in [*warmup_results, *profile_results]: + handle.write(json.dumps(_result_to_dict(result), ensure_ascii=False) + "\n") + + summary["artifacts_dir"] = str(artifacts_root) + summary["summary_path"] = str(summary_path) + summary["sessions_path"] = str(sessions_path) + return summary + + +def main() -> None: + parser = _build_parser() + args = parser.parse_args() + config = _load_config(args) + summary = asyncio.run(_run_benchmark(config)) + _print_console_summary(summary) + print(f"Artifacts: {summary['artifacts_dir']}") + print(f"Summary JSON: {summary['summary_path']}") + print(f"Session records: {summary['sessions_path']}") + + +if __name__ == "__main__": + main() diff --git a/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh new file mode 100644 index 0000000..3dea4f0 --- /dev/null +++ b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "${ROOT_DIR}" +export PYTHONPATH="${ROOT_DIR}${PYTHONPATH:+:${PYTHONPATH}}" + +CONFIG_PATH="${1:-benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json}" +if [[ $# -gt 0 ]]; then + shift +fi + +NOFILE_LIMIT="${TELEFUSER_BENCH_NOFILE_LIMIT:-8192}" +if ! ulimit -n "${NOFILE_LIMIT}" >/dev/null 2>&1; then + echo "Warning: failed to raise open-file limit to ${NOFILE_LIMIT}" >&2 +fi + +SERVER_URL="${TELEFUSER_STREAM_BENCH_URL:-http://127.0.0.1:8088}" +HEALTH_URL="${TELEFUSER_STREAM_BENCH_HEALTH_URL:-${SERVER_URL}/v1/service/health}" +PYTHON_BIN="${TELEFUSER_STREAM_BENCH_PYTHON:-python3}" +ICE_HOST_IPS="${TELEFUSER_STREAM_BENCH_ICE_HOST_IPS:-}" +ICE_HOST_ARGS=() +if [[ -n "${ICE_HOST_IPS}" ]]; then + IFS=',' read -r -a _ICE_HOST_IP_ARRAY <<< "${ICE_HOST_IPS}" + for ice_host_ip in "${_ICE_HOST_IP_ARRAY[@]}"; do + if [[ -n "${ice_host_ip}" ]]; then + ICE_HOST_ARGS+=(--ice-host-ip "${ice_host_ip}") + fi + done +fi + +if command -v curl >/dev/null 2>&1; then + echo "Checking TeleFuser stream health: ${HEALTH_URL}" + curl --fail --silent --show-error "${HEALTH_URL}" >/dev/null +fi + +echo "Running stream benchmark with config: ${CONFIG_PATH}" +"${PYTHON_BIN}" benchmarks/telefuser_aiperf/scripts/run_stream_bench.py \ + --config "${CONFIG_PATH}" \ + --server-url "${SERVER_URL}" \ + "${ICE_HOST_ARGS[@]}" \ + "$@" diff --git a/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh b/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh new file mode 100755 index 0000000..02163f2 --- /dev/null +++ b/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "${ROOT_DIR}" + +CONFIG_PATH="${1:-benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml}" +SERVER_URL="${TELEFUSER_AIPERF_URL:-http://127.0.0.1:8000}" +HEALTH_URL="${TELEFUSER_AIPERF_HEALTH_URL:-${SERVER_URL}/v1/service/health}" +AIPERF_BIN="${AIPERF_BIN:-aiperf}" +NOFILE_LIMIT="${TELEFUSER_BENCH_NOFILE_LIMIT:-8192}" + +if ! ulimit -n "${NOFILE_LIMIT}" >/dev/null 2>&1; then + echo "Warning: failed to raise open-file limit to ${NOFILE_LIMIT}" >&2 +fi + +if ! command -v "${AIPERF_BIN}" >/dev/null 2>&1; then + echo "aiperf is not installed. Set AIPERF_BIN or run: pip install -e ./benchmarks/aiperf" >&2 + exit 1 +fi + +if command -v curl >/dev/null 2>&1; then + echo "Checking TeleFuser health: ${HEALTH_URL}" + curl --fail --silent --show-error "${HEALTH_URL}" >/dev/null +fi + +echo "Running AIPerf with config: ${CONFIG_PATH}" +"${AIPERF_BIN}" profile --config "${CONFIG_PATH}" diff --git a/docs/en/benchmark_aiperf.md b/docs/en/benchmark_aiperf.md new file mode 100644 index 0000000..fdebe3b --- /dev/null +++ b/docs/en/benchmark_aiperf.md @@ -0,0 +1,105 @@ +# TeleFuser and AIPerf Benchmark + +This page describes how to benchmark TeleFuser batch video serving with AIPerf. + +Current scope: + +- `telefuser serve` +- OpenAI-compatible `/v1/videos` API +- end-to-end request latency and throughput +- optional HTTP trace and TeleFuser metrics scraping + +Not covered yet: + +- `telefuser stream-serve` +- WebRTC / DataChannel world-model sessions +- frame-level and control-latency metrics + +For remote stream setup, use: + +```bash +python3 scripts/remote_bench_sync.py stream-bootstrap +``` + +That helper syncs the stream assets, installs WebRTC dependencies, auto-detects a usable `model_zoo` root, and writes a non-destructive `model_zoo` symlink inside the remote repo for later `telefuser stream-serve` startup. + +Both `run_stream_bench.sh` and `run_video_bench.sh` raise the open-file limit to `8192` by default to avoid AIPerf/WebRTC startup failures; override with `TELEFUSER_BENCH_NOFILE_LIMIT` if your environment needs a different value. + +For stream runs, `run_stream_bench.py` also auto-detects non-loopback local IPs for ICE candidate gathering when no explicit allowlist is provided. Use `--ice-host-ip` or `TELEFUSER_WEBRTC_ICE_HOST_IPS` only when you need to pin a specific address. + +## Assets + +The benchmark assets live in: + +```text +benchmarks/telefuser_aiperf/ +├── README.md +├── configs/ +├── data/ +└── scripts/ +``` + +## Prerequisites + +1. Start a TeleFuser batch video server. +2. Install AIPerf from the vendored clone. + +Example: + +```bash +telefuser serve \ + examples/wan_video/wan21_14b_image_to_video_h100.py \ + --port 8000 \ + --task i2v + +pip install -e ./benchmarks/aiperf +``` + +## Quick Start + +Run the default benchmark: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh +``` + +The script checks: + +```bash +curl http://127.0.0.1:8000/v1/service/health +``` + +and then runs AIPerf with: + +```bash +aiperf profile --config benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml +``` + +## Configs + +- `video_generation_quick.yaml`: minimal connectivity and latency check +- `video_generation_e2e.yaml`: fuller E2E run with warmup, download, trace, and optional server metrics +- `video_generation_rate.yaml`: Poisson arrival benchmark for rate-based load + +## Why Built-In Readiness Probe Is Disabled + +These configs intentionally do not use AIPerf's built-in `waitForModel*` readiness probe. + +Reason: + +- TeleFuser is being benchmarked through `video_generation` +- AIPerf readiness probing is better aligned with chat/completions/embeddings-style endpoints +- TeleFuser does not currently expose a stable `/v1/models` path for this use case + +So the helper script uses TeleFuser's `/v1/service/health` instead. + +## Limitation + +This is a **batch video serving benchmark**, not yet a **real-time world-model streaming benchmark**. + +For `stream-serve`, the next layer should add: + +1. a WebRTC transport +2. a world-model session endpoint +3. an interactive trace workload +4. frame-level latency metrics diff --git a/docs/en/index.md b/docs/en/index.md index 3278364..d13aaad 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -56,6 +56,7 @@ telefuser stream-serve examples/stream_server/stream_lingbot_world_fast.py -p 80 - **[Service Guide](service.md)** — Batch serving, task APIs, and SDK - **[Stream Server](stream_server.md)** — WebRTC streaming and bidirectional control +- **[TeleFuser and AIPerf Benchmark](benchmark_aiperf.md)** — Batch video serving benchmark with AIPerf - **[Configuration](configuration.md)** — Runtime and model configuration - **[Parallel Inference](parallel.md)** — Distributed processing strategies - **[Adding New Model](adding_new_model.md)** — Integrate new models diff --git a/docs/zh/benchmark_aiperf.md b/docs/zh/benchmark_aiperf.md new file mode 100644 index 0000000..a6f218c --- /dev/null +++ b/docs/zh/benchmark_aiperf.md @@ -0,0 +1,605 @@ +# TeleFuser 与 AIPerf Benchmark + +本文档是 TeleFuser 的 benchmark 执行入口,主要维护: + +- 批处理视频服务的压测运行方式 +- AIPerf 配置、参数和结果文件 +- 远程同步、部署和复现实验脚本 + +更细的 benchmark 设计、分层、对标 shortlist 和 `stream-serve` 规划放在: + +- [TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md) + +## 目录结构 + +仓库中新增的 benchmark 资产位于: + +```text +benchmarks/telefuser_aiperf/ +├── README.md +├── configs/ +│ ├── video_generation_quick.yaml +│ ├── video_generation_e2e.yaml +│ ├── video_generation_rate.yaml +│ └── stream_lingbot_world_fast_quick.json +├── data/ +│ ├── video_prompts.jsonl +│ └── stream_lingbot_controls.json +└── scripts/ + ├── run_video_bench.sh + └── run_stream_bench.sh +``` + +## 前置条件 + +### 1. 启动 TeleFuser 批处理服务 + +例如启动一个视频生成服务: + +```bash +telefuser serve \ + examples/wan_video/wan21_14b_image_to_video_h100.py \ + --port 8000 \ + --task i2v +``` + +确认服务健康检查可访问: + +```bash +curl http://127.0.0.1:8000/v1/service/health +``` + +### 2. 安装 AIPerf + +当前仓库已经 vendored 一份 AIPerf clone 到 `benchmarks/aiperf`,可直接本地安装: + +```bash +pip install -e ./benchmarks/aiperf +``` + +安装后确认 `aiperf` 命令可用: + +```bash +aiperf --help +``` + +## 快速开始 + +### 批处理视频 + +运行一个最小视频 benchmark: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh +``` + +默认使用: + +- 配置文件:`benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml` +- 服务地址:`http://127.0.0.1:8000` +- prompt 文件:`benchmarks/telefuser_aiperf/data/video_prompts.jsonl` + +脚本会先检查: + +```bash +curl http://127.0.0.1:8000/v1/service/health +``` + +然后执行: + +```bash +aiperf profile --config benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml +``` + +### 流式世界模型 + +先启动 TeleFuser 流式服务: + +```bash +telefuser stream-serve \ + examples/lingbot/stream_lingbot_world_fast.py \ + -p 8088 \ + --skip-validation +``` + +确认流式服务健康检查可访问: + +```bash +curl http://127.0.0.1:8088/v1/service/health +``` + +然后运行最小流式 benchmark: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh +``` + +默认使用: + +- 配置文件:`benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json` +- 控制 trace:`benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json` +- 服务地址:`http://127.0.0.1:8088` + +如果远端机器有很多网卡或容器 veth,`run_stream_bench.py` 现在会默认自动探测本机的非 loopback 地址并用于 ICE host candidate 收敛。 + +只有在你明确知道要锁定某个地址时,才手动传 `--ice-host-ip` 或设置 `TELEFUSER_WEBRTC_ICE_HOST_IPS`。不要默认写 `127.0.0.1`,否则在远程主机 / 容器网络里很容易把 WebRTC 连接锁死在本机回环地址。 + +这条路径当前不是 AIPerf 原生 endpoint,而是 TeleFuser 侧独立的 WebRTC benchmark harness。 + +如果在远程环境执行,建议直接用: + +```bash +python3 scripts/remote_bench_sync.py stream-bootstrap +``` + +它会同步代码、安装 WebRTC 依赖、自动探测可用的 `model_zoo` 根目录,并在远程仓库里写回一个非破坏性的 `model_zoo` 软链,方便后续直接起 `telefuser stream-serve`。 + +另外,`run_stream_bench.sh` 和 `run_video_bench.sh` 会默认把 open-file limit 提升到 `8192`,避免 AIPerf/WebRTC 在启动时撞到文件描述符上限;如果你的环境需要不同值,可以用 `TELEFUSER_BENCH_NOFILE_LIMIT` 覆盖。 + +## 可用配置 + +### `video_generation_quick.yaml` + +适合快速联通性验证。 + +特点: + +- 5 个请求 +- 并发 1 +- 不下载视频内容 +- 只看最基本的端到端结果 + +使用: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh \ + benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml +``` + +### `video_generation_e2e.yaml` + +适合更完整的 E2E benchmark。 + +特点: + +- 1 个 warmup 请求 +- 默认 profiling 并发 2 +- 默认 profiling 请求数 6 +- 开启 `downloadVideoContent: true` +- 开启 HTTP trace 导出 +- 可抓取 TeleFuser `/v1/service/metrics` + +使用: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh \ + benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml +``` + +### `video_generation_rate.yaml` + +适合看低频到中频请求率下的表现。 + +特点: + +- `poisson` 到达模式 +- 默认速率 `0.2 req/s` +- 默认最大并发 4 +- 适合观察排队与时延波动 + +使用: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh \ + benchmarks/telefuser_aiperf/configs/video_generation_rate.yaml +``` + +### `stream_lingbot_world_fast_quick.json` + +适合流式世界模型最小联通和端到端时延验证。 + +特点: + +- 1 个 session +- `WebRTC + DataChannel` +- 默认运行 `12s` +- 按固定 control trace 注入方向控制 +- 输出 `offer RTT`、`首帧时延`、`稳态 FPS`、`control ack latency`、`control-to-next-frame latency` + +使用: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ + benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json +``` + +## 常用环境变量 + +这些配置支持通过环境变量覆盖: + +```bash +export TELEFUSER_AIPERF_URL=http://127.0.0.1:8000 +export TELEFUSER_AIPERF_METRICS_URL=http://127.0.0.1:8000/v1/service/metrics +export TELEFUSER_AIPERF_CONCURRENCY=4 +export TELEFUSER_AIPERF_REQUESTS=12 +export TELEFUSER_AIPERF_SIZE=1280x720 +export TELEFUSER_AIPERF_SECONDS=4 +export TELEFUSER_AIPERF_SERVER_METRICS=true +``` + +例如跑一个更高并发的完整 E2E: + +```bash +export TELEFUSER_AIPERF_CONCURRENCY=4 +export TELEFUSER_AIPERF_REQUESTS=12 + +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh \ + benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml +``` + +流式 benchmark 也支持通过 CLI 覆盖,例如: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ + benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json \ + --session-count 1 \ + --session-duration-s 20 \ + --print-events +``` + +## 远程一键同步与校验 + +为了方便把这套 benchmark 资产同步到远程测试机,仓库中新增了: + +```bash +python3 scripts/remote_bench_sync.py --help +``` + +这个脚本默认面向当前测试机: + +- `root@116.238.240.2` +- SSH 端口 `30724` +- 远程仓库路径 `/workspace/TeleFuser` +- TeleFuser 服务 Python `/root/venv/cu128/bin/python` +- 独立 benchmark Python `/root/venv/telefuser-bench/bin/python` + +推荐模式是**分离部署**: + +- 已经部署好的 TeleFuser 服务环境继续保留 +- 额外创建一个独立的 benchmark venv,只安装 AIPerf 和压测依赖 + +这样可以避免 benchmark 依赖覆盖 TeleFuser 现有运行环境里的: + +- `transformers` +- `protobuf` +- `pillow` +- `huggingface_hub` + +如果你要跑 `telefuser stream-serve` 的世界模型流式 benchmark,推荐直接走一键自动化预备: + +```bash +python3 scripts/remote_bench_sync.py stream-bootstrap +``` + +这条命令会自动完成: + +- 同步流式 benchmark 资产 +- 安装 AIPerf 和 `aiortc` / `opencv-python-headless` +- 安装 TeleFuser 到独立服务环境 +- 校验 `examples/lingbot/stream_lingbot_world_fast.py` +- 校验 `model_zoo` 路径 +- 打印远程 GPU 状态 + +如果你只想更新安装而不重新同步: + +```bash +python3 scripts/remote_bench_sync.py install --install-webrtc --install-telefuser +``` + +如果目标机器变化,可以通过参数覆盖: + +```bash +python3 scripts/remote_bench_sync.py \ + --host \ + --port \ + --user \ + --remote-repo \ + --remote-python \ + --remote-bench-python \ + gpu-status +``` + +### 1. 查看远程 GPU 状态 + +```bash +python3 scripts/remote_bench_sync.py gpu-status +``` + +这个命令会通过 SSH 执行 `nvidia-smi`,输出: + +- 每张卡的利用率 +- 显存占用 +- 当前活跃计算进程 + +### 2. 首次全量同步 + +```bash +python3 scripts/remote_bench_sync.py bootstrap +``` + +这个命令会把当前管理的 benchmark 资产全量同步到远程,包括: + +- `benchmarks/aiperf` +- `benchmarks/telefuser_aiperf` +- `docs/en/benchmark_aiperf.md` +- `docs/zh/benchmark_aiperf.md` +- `docs/en/index.md` +- `docs/zh/index.md` +- `mkdocs.yml` + +如果你还希望同步完成后直接安装并校验: + +```bash +python3 scripts/remote_bench_sync.py bootstrap --install --verify +``` + +默认行为是: + +- 创建独立 benchmark venv +- 在独立环境中安装 vendored `benchmarks/aiperf` +- 复用同一个 benchmark venv 运行流式 benchmark Python 脚本 +- 不修改当前 TeleFuser 服务环境 + +如果你明确希望同时刷新 TeleFuser 服务环境: + +```bash +python3 scripts/remote_bench_sync.py bootstrap --install --verify --install-telefuser +``` + +如果你想强制重建独立 benchmark venv: + +```bash +python3 scripts/remote_bench_sync.py bootstrap --install --verify --recreate-bench-venv +``` + +如果当前只想演练命令而不真正执行: + +```bash +python3 scripts/remote_bench_sync.py --dry-run bootstrap --install --verify +``` + +### 3. 后续增量同步 + +首次同步后,脚本会在本地写一个状态文件,用于记录上次已同步的文件哈希: + +```text +~/.cache/telefuser/remote_bench_sync/.json +``` + +后续直接运行: + +```bash +python3 scripts/remote_bench_sync.py sync +``` + +脚本只会上传发生变化的受管文件。 + +如果你想看本次计划同步哪些文件: + +```bash +python3 scripts/remote_bench_sync.py sync --print-files +``` + +如果你想忽略状态文件并强制重新全量上传: + +```bash +python3 scripts/remote_bench_sync.py sync --full +``` + +### 4. 远程安装 + +```bash +python3 scripts/remote_bench_sync.py install +``` + +默认会在远程执行: + +- 创建独立 venv:`/root/venv/telefuser-bench` +- `pip install -e /workspace/TeleFuser/benchmarks/aiperf` + +也就是说,默认**不会**修改现有 TeleFuser 服务环境。 + +如果你确实要刷新 TeleFuser 服务环境: + +```bash +python3 scripts/remote_bench_sync.py install --install-telefuser +``` + +如果你想强制重建独立 benchmark venv: + +```bash +python3 scripts/remote_bench_sync.py install --recreate-bench-venv +``` + +如果你只想更新源码,不想拉依赖,可以使用: + +```bash +python3 scripts/remote_bench_sync.py install --no-deps +``` + +如果你当前不想安装 AIPerf,只想做其它步骤: + +```bash +python3 scripts/remote_bench_sync.py install --skip-aiperf +``` + +### 5. 远程校验 + +```bash +python3 scripts/remote_bench_sync.py verify +``` + +校验内容包括: + +- benchmark 配置文件是否存在 +- TeleFuser 服务环境中的 `import telefuser` +- TeleFuser 服务环境中的 `telefuser --help` +- 独立 benchmark 环境中的 `import aiperf` +- 独立 benchmark 环境中的 `aiperf --help` +- 独立 benchmark 环境中的 `python benchmarks/telefuser_aiperf/scripts/run_stream_bench.py --help` + +注意: + +- 如果你之前使用的是 `install --no-deps`,那么对应环境的 CLI 校验可能失败 +- 如果你只想检查 benchmark 环境,可以使用: + +```bash +python3 scripts/remote_bench_sync.py verify --skip-telefuser +``` + +### 6. 运行 benchmark + +#### 6.1 一键跑 `Wan2.1-I2V-14B-480P` 对比 + +如果远程机器上的 benchmark 资产和隔离环境已经准备好,可以直接用一条命令顺序跑: + +- `TeleFuser` +- `Diffusers` baseline + +```bash +python3 scripts/remote_bench_sync.py batch-compare \ + --framework both \ + --gpu 0 +``` + +这条命令会自动完成: + +- 校验远程 `telefuser` / `aiperf` 环境 +- 解析 TeleFuser batch 模型目录 +- 对指定 GPU 执行 `gpu_burner.sh stop ` +- 检查该 GPU 是否真的空闲 +- 拉起 TeleFuser 480P 服务 +- 跑对齐后的 TeleFuser AIPerf config +- 停掉 TeleFuser 服务 +- 拉起 Diffusers baseline 服务 +- 跑对齐后的 Diffusers AIPerf config +- 输出两边最新 `summary.json` 路径和关键指标 + +默认行为是**非破坏性的**: + +- 只会停止指定 GPU 上的 burner 占位 +- 不会主动杀掉远程机器上已有的其它计算任务 +- 如果 GPU 在停止 burner 之后仍然被真实任务占用,会直接失败 + +如果你希望它等待 GPU 空闲,而不是立即失败,可以显式给等待窗口: + +```bash +python3 scripts/remote_bench_sync.py batch-compare \ + --framework both \ + --gpu 0 \ + --gpu-idle-timeout 600 +``` + +如果你只想单独跑一边: + +```bash +python3 scripts/remote_bench_sync.py batch-compare --framework telefuser --gpu 0 +python3 scripts/remote_bench_sync.py batch-compare --framework diffusers --gpu 0 +``` + +这条自动化入口当前固定使用: + +- TeleFuser config: + `benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml` +- Diffusers config: + `benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml` + +如果远程环境还没准备好,先执行: + +```bash +python3 scripts/remote_bench_sync.py sync +python3 scripts/remote_bench_sync.py install --install-telefuser +python3 scripts/remote_bench_sync.py verify +``` + +#### 6.2 手工跑 TeleFuser batch benchmark + +当 TeleFuser 服务已经在远程机器上启动后,可以直接指定独立 benchmark 环境中的 `aiperf` 可执行文件: + +```bash +export AIPERF_BIN=/root/venv/telefuser-bench/bin/aiperf + +bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh \ + benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml +``` + +这样 benchmark 走独立环境,TeleFuser 服务继续走原来的运行环境,两边互不覆盖。 + +#### 6.3 手工跑流式 benchmark + +流式 benchmark 则直接走独立环境里的 Python: + +```bash +export TELEFUSER_STREAM_BENCH_PYTHON=/root/venv/telefuser-bench/bin/python + +bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ + benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json +``` + +## 为什么没有启用 AIPerf 自带 readiness probe + +当前这套配置没有使用 AIPerf 的 `waitForModelTimeout` / `waitForModelMode`。 + +原因是: + +- TeleFuser 这里对接的是 `video_generation` endpoint +- AIPerf 的 readiness probe 更偏向 `chat` / `completions` / `embeddings` +- TeleFuser 当前没有稳定的 `/v1/models` 路径可用于这类 probe + +因此这里改为更直接的做法:在 benchmark 脚本里先检查 TeleFuser 的 `/v1/service/health`。 + +## 指标解释 + +这套 benchmark 里最值得关注的通常是: + +- `Request Latency` + 表示一次完整视频请求从提交到完成的端到端耗时。 + +- `Request Throughput` + 表示单位时间内完成的请求数。 + +- HTTP Trace 相关指标 + 用于区分时间主要耗在: + - 连接建立 + - 请求发送 + - 服务端等待 + - 响应接收 + +- TeleFuser 服务指标 + 如果启用了 `serverMetrics`,可以同时对照: + - 队列长度 + - 已创建 / 已完成 / 已失败任务数 + - GPU 指标 + +## 结果文件 + +结果默认写到: + +```text +artifacts/telefuser_aiperf/ +``` + +不同配置写入不同子目录,例如: + +- `artifacts/telefuser_aiperf/video_quick` +- `artifacts/telefuser_aiperf/video_e2e` +- `artifacts/telefuser_aiperf/video_rate` +- `artifacts/telefuser_aiperf/stream_lingbot_quick/` + +通常可以关注: + +- summary JSON +- raw records JSONL +- server metrics JSON / CSV +- stream benchmark 的逐 session JSONL +- stream benchmark 的逐事件 JSONL diff --git a/docs/zh/benchmark_aiperf_design.md b/docs/zh/benchmark_aiperf_design.md new file mode 100644 index 0000000..ec5dc8a --- /dev/null +++ b/docs/zh/benchmark_aiperf_design.md @@ -0,0 +1,684 @@ +# TeleFuser 与 AIPerf Benchmark 设计 + +本文档记录 benchmark 的设计决策、对标边界和后续扩展方向。 + +具体跑法、脚本、参数和结果文件说明请看: + +- [TeleFuser 与 AIPerf Benchmark](benchmark_aiperf.md) + +## 1. Benchmark 分层 + +### 1.1 批处理视频服务 benchmark + +这类场景指的是: + +- 用户提交一次文本或图像条件 +- 服务端异步生成一个完整视频 +- 客户端轮询任务状态 +- 最终拿到视频文件 + +这一类当前最适合: + +- `telefuser serve` +- OpenAI 兼容 `/v1/videos` +- `AIPerf` 端到端 HTTP benchmark + +### 1.2 世界模型流式系统 benchmark + +这类场景指的是: + +- 长生命周期 session +- 持续输入控制信号 +- 持续输出视频帧或媒体流 +- 更关注控制反馈闭环,而不是单次完整视频生成 + +这一类更接近: + +- `telefuser stream-serve` +- WebRTC + DataChannel +- 世界模型在线交互推理 + +这两类负载的评价指标不同,不能混在同一套 benchmark 结论里。 + +### 1.3 世界模型实时流式系统框架 shortlist + +如果 TeleFuser 后面要做 `stream-serve`,更合适的对标对象应该是系统框架 / serving runtime,而不是模型本体。 + +当前更接近的开源系统层候选是: + +- `SGLang-Omni` +- `vLLM-Omni` +- `vLLM` +- `SGLang` +- `Ray Serve` +- `Triton Inference Server` + +如果需要实时媒体传输层,还可以单独观察: + +- `LiveKit` +- `Janus` +- `mediasoup` +- `aiortc` +- `Pion` + +这些是 transport 组件,不是推理框架本身。 + +需要明确区分的是: + +- `Matrix-Game`、`MineWorld`、`LongLive`、`CausVid`、`Helios` 更偏“模型 / 研究系统 / reference system” +- 它们适合用来定义负载和行为模式 +- 但不应混进“系统框架 shortlist” + +真正的世界模型流式端到端 benchmark 仍需要我们自己定义请求协议、会话状态和指标。 + +### 1.4 `stream-serve` 第一批系统框架对比顺序 + +如果后面要真的落地 `stream-serve` 的端到端 benchmark,第一批建议先比: + +1. `TeleFuser stream-serve` +2. `SGLang-Omni` +3. `vLLM-Omni` + +优先级原因很简单: + +- `SGLang-Omni` 更贴近我们现在想做的“长会话、多阶段、低延迟在线生成”系统形态 +- `vLLM-Omni` 可以作为更通用的第二基线 +- 两者都比通用 `Ray Serve` / `FastAPI` 更接近推理系统本体 + +如果需要把 transport 也纳入对比,再单独固定 WebRTC / SSE / WebSocket 实现,不要和推理框架混算。 + +### 1.5 批处理视频模型对标顺序 + +如果目标是评估 **TeleFuser 与其它视频推理框架** 的差异,那么第一轮 benchmark 必须满足下面这些约束: + +1. **模型相同** + - 例如都跑 `Wan2.1-I2V-14B-480P` + - 或都跑 `HunyuanVideo-1.5` + - 或都跑 `LTX-2` + +2. **任务相同** + - `t2v` 只和 `t2v` 比 + - `i2v` 只和 `i2v` 比 + - 不把 `t2v`、`i2v`、`ti2v` 混在一起 + +3. **输入相同** + - 同 prompt + - 同输入图 + - 同输出分辨率 + - 同帧数或等价生成长度 + - 同 diffusion steps + +4. **硬件相同** + - 同 GPU 型号 + - 同卡数 + - 同显存约束 + - 尽量控制一致的软件栈 + +5. **只变推理框架** + - 可以比较 `TeleFuser`、`Diffusers`、`xDiT`、`LightX2V`、`FastVideo`、`SGLang Diffusion` + - 但不能把“更换模型蒸馏版权重”误当成“只变框架” + +只要不满足这些条件,就不应被视为第一轮主 benchmark。 + +推荐优先级: + +1. `Wan2.1-I2V-14B-480P` +2. `HunyuanVideo-1.5` +3. `LTX-2` + +### 1.6 当前已落地的第一版可执行基线 + +仓库里已经补齐了第一版“同模型、只变框架”的可执行闭环,当前不是只停留在规划层面了: + +- TeleFuser 侧固定 480P 服务示例:[examples/wan_video/wan21_14b_image_to_video_480p_service.py](../../examples/wan_video/wan21_14b_image_to_video_480p_service.py) +- Diffusers 侧独立基线服务:[benchmarks/baseline/diffusers_wan_i2v/service.py](../../benchmarks/baseline/diffusers_wan_i2v/service.py) + +这版首个可直接开跑的对照组合固定为: + +- 模型:`Wan2.1-I2V-14B-480P` +- 分辨率:`832x480` +- 帧数:`81` +- 推理步数:`40` +- `guidance_scale=5.0` +- `fps=16` + +这里刻意把 TeleFuser 侧的 480P 服务从通用面积启发式改成了固定 `832x480`,目的是避免与官方 Diffusers 基线出现宽高不一致,保证第一轮对照可比。 + +### 1.7 AIPerf 对批处理视频负载的支持情况 + +AIPerf 对 `telefuser serve` 这类批处理视频服务已经有明确支持,当前覆盖的核心能力是: + +- OpenAI 兼容 `/v1/videos` 的异步提交 / 轮询 / 下载流程 +- `video_generation` endpoint +- `--download-video-content` 把下载时间纳入端到端时延 +- `multipart/form-data` 请求编码 +- `--extra-inputs` 传递视频生成参数 +- `single_turn` / `multi_turn` 这类输入组织方式 + +对 TeleFuser 这条线来说,AIPerf 已经能支撑两类最重要的批处理视频负载: + +1. **T2V** + - 直接用 `video_generation` endpoint + - 用文本 prompt 驱动 `/v1/videos` + +2. **I2V** + - 同样走 `video_generation` endpoint + - 通过 `reference_url` 或 `input_reference` 传入参考图 + - 我们当前的 `telefuser_aiperf` prompt 文件已经在用 `reference_url` 这条路 + +也就是说: + +- AIPerf 不是只能测纯文本视频生成 +- 它已经可以覆盖我们当前 TeleFuser 批处理视频 benchmark 里的 `T2V / I2V` 主线 +- 但它的上游文档和教程仍然更偏通用视频生成,不是专门为 TeleFuser 的 `I2V` 场景定制 + +当前对我们最有用的落点是: + +- `telefuser serve` + `AIPerf` 已足够做 batch video E2E benchmark +- `Wan2.1-I2V-14B-480P` 这条线已经可以直接跑 +- 后续若要扩展到更复杂的 `I2V` 变体,只需要继续补 prompt/schema 适配,而不是重做 benchmark 框架 + +### 1.8 Baseline 接口协议标准 + +随着后面要逐步接入更多对照实现,例如: + +- `Diffusers` +- `xDiT` +- `FastVideo` +- `LightX2V` +- `SGLang Diffusion` +- `vLLM-Omni` + +仓库里需要一套统一的 **baseline benchmark contract**,否则每接一个新 baseline,就会重复发明一套: + +- 目录布局 +- 启动脚本 +- 健康检查方式 +- HTTP / WebRTC 适配层 +- 指标采集字段 +- 文档说明格式 + +这里要标准化的不是 baseline 的内部实现,而是 **benchmark 视角下的最小可测协议**。 + +#### 1.8.1 标准化目标 + +第一版建议定义成 **versioned contract**: + +- `contract_version: v1` +- `mode: batch_video | stream_world` + +第一版只覆盖两类主负载: + +1. `batch_video` + - 对应 `telefuser serve` + - 对应 AIPerf 当前已经能覆盖的 `/v1/videos` 异步完整视频生成 + +2. `stream_world` + - 对应 `telefuser stream-serve` + - 对应 WebRTC 长会话、持续控制、持续输出的视频流 benchmark + +后续如果再扩展: + +- `audio_video_realtime` +- `multi_camera_world` +- `tool_augmented_agent_stream` + +应在 `v1` 之外继续演进,而不是一开始把所有可能性都揉进同一套协议。 + +#### 1.8.2 目录协议 + +第三方或对照实现统一放在: + +- `benchmarks/baseline//` + +推荐最小目录结构: + +- `README.md` +- `service.py` 或 `launcher.py` +- `configs/` +- `scripts/` +- `benchmark_contract.yaml` + +其中: + +- `telefuser_aiperf/` 不是 baseline,它是 TeleFuser 自己的 benchmark harness +- `baseline/` 下面放“只变推理框架”的对照实现 +- `benchmark_contract.yaml` 用来描述 baseline 能力,而不是替代 README +- 当前 TeleFuser benchmark harness 和 Diffusers baseline 已经分别提供了第一版薄 contract: + - [benchmarks/telefuser_aiperf/benchmark_contract.yaml](../../benchmarks/telefuser_aiperf/benchmark_contract.yaml) + - [benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml](../../benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml) + +#### 1.8.3 能力声明协议 + +每个 baseline 建议暴露一个能力声明文件,例如 `benchmark_contract.yaml`,最少包含: + +- `contract_version` +- `baseline_name` +- `mode` +- `model_family` +- `supported_tasks` +- `transport` +- `request_encoding` +- `result_delivery` +- `metrics` + +建议第一版字段示例: + +```yaml +contract_version: v1 +baseline_name: diffusers_wan_i2v +mode: batch_video +model_family: wan21_i2v_14b_480p +supported_tasks: + - i2v +transport: http +request_encoding: + - multipart_form + - json +result_delivery: + - poll_status + - download_content +capabilities: + supports_reference_url: true + supports_input_reference_upload: true + supports_cancel: true +metrics: + - request_latency + - completion_latency + - download_latency + - peak_memory_mb +``` + +这一层的价值是: + +- benchmark 脚本能先检查 baseline 支持什么 +- 文档和自动化部署脚本可以直接消费这份声明 +- 后续接新 baseline 时,先补 contract,再补适配脚本 + +#### 1.8.4 `batch_video` 最小接口协议 + +对 `batch_video` 类型 baseline,建议统一最小 HTTP 面: + +- `GET /v1/service/health` +- `POST /v1/videos` +- `GET /v1/videos/{id}` +- `GET /v1/videos/{id}/content` +- `DELETE /v1/videos/{id}` 可选 + +最小请求语义建议统一为: + +- `prompt` +- `model` +- `size` +- `seconds` +- `seed` +- `negative_prompt` +- `reference_url` 或 `input_reference` + +允许 baseline 自己额外扩展字段,但 benchmark 第一轮只依赖最小公共子集。 + +`GET /v1/videos/{id}` 的最小返回字段建议统一为: + +- `id` +- `status` +- `progress` +- `created_at` +- `completed_at` +- `error` +- `url` 或 `file_path` + +其中状态集合建议至少覆盖: + +- `queued` +- `generating` +- `completed` +- `failed` +- `cancelled` + +这样 AIPerf 和后续自定义 harness 都可以复用同一套 submit / poll / download 流程。 + +#### 1.8.5 `stream_world` 最小接口协议 + +对 `stream_world` 类型 baseline,建议统一最小会话面: + +- `GET /v1/service/health` +- `POST /v1/stream/webrtc/offer` +- `DELETE /v1/stream/webrtc/{session_id}` + +如果使用 DataChannel,建议至少约定这些消息类别: + +- `control` +- `status` +- `chunk` +- `done` +- `error` + +其中: + +- `control` 由客户端发送,代表方向控制、动作控制或结构化控制输入 +- `status` 由服务端发送,代表会话阶段和状态机推进 +- `chunk` 由服务端发送,代表一批可消费的输出 +- `done` 代表本轮或整个 session 正常结束 +- `error` 代表 session 失败或不可恢复异常 + +不要求所有 baseline 都与 TeleFuser 的 DataChannel payload 完全一致,但必须有一层 adapter 能映射到这套 benchmark 语义。 + +#### 1.8.6 指标协议 + +接口统一之后,还需要统一结果侧最小指标集合。 + +`batch_video` 至少建议采: + +- `request_latency` +- `queue_latency` +- `completion_latency` +- `download_latency` +- `success_rate` +- `throughput` + +`stream_world` 至少建议采: + +- `offer_rtt` +- `connected_latency` +- `first_frame_latency` +- `first_metadata_latency` +- `steady_state_fps` +- `control_ack_latency` +- `control_to_next_frame_latency` +- `session_success_rate` + +如果 baseline 能提供服务端附加指标,也建议通过统一字段上报,例如: + +- `inference_time_s` +- `peak_memory_mb` +- `server_metrics_url` + +#### 1.8.7 非目标 + +这套标准当前 **不** 试图做下面这些事情: + +- 不统一 baseline 的内部代码结构 +- 不强制所有 baseline 使用同一种 Web 框架 +- 不强制所有 baseline 使用同一种任务队列实现 +- 不把 transport 协议细节过度收紧到无法接第三方系统 + +也就是说: + +- 我们标准化的是“怎么测” +- 不是“怎么实现” + +#### 1.8.8 第一批落地顺序 + +这套 baseline contract 第一批建议先落到: + +1. `benchmarks/baseline/diffusers_wan_i2v/` +2. 后续第二个 batch baseline +3. 再考虑 `stream_world` 类型 baseline + +原因是: + +- `diffusers_wan_i2v` 已经是现成的第一版对照实现 +- 最容易先把目录协议、能力声明和 batch_video 最小接口约束跑通 +- `stream_world` 的协议复杂度更高,适合在 batch contract 稳定后再继续抽象 + +这部分设计一旦落地,后续新增 baseline 时应优先遵守 contract,再补实现,而不是先写一套散落脚本再回头整理。 + +#### 1.8.9 Batch Compare 自动化约束 + +对于第一版 `Wan2.1-I2V-14B-480P` 对照,不应该继续靠手工 SSH 一条条拼: + +- 停 burner +- 起 TeleFuser +- 跑 AIPerf +- 停服务 +- 起 baseline +- 再跑一次 + +这条链路现在应该固化成一个总入口: + +```bash +python3 scripts/remote_bench_sync.py batch-compare +``` + +这条自动化入口的职责边界应该明确成下面这样: + +1. 先校验远程环境 + - `telefuser` CLI 可用 + - `aiperf` CLI 可用 + - compare config 存在 + +2. 只管理“我们自己的 benchmark 资源” + - 可以对指定 GPU 执行 burner `stop` + - 可以拉起和停止本次 benchmark 启动的服务进程 + - 不应默认杀掉远程机器上其它已在运行的真实计算任务 + +3. GPU 资源策略默认非破坏性 + - `stop burner` 之后检查目标 GPU 是否真的空闲 + - 如果还有其它 compute process,占用信息直接报出来 + - 默认立即失败,或按显式参数进入等待模式 + +4. 对比执行顺序固定 + - 同一张 GPU + - 先 TeleFuser + - 再 Diffusers baseline + - 使用同一组对齐后的 compare config + +5. 结果输出标准化 + - 返回各自最新 `summary.json` 路径 + - 汇总 `request_latency` + - 汇总 `request_throughput` + - 后续如果补 `completion_latency` / `download_latency` 也沿同一路径扩展 + +这样做的目的不是把所有逻辑塞进一个巨型脚本,而是把: + +- 资源预留 +- 服务启停 +- benchmark 执行 +- 结果采集 + +变成一条可重复、可审计、默认安全的 batch compare 链路。 + +## 2. 设计边界 + +### 2.1 不把通用 serving 框架混进第一层主对标 + +`FastAPI`、`Ray Serve`、`BentoML`、`Xinference` 这类项目当然有价值,但它们解决的问题主要是: + +- HTTP 服务封装 +- 副本管理 +- 路由与部署 +- 多租户和线上治理 + +它们不是视频推理优化本身。 + +### 2.2 世界模型流式 benchmark 仍需要独立 harness + +当前开源里仍然没有一个像 AIPerf 这样成熟、通用的 **世界模型流式系统 benchmark**。 + +`AIPerf` 更适合当前的完整视频生成 HTTP 服务 benchmark,`telefuser stream-serve` 最终仍然需要单独建设一套 benchmark harness。 + +这类 benchmark 更应该关注: + +- session 建立时延 +- 首帧时延 +- steady-state FPS +- frame jitter +- control-to-frame latency +- 长会话稳定性 +- 会话并发数 +- 断线重连与恢复 + +当前仓库里已经补了一版最小可跑 harness: + +- `benchmarks/telefuser_aiperf/scripts/run_stream_bench.py` +- `benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh` +- `benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json` + +这一版的边界是: + +- 先对齐 `telefuser stream-serve` 的真实 WebRTC offer / answer 协议 +- 先对齐 `LingBotWorldFastService` 的 DataChannel 控制消息格式 +- 先把核心时延指标打通,不强行塞进 AIPerf core + +当前可直接采的指标有: + +- `offer RTT` +- `connected latency` +- `first frame latency` +- `first metadata latency` +- `stream FPS` +- `control ack latency` +- `control-to-next-frame latency` + +当前已知限制也要写清楚: + +- benchmark harness 本身支持多 session 参数 +- 但 `LingBotWorldFastService` 当前代码只允许 `1 active session` +- 所以现在这条 pipeline 还不能拿来做真正的多会话并发对比 + +### 2.2.1 自动化部署与运行链路 + +这条流式 benchmark 不应该依赖手工 SSH 拼命令。建议把准备流程固定成一条自动化链路: + +1. `sync` + - 同步 `benchmarks/telefuser_aiperf` + - 同步 `examples/lingbot/stream_lingbot_world_fast.py` + - 同步文档和 burner 脚本 + +2. `install` + - 在独立 benchmark venv 安装 AIPerf + - 安装 `aiortc` / `opencv-python-headless` + - 可选安装 TeleFuser 到独立服务 venv + +3. `verify` + - 检查脚本、配置、模型入口是否存在 + - 检查 `telefuser` / `aiperf` CLI + - 检查 WebRTC 依赖是否已装 + - 检查流式模型 zoo 路径是否可见 + +4. `start` + - 由远程服务环境启动 `telefuser stream-serve` + - 由 benchmark 环境启动 `run_stream_bench.sh` + +补充说明: + +- `stream-bootstrap` 会优先探测 `TF_MODEL_ZOO_PATH`,再回退到仓库内 `model_zoo` 和几个常见远程挂载点 +- 一旦解析到可用根目录,会在远程仓库下创建非破坏性的 `model_zoo -> 实际路径` 软链,后续启动命令可以直接用默认相对路径 +- 这样能避免每次手工记住 `TF_MODEL_ZOO_PATH`,也能保证 TeleFuser 服务和 benchmark 使用同一份权重根目录 + +建议的总入口是: + +```bash +python3 scripts/remote_bench_sync.py stream-bootstrap +``` + +这会把流式 benchmark 的前置条件一次性准备好,减少手工出错。 + +## 3. 后续扩展方向 + +如果后续要 benchmark `telefuser stream-serve`,建议在同一份总文档下继续扩展为: + +1. `SGLang-Omni` / `vLLM-Omni` 的系统框架接入 +2. WebRTC / SSE / WebSocket transport +3. world-model session endpoint +4. interactive trace workload +5. frame-level metrics +6. control-to-frame latency metrics + +这样才能覆盖 TeleFuser 最核心的实时世界模型场景,并保持和批处理视频 benchmark 在同一入口下管理。 + +## 4. AIPerf 需要做哪些改造 + +AIPerf 现在已经足够支撑 `telefuser serve` 这类批处理视频压测,但如果要把它扩展到 `stream-serve` 和更完整的世界模型流式场景,还需要补下面这些能力。 + +### 4.1 保留的部分 + +不需要重写的部分: + +- benchmark runner 和 config 解析 +- 并发控制、warmup、请求数、速率模型 +- 结果汇总、summary JSON、原始记录导出 +- HTTP trace 采集 +- 远程执行、结果落盘、artifact 管理 + +这些都可以继续复用。 + +### 4.2 需要新增的抽象 + +AIPerf **已经支持** chat 场景里的 multi-turn conversation benchmark,包括: + +- `multi_turn` 数据集 +- `--conversation-turn-mean` / `--session-turns-mean` +- `--conversation-turn-delay-mean` / `--session-turn-delay-mean` +- `sticky-user-sessions` + +所以这里不是“补一个多轮对话能力”这么简单,而是要把现有多轮对话能力进一步抽象成适合 `stream-serve` 的会话模型: + +- `request` 之外增加更明确的 `session` 生命周期 +- 支持 `session start / session step / session end` +- 支持同一 session 内多轮控制消息 +- 支持流式响应里的分帧事件 +- 支持把一个 session 的生命周期完整打包成一条 trace + +这样它才能覆盖 `stream-serve`,而不只是覆盖 chat multi-turn。 + +### 4.3 需要新增的 transport adapter + +现在 AIPerf 主要是 HTTP 请求模型。后续要对齐 `stream-serve`,建议加 adapter 层: + +- HTTP polling adapter +- WebSocket adapter +- SSE adapter +- WebRTC adapter + +每种 adapter 只负责 transport,不负责 benchmark 逻辑。 + +### 4.4 需要新增的 workload model + +除了现在这种 batch video workload,还要再加: + +- 长会话交互 workload +- 多轮控制 workload +- 连续帧输出 workload +- 动态 prompt / control message workload +- session 断开与恢复 workload + +如果只保留单次请求模型,就测不到 world model 的真实交互成本。 + +### 4.5 需要新增的指标 + +批处理视频继续看: + +- request latency +- throughput +- success rate +- peak memory +- tail latency + +流式世界模型还要看: + +- first frame latency +- steady-state FPS +- frame jitter +- control-to-frame latency +- session duration stability +- reconnect latency + +### 4.6 推荐的改造顺序 + +建议按这个顺序做: + +1. 保持现有 batch video benchmark 不动 +2. 把 `session` 抽象加到 AIPerf +3. 加 WebSocket / SSE adapter +4. 再补 WebRTC adapter +5. 最后接 `stream-serve` workload 和 frame-level 指标 + +这样可以先不破坏现有 `telefuser serve` 压测,同时逐步覆盖实时世界模型场景。 + +## 5. 参考链接 + +- [SGLang-Omni](https://sgl-project.github.io/sglang-omni/) +- [vLLM-Omni](https://docs.vllm.ai/projects/vllm-omni/en/latest/) +- [TeleFuser 480P 服务示例](../../examples/wan_video/wan21_14b_image_to_video_480p_service.py) +- [Diffusers baseline 服务](../../benchmarks/baseline/diffusers_wan_i2v/service.py) diff --git a/docs/zh/index.md b/docs/zh/index.md index c5f2a40..3d01156 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -56,6 +56,8 @@ telefuser stream-serve examples/stream_server/stream_lingbot_world_fast.py -p 80 - **[服务指南](service.md)** — 批量服务、任务 API 和 SDK - **[流式服务](stream_server.md)** — WebRTC 流式传输和双向控制 +- **[TeleFuser 与 AIPerf Benchmark](benchmark_aiperf.md)** — 批处理视频压测与运行入口 +- **[TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md)** — 对标分层、stream-serve 规划和固定 workload 设计 - **[配置](configuration.md)** — 运行时和模型配置 - **[并行推理](parallel.md)** — 分布式处理策略 - **[新增模型](adding_new_model.md)** — 集成新模型 diff --git a/examples/lingbot/stream_lingbot_world_fast.py b/examples/lingbot/stream_lingbot_world_fast.py index f982bb8..381064a 100644 --- a/examples/lingbot/stream_lingbot_world_fast.py +++ b/examples/lingbot/stream_lingbot_world_fast.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +from pathlib import Path import torch @@ -11,15 +12,15 @@ ) from telefuser.pipelines.lingbot_world_fast.service import LingBotWorldFastService -TF_MODEL_ZOO_PATH = os.environ.get("TF_MODEL_ZOO_PATH", "model_zoo") +TF_MODEL_ZOO_PATH = Path(os.environ.get("TF_MODEL_ZOO_PATH", "model_zoo")).expanduser().resolve() PPL_CONFIG = dict( name="lingbot_world_fast_stream", # LingBot-World-Fast reuses the Wan2.2 base weights (VAE + T5 text encoder + ``google/umt5-xxl`` # tokenizer), which ship in the shared Wan2.2-I2V-A14B directory. The DiT fast weights live in their - # own ``lingbot-world-fast`` directory, given as an absolute path so the pipeline keeps it standalone + # own ``lingbot-world-fast`` directory, passed as an absolute path so the pipeline keeps it standalone # rather than nesting it under ``checkpoint_dir``. - checkpoint_dir=TF_MODEL_ZOO_PATH + "/Wan2.2-I2V-A14B", - fast_checkpoint_subdir=TF_MODEL_ZOO_PATH + "/lingbot-world-fast", + checkpoint_dir=str(TF_MODEL_ZOO_PATH / "Wan2.2-I2V-A14B"), + fast_checkpoint_subdir=str(TF_MODEL_ZOO_PATH / "lingbot-world-fast"), control_type="cam", vae_device="cuda", vae_device_id=0, diff --git a/examples/wan_video/wan21_14b_image_to_video_480p_service.py b/examples/wan_video/wan21_14b_image_to_video_480p_service.py new file mode 100644 index 0000000..d06787d --- /dev/null +++ b/examples/wan_video/wan21_14b_image_to_video_480p_service.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +import os + +import torch +from PIL import Image + +from telefuser.core.config import AttentionConfig, AttnImplType, WeightOffloadType +from telefuser.core.module_manager import ModuleManager +from telefuser.pipelines.wan_video.wan21_video import ( + Wan21VideoPipeline, + Wan21VideoPipelineConfig, +) +from telefuser.service.core.contract_templates import build_pipeline_manifest, build_task_contract_template +from telefuser.utils.video import save_video + +TF_MODEL_ZOO_PATH = os.environ.get("TF_MODEL_ZOO_PATH", "model_zoo") +PPL_CONFIG = dict( + name="wan21_14B_i2v_480p_service", + model_root=TF_MODEL_ZOO_PATH + "/Wan2.1-I2V-14B-480P", + negative_prompt="Camera shake, overly saturated colors, overexposed, static, blurry details, subtitles, style, artwork, painting, frame, still, overall grayish, worst quality, low quality, JPEG compression artifacts, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn face, deformed, disfigured, malformed limbs, fused fingers, static frames, cluttered background, three legs, crowded background, walking backwards", + num_inference_steps=40, + num_frames=81, + resolution="480p", + cfg_scale=5.0, + cfg_skip_ratio=0.0, + seed=42, + tiled=False, + sample_solver="unipc", + attn_impl=AttnImplType.TORCH_SDPA, + sigma_shift=5.0, + target_fps=16, +) + +PIPELINE_MANIFEST = build_pipeline_manifest( + pipeline_name=PPL_CONFIG["name"], + supported_tasks=["i2v"], + task_contracts={ + "i2v": build_task_contract_template( + "i2v", + parameter_overrides={ + "prompt": { + "description": "Positive guidance text prompt for Wan2.1 I2V 480P inference.", + "required": True, + }, + "negative_prompt": { + "default": "", + "description": "Optional negative prompt appended before the built-in Wan2.1 negative prompt.", + }, + "seed": { + "default": PPL_CONFIG["seed"], + "description": "Random seed used for the denoising trajectory.", + }, + "resolution": { + "default": PPL_CONFIG["resolution"], + "description": "Output resolution for this service example.", + "enum": ["480p"], + }, + "target_video_length": { + "default": 5, + "description": "Accepted by the OpenAI-compatible API but ignored by this fixed-workload benchmark service.", + "exposed": False, + }, + }, + excluded_parameters=("aspect_ratio",), + ) + }, +) + + +def get_dit_path_list(model_root: str) -> list[str]: + """Generate DiT model shard paths based on model_root.""" + return [ + f"{model_root}/diffusion_pytorch_model-00001-of-00007.safetensors", + f"{model_root}/diffusion_pytorch_model-00002-of-00007.safetensors", + f"{model_root}/diffusion_pytorch_model-00003-of-00007.safetensors", + f"{model_root}/diffusion_pytorch_model-00004-of-00007.safetensors", + f"{model_root}/diffusion_pytorch_model-00005-of-00007.safetensors", + f"{model_root}/diffusion_pytorch_model-00006-of-00007.safetensors", + f"{model_root}/diffusion_pytorch_model-00007-of-00007.safetensors", + ] + + +def get_pipeline(parallelism: int = 1, model_root: str = PPL_CONFIG["model_root"]): + """Build Wan2.1 14B 480P I2V pipeline for TeleFuser service.""" + dit_path_list = get_dit_path_list(model_root) + module_manager = ModuleManager(device="cpu") + module_manager.load_models( + [f"{model_root}/models_clip_open-clip-xlm-roberta-large-vit-huge-14.pth"], + torch_dtype=torch.float16, + ) + module_manager.load_models([f"{model_root}/Wan2.1_VAE.pth"], torch_dtype=torch.bfloat16) + module_manager.load_models([dit_path_list], torch_dtype=torch.bfloat16) + module_manager.load_models([f"{model_root}/models_t5_umt5-xxl-enc-bf16.pth"], torch_dtype=torch.bfloat16) + + pipe = Wan21VideoPipeline(device="cuda", torch_dtype=torch.bfloat16) + pipe_config = Wan21VideoPipelineConfig() + pipe_config.dit_config.attention_config = AttentionConfig.dense_attention(PPL_CONFIG["attn_impl"]) + pipe_config.dit_config.offload_config.offload_type = WeightOffloadType.MODEL_CPU_OFFLOAD + pipe_config.clip_config.offload_config.offload_type = WeightOffloadType.MODEL_CPU_OFFLOAD + pipe_config.vae_config.offload_config.offload_type = WeightOffloadType.MODEL_CPU_OFFLOAD + pipe_config.text_encoding_config.offload_config.offload_type = WeightOffloadType.MODEL_CPU_OFFLOAD + pipe_config.sample_solver = PPL_CONFIG["sample_solver"] + pipe_config.enable_clip_stage = True + + if parallelism > 1: + cfg_scale = PPL_CONFIG["cfg_scale"] + if cfg_scale > 1: + pipe_config.dit_config.parallel_config.cfg_degree = 2 + pipe_config.dit_config.parallel_config.sp_ulysses_degree = parallelism // 2 + else: + pipe_config.dit_config.parallel_config.sp_ulysses_degree = parallelism + + pipe_config.dit_config.parallel_config.device_ids = list(range(parallelism)) + pipe_config.enable_denoising_parallel = True + + pipe.init(module_manager, pipe_config) + return pipe + + +def run( + pipeline, + image: Image.Image, + prompt: str, + negative_prompt: str = "", + seed: int = PPL_CONFIG["seed"], + resolution: str = PPL_CONFIG["resolution"], +): + """Convert a static image to a video sequence using the fixed 480P Wan2.1 benchmark workload.""" + if resolution != "480p": + raise ValueError(f"Unsupported benchmark resolution: {resolution}") + + # Keep TeleFuser aligned with the official Diffusers Wan2.1-I2V-14B-480P + # benchmark shape instead of the repo-wide 480p area heuristic. + width, height = 832, 480 + video = pipeline( + prompt=prompt, + input_image=image, + negative_prompt=f"{negative_prompt} {PPL_CONFIG['negative_prompt']}", + num_inference_steps=PPL_CONFIG["num_inference_steps"], + num_frames=PPL_CONFIG["num_frames"], + cfg_scale=PPL_CONFIG["cfg_scale"], + seed=seed, + tiled=PPL_CONFIG["tiled"], + height=height, + width=width, + sigma_shift=PPL_CONFIG["sigma_shift"], + ) + return video + + +def run_with_file( + pipeline, + image: Image.Image, + prompt: str, + negative_prompt: str, + seed: int, + output_path: str, + resolution: str = PPL_CONFIG["resolution"], + **kwargs, +) -> dict: + """Run the fixed benchmark workload and save the result to file.""" + video = run( + pipeline, + image, + prompt, + negative_prompt, + seed, + resolution, + ) + save_video( + video, + output_path, + fps=PPL_CONFIG["target_fps"], + quality=6, + ) + return {"output_path": str(output_path)} diff --git a/mkdocs.yml b/mkdocs.yml index 424456d..782d516 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -122,12 +122,14 @@ plugins: nav_translations: Home: 首页 User Guides: 用户指南 + Benchmarks: 基准测试 Parallel Inference: 并行推理 Developer Guides: 开发者指南 Configuration: 配置 Tools: 工具 Service: 服务指南 Stream Server: 流式服务 + TeleFuser and AIPerf Benchmark: TeleFuser 与 AIPerf Benchmark Service Metadata: 服务元数据 Adding New Example: 新增示例 Model Loading: 模型加载 @@ -163,6 +165,8 @@ nav: - adding_new_example.md - model_loading.md - offload.md + - Benchmarks: + - TeleFuser and AIPerf Benchmark: benchmark_aiperf.md - Configuration: - configuration.md - Parallel Inference: diff --git a/pyproject.toml b/pyproject.toml index 695d1ed..1b1dc79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,12 @@ dependencies = [ "loguru", "openai", "fastapi", + # ASGI server runtime used to actually host the FastAPI app for + # `telefuser serve` / `stream-serve` in telefuser/service/main.py. + "uvicorn[standard]>=0.34.0,<1", + # Multipart/form-data parser required for FastAPI file upload and HTML form + # endpoints in service/api/routers/tasks.py and service/api/openai/*.py. + "python-multipart>=0.0.20", "click", "ftfy", "ray", diff --git a/telefuser/models/__init__.py b/telefuser/models/__init__.py index cd38b5d..c6a0920 100644 --- a/telefuser/models/__init__.py +++ b/telefuser/models/__init__.py @@ -9,16 +9,20 @@ # Cross-file registration: LTX 2.3 shared checkpoint contains 3 models # that live in separate files with circular import dependencies, so it -# cannot be registered inside any one of them. -from telefuser.core.model_registry import register_model_config -from telefuser.models.ltx_dit import LTXVideoTransformer -from telefuser.models.ltx_gemma_text_encoder import LTXEmbeddingsProcessor -from telefuser.models.ltx_video_vae import LTXVideoVAE - -register_model_config( - None, - "f3a83ecf3995dcc4fae2d27e08ad5767", - ["ltx_embeddings_processor", "ltx_video_vae", "ltx_dit"], - [LTXEmbeddingsProcessor, LTXVideoVAE, LTXVideoTransformer], - "official", -) +# cannot be registered inside any one of them. Keep this optional so +# stream-serving pipelines do not fail to import when LTX extras are absent. +try: + from telefuser.core.model_registry import register_model_config + from telefuser.models.ltx_dit import LTXVideoTransformer + from telefuser.models.ltx_gemma_text_encoder import LTXEmbeddingsProcessor + from telefuser.models.ltx_video_vae import LTXVideoVAE +except Exception: + pass +else: + register_model_config( + None, + "f3a83ecf3995dcc4fae2d27e08ad5767", + ["ltx_embeddings_processor", "ltx_video_vae", "ltx_dit"], + [LTXEmbeddingsProcessor, LTXVideoVAE, LTXVideoTransformer], + "official", + ) diff --git a/telefuser/pipelines/lingbot_world_fast/pipeline.py b/telefuser/pipelines/lingbot_world_fast/pipeline.py index 4e22735..d08ce5a 100644 --- a/telefuser/pipelines/lingbot_world_fast/pipeline.py +++ b/telefuser/pipelines/lingbot_world_fast/pipeline.py @@ -98,7 +98,11 @@ def init(self, module_manager, config: LingBotWorldFastPipelineConfig) -> None: self.vae.load_state_dict(vae_state_dict, strict=False) self.vae = self.vae.to(device=self.vae_device, dtype=self.torch_dtype).eval() - fast_path = checkpoint_root / config.fast_checkpoint_subdir + fast_path = Path(config.fast_checkpoint_subdir).expanduser() + if not fast_path.is_absolute(): + fast_path = (checkpoint_root / fast_path).resolve() + else: + fast_path = fast_path.resolve() self.dit = LingBotWorldFastDiT.from_pretrained( str(fast_path), torch_dtype=config.dit_torch_dtype, diff --git a/telefuser/service/api/openai/adapter.py b/telefuser/service/api/openai/adapter.py index 9289c8a..91b72aa 100644 --- a/telefuser/service/api/openai/adapter.py +++ b/telefuser/service/api/openai/adapter.py @@ -153,6 +153,17 @@ def _video_to_task(req: VideoGenerationsRequest, task_type: str | None = None) - target_video_length = req.seconds or 4 aspect_ratio = ASPECT_RATIOS.get(req.size, "16:9") ref_path = req.input_reference or req.reference_url or "" + extra_fields: dict[str, Any] = {"model": req.model} + if req.size: + extra_fields["openai_video_size"] = req.size + try: + width, height = validate_image_size(req.size) + except ValueError: + pass + else: + extra_fields["width"] = width + extra_fields["height"] = height + aspect_ratio = infer_aspect_ratio(width, height) base_fields = { "task": task, @@ -170,8 +181,6 @@ def _video_to_task(req: VideoGenerationsRequest, task_type: str | None = None) - else: base_fields["first_image_path"] = ref_path - extra_fields = {"model": req.model} - return create_extended_task_request(base_fields, extra_fields) @staticmethod diff --git a/telefuser/service/core/pipeline_runner.py b/telefuser/service/core/pipeline_runner.py index dbc5dcd..2b4bbcb 100644 --- a/telefuser/service/core/pipeline_runner.py +++ b/telefuser/service/core/pipeline_runner.py @@ -47,6 +47,32 @@ def _coerce_output_path(value: Any) -> str | None: return str(value) +def _first_image_path(task_data: dict[str, Any]) -> Any | None: + return task_data.get("first_image_path") or task_data.get("image_path") + + +def _load_rgb_image(path: Any) -> Any: + from PIL import Image + + return Image.open(path).convert("RGB") + + +def _inject_media_aliases(kwargs: dict[str, Any], task_data: dict[str, Any]) -> None: + first_path = _first_image_path(task_data) + if first_path: + if "image" not in kwargs: + kwargs["image"] = _load_rgb_image(first_path) + kwargs.setdefault("image_path", first_path) + + last_path = task_data.get("last_image_path") + if last_path: + kwargs.setdefault("last_image_path", last_path) + + ref_video_path = task_data.get("ref_video_path") + if ref_video_path: + kwargs.setdefault("video_path", ref_video_path) + + def _select_kwargs( fn: Any, *, @@ -58,17 +84,22 @@ def _select_kwargs( Prefer passing full task_data when **kwargs is supported; otherwise pass a compatible subset and apply a small set of common alias conversions for orchestrator-based scripts. """ + kwargs: dict[str, Any] = {} try: sig = inspect.signature(fn) except (TypeError, ValueError): - return dict(task_data) - - params = list(sig.parameters.values()) - accepts_var_kw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params) - if accepts_var_kw: - return dict(task_data) + sig = None + + if sig is None: + kwargs.update(task_data) + params = [] + accepts_var_kw = True + else: + params = list(sig.parameters.values()) + accepts_var_kw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params) + if accepts_var_kw: + kwargs.update(task_data) - kwargs: dict[str, Any] = {} for p in params: if p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.VAR_POSITIONAL): continue @@ -85,17 +116,18 @@ def _select_kwargs( continue if name in ("image", "input_image"): - first_path = task_data.get("first_image_path") or task_data.get("image_path") + first_path = _first_image_path(task_data) if first_path: - from PIL import Image - - kwargs[name] = Image.open(first_path).convert("RGB") + kwargs[name] = _load_rgb_image(first_path) continue if name == "ppl_config" and module is not None and hasattr(module, "PPL_CONFIG"): kwargs[name] = getattr(module, "PPL_CONFIG") continue + if accepts_var_kw: + _inject_media_aliases(kwargs, task_data) + return kwargs diff --git a/telefuser/service/main.py b/telefuser/service/main.py index 35f4afe..b0c297f 100644 --- a/telefuser/service/main.py +++ b/telefuser/service/main.py @@ -8,6 +8,7 @@ from telefuser.core.config import TELEFUSER_LOGO from telefuser.service_types import TaskType from telefuser.utils.logging import logger +from telefuser.webrtc_ice import configure_ice_host_addresses from .core.config import server_config from .core.container import ServiceContainer @@ -88,6 +89,7 @@ def run_stream_server( Unlike run_server (request-response), this loads a stream pipeline that exposes get_service() and serves via WebRTC or WebSocket. """ + configure_ice_host_addresses() server_config.host = host server_config.port = port diff --git a/telefuser/webrtc_ice.py b/telefuser/webrtc_ice.py new file mode 100644 index 0000000..288670e --- /dev/null +++ b/telefuser/webrtc_ice.py @@ -0,0 +1,67 @@ +"""Helpers for constraining WebRTC ICE host candidate gathering.""" + +from __future__ import annotations + +import ipaddress +import os +from collections.abc import Iterable + + +def _normalize_host_ips(host_ips: Iterable[str]) -> list[str]: + normalized: list[str] = [] + seen: set[str] = set() + for raw_host_ip in host_ips: + host_ip = raw_host_ip.strip() + if not host_ip: + continue + canonical_host_ip = str(ipaddress.ip_address(host_ip)) + if canonical_host_ip in seen: + continue + seen.add(canonical_host_ip) + normalized.append(canonical_host_ip) + return normalized + + +def _matches_ip_version(host_ip: str, *, use_ipv4: bool, use_ipv6: bool) -> bool: + version = ipaddress.ip_address(host_ip).version + if version == 4: + return use_ipv4 + if version == 6: + return use_ipv6 + return False + + +def configure_ice_host_addresses(host_ips: Iterable[str] | None = None) -> None: + """Limit aiortc host candidate gathering to a small, explicit IP allowlist.""" + + if host_ips is None: + raw_host_ips = os.environ.get("TELEFUSER_WEBRTC_ICE_HOST_IPS", "") + if not raw_host_ips.strip(): + return + host_ips = raw_host_ips.split(",") + + allowed_host_ips = _normalize_host_ips(host_ips) + if not allowed_host_ips: + return + + try: + from aioice import ice as aioice_ice + except Exception: + return + + current_get_host_addresses = aioice_ice.get_host_addresses + if getattr(current_get_host_addresses, "_telefuser_host_filter", False): + return + + def _get_host_addresses(use_ipv4: bool, use_ipv6: bool) -> list[str]: + filtered = [ + host_ip + for host_ip in allowed_host_ips + if _matches_ip_version(host_ip, use_ipv4=use_ipv4, use_ipv6=use_ipv6) + ] + if filtered: + return filtered + return current_get_host_addresses(use_ipv4=use_ipv4, use_ipv6=use_ipv6) + + _get_host_addresses._telefuser_host_filter = True # type: ignore[attr-defined] + aioice_ice.get_host_addresses = _get_host_addresses diff --git a/tests/unit/openai/test_adapter.py b/tests/unit/openai/test_adapter.py index f0268b8..4a99543 100644 --- a/tests/unit/openai/test_adapter.py +++ b/tests/unit/openai/test_adapter.py @@ -65,6 +65,21 @@ def test_video_to_task_text_to_video(self): assert task_req.target_video_length == 5 assert task_req.aspect_ratio == "16:9" + def test_video_to_task_preserves_exact_openai_size(self): + """Keep exact WxH size available for fixed-workload benchmark services.""" + openai_req = VideoGenerationsRequest( + prompt="a cat playing", + seconds=5, + size="832x480", + ) + task_req = OpenAIRequestAdapter.to_task_request(openai_req) + + assert task_req.resolution == "480p" + assert task_req.openai_video_size == "832x480" + assert task_req.width == 832 + assert task_req.height == 480 + assert task_req.aspect_ratio == "26:15" + def test_video_to_task_image_to_video(self): """Convert I2V request (auto-detected from input).""" openai_req = VideoGenerationsRequest( diff --git a/tests/unit/service/test_pipeline_runner.py b/tests/unit/service/test_pipeline_runner.py new file mode 100644 index 0000000..727c8d8 --- /dev/null +++ b/tests/unit/service/test_pipeline_runner.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from pathlib import Path + +from PIL import Image + +from telefuser.service.core.pipeline_runner import _select_kwargs + + +def test_select_kwargs_injects_image_for_var_kwargs_pipeline(tmp_path: Path) -> None: + image_path = tmp_path / "input.png" + Image.new("RGB", (16, 16), color="white").save(image_path) + + def run_with_file(pipeline, prompt, output_path, **kwargs): + return None + + kwargs = _select_kwargs( + run_with_file, + task_data={ + "prompt": "test prompt", + "output_path": "out.mp4", + "first_image_path": str(image_path), + }, + module=None, + ) + + assert kwargs["prompt"] == "test prompt" + assert kwargs["output_path"] == "out.mp4" + assert kwargs["image_path"] == str(image_path) + assert isinstance(kwargs["image"], Image.Image) + assert kwargs["image"].size == (16, 16) From fff6298133b664817f75d88f59bb0c936fbe9082 Mon Sep 17 00:00:00 2001 From: ActivePeter <1020401660@qq.com> Date: Mon, 13 Jul 2026 23:09:43 +0800 Subject: [PATCH 2/2] backup --- .gitignore | 9 + CLAUDE.md | 8 +- .../baseline/diffusers_wan_i2v/README.md | 4 +- .../diffusers_wan_i2v/benchmark_contract.yaml | 40 +- .../scripts/run_video_bench.sh | 2 +- .../baseline/sglang_lingbot_stream/README.md | 217 + .../benchmark_contract.yaml | 103 + .../stream_lingbot_world_fast_compare.json | 34 + ..._lingbot_world_fast_h100_gpu_resident.json | 34 + .../stream_lingbot_world_fast_quick.json | 31 + .../stream_transport_mock_compare.json | 28 + .../configs/stream_transport_mock_quick.json | 28 + .../python_shims/addict/__init__.py | 44 + .../python_shims/cache_dit/__init__.py | 46 + .../cache_dit/caching/__init__.py | 1 + .../cache_dit/caching/block_adapters.py | 7 + .../cache_dit/caching/cache_contexts.py | 13 + .../python_shims/cache_dit/parallelism.py | 17 + .../python_shims/sitecustomize.py | 135 + .../python_shims/trimesh/__init__.py | 38 + .../scripts/run_mock_stream_service.py | 229 + .../scripts/run_mock_stream_service.sh | 21 + .../scripts/run_service.sh | 44 + .../scripts/run_stream_bench.py | 46 + .../scripts/run_stream_bench.sh | 27 + benchmarks/telefuser_aiperf/README.md | 110 - .../telefuser_aiperf/benchmark_contract.yaml | 40 +- .../stream_lingbot_world_fast_compare.json | 42 + .../stream_lingbot_world_fast_quick.json | 42 + .../stream_transport_mock_compare.json | 33 + .../configs/stream_transport_mock_quick.json | 33 + .../configs/video_generation_e2e.yaml | 2 +- .../data/stream_lingbot_controls.json | 52 + .../scripts/run_mock_webrtc_service.py | 209 + .../scripts/run_mock_webrtc_service.sh | 21 + .../scripts/run_stream_bench.py | 881 +--- .../scripts/run_stream_bench.sh | 34 +- .../scripts/run_video_bench.sh | 2 +- .../scripts/run_video_smoke.py | 175 + .../stream_benchmark_contract.yaml | 82 + docs/en/benchmark_aiperf.md | 140 +- docs/en/index.md | 2 +- docs/zh/benchmark_aiperf.md | 708 +-- docs/zh/benchmark_aiperf_design.md | 1351 +++--- docs/zh/benchmark_current_comparison.md | 184 + docs/zh/index.md | 5 +- scripts/setup_aiperf_repo.sh | 154 + telefuser/metrics/__init__.py | 15 + telefuser/metrics/collector.py | 9 +- telefuser/metrics/runtime.py | 139 + .../pipelines/lingbot_world_fast/service.py | 93 +- .../pipelines/lingbot_world_fast/session.py | 1 + telefuser/service/api/openai/adapter.py | 5 +- telefuser/service/api/openai/image_routes.py | 12 +- telefuser/service/api/openai/video_routes.py | 16 +- telefuser/service/api/schema.py | 2 + telefuser/service/core/file_service.py | 1 + telefuser/service/core/pipeline_service.py | 50 + .../service/core/stream_pipeline_service.py | 57 +- telefuser/service/core/task_manager.py | 15 +- telefuser/service/core/task_processor.py | 7 +- telefuser/service/core/task_service.py | 2 + telefuser/service/webrtc/track.py | 28 +- tests/integration/test_service_api.py | 11 +- tests/unit/distributed/test_pp_comm.py | 12 +- tests/unit/kernel/test_rmsnorm.py | 18 +- tests/unit/kernel/test_rotary.py | 10 +- tests/unit/kernel/test_scale_shift.py | 20 +- tests/unit/offload/test_async_offload.py | 6 + tests/unit/openai/test_adapter.py | 4 +- tests/unit/openai/test_video_routes.py | 71 + .../test_service_metrics.py | 87 + tests/unit/service/test_file_service.py | 17 + .../service/test_pipeline_service_metrics.py | 72 + .../service/test_stream_pipeline_metrics.py | 48 + tests/unit/service/test_task_runtime.py | 20 + tests/unit/service/test_webrtc_track.py | 31 + tests/unit/test_metrics.py | 3 + tests/unit/test_runtime_metrics.py | 34 + uv.lock | 4249 +++++++++++++++++ 80 files changed, 8632 insertions(+), 2041 deletions(-) create mode 100644 benchmarks/baseline/sglang_lingbot_stream/README.md create mode 100644 benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml create mode 100644 benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_compare.json create mode 100644 benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_h100_gpu_resident.json create mode 100644 benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json create mode 100644 benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json create mode 100644 benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_quick.json create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/addict/__init__.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/__init__.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/__init__.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/block_adapters.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/cache_contexts.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/parallelism.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/sitecustomize.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/python_shims/trimesh/__init__.py create mode 100755 benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.py create mode 100755 benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.sh create mode 100755 benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh create mode 100644 benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.py create mode 100644 benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh delete mode 100644 benchmarks/telefuser_aiperf/README.md create mode 100644 benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json create mode 100644 benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json create mode 100644 benchmarks/telefuser_aiperf/configs/stream_transport_mock_compare.json create mode 100644 benchmarks/telefuser_aiperf/configs/stream_transport_mock_quick.json create mode 100644 benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json create mode 100755 benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.py create mode 100755 benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.sh create mode 100644 benchmarks/telefuser_aiperf/scripts/run_video_smoke.py create mode 100644 benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml create mode 100644 docs/zh/benchmark_current_comparison.md create mode 100755 scripts/setup_aiperf_repo.sh create mode 100644 telefuser/metrics/runtime.py create mode 100644 tests/unit/pipelines/lingbot_world_fast/test_service_metrics.py create mode 100644 tests/unit/service/test_file_service.py create mode 100644 tests/unit/service/test_pipeline_service_metrics.py create mode 100644 tests/unit/service/test_stream_pipeline_metrics.py create mode 100644 tests/unit/service/test_webrtc_track.py create mode 100644 tests/unit/test_runtime_metrics.py create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 2f78881..26679a6 100755 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,10 @@ venv.bak/ .mypy_cache/ # custom +.tmp/ +/artifacts/ +/benchmarks/aiperf/ +/skills/ /models /data .vscode @@ -138,6 +142,11 @@ docs/**/modelzoo.md *.MP3 *.mp3 +# Source-controlled AIPerf stream benchmark profiles and control trace. +!benchmarks/telefuser_aiperf/configs/*.json +!benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json +!benchmarks/baseline/sglang_lingbot_stream/configs/*.json + project output work_dirs diff --git a/CLAUDE.md b/CLAUDE.md index e9b7cfb..308bf14 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ telefuser/ ├── feature_cache/ # Feature caching: AdaTaylorCache ├── cache/ # General cache management ├── offload/ # CPU offload strategies -├── metrics/ # Metrics collection and monitoring +├── metrics/ # Metrics collection, monitoring, and raw runtime facts ├── orchestrator/ # Pipeline orchestration ├── worker/ # Distributed worker management ├── entrypoints/ # CLI entry points @@ -108,6 +108,12 @@ TeleFuser's model follows a strict layered architecture for operations: - May use `torch.library.custom_op` for torch.compile compatibility - Only used by ops/ layer, never directly by models/ +### Benchmark Metrics Boundary + +- `telefuser/metrics/runtime.py` may measure synchronized target-side phase duration and allocator peaks. +- Target services emit only raw, bounded phase/chunk/runtime facts; AIPerf owns warmup exclusion, aggregation, semantic mapping, artifacts, and UI. +- Client delivery metrics and target compute metrics must remain distinct (`stream_fps` versus `chunk_compute_fps`). + ## Code Style - PEP8 with ruff (line length: 120) diff --git a/benchmarks/baseline/diffusers_wan_i2v/README.md b/benchmarks/baseline/diffusers_wan_i2v/README.md index def692a..983e2f6 100644 --- a/benchmarks/baseline/diffusers_wan_i2v/README.md +++ b/benchmarks/baseline/diffusers_wan_i2v/README.md @@ -51,10 +51,10 @@ curl http://127.0.0.1:8010/v1/service/health ## Run AIPerf -Install the vendored AIPerf first: +Set up the AIPerf dependency repository first: ```bash -pip install -e ./benchmarks/aiperf +bash scripts/setup_aiperf_repo.sh ``` Then run: diff --git a/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml b/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml index b206817..8236e3c 100644 --- a/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml +++ b/benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml @@ -1,40 +1,42 @@ -contract_version: v1 -name: diffusers_wan_i2v -mode: batch_video -implementation: diffusers -model_family: wan_video -model: Wan2.1-I2V-14B-480P-Diffusers -supported_tasks: +# Benchmark contract example for a batch-video baseline. +# Enum comments use "one-of". List comments use "one-or-more". +contract_version: v1 # one-of: v1. Bump only when the contract schema changes. +name: diffusers_wan_i2v # Stable benchmark target id used in reports and automation. +mode: batch_video # one-of: batch_video, stream_world. +implementation: diffusers # one-of for this PR: telefuser, diffusers, sglang_diffusion. Add new runtimes as new baselines land. +model_family: wan_video # Example values: wan_video, lingbot_world_fast, hunyuan_video, ltx_video. +model: Wan2.1-I2V-14B-480P-Diffusers # Concrete model or service profile under test. +supported_tasks: # one-or-more: t2v, i2v, ti2v, bidirectional. - i2v -transport: http +transport: http # one-of: http, http_polling, websocket, sse, webrtc. endpoint: - path: /v1/videos - protocol: openai_video_generation + path: /v1/videos # HTTP path used by the benchmark runner. + protocol: openai_video_generation # one-of for batch video: openai_video_generation. request_encoding: - content_type: multipart/form-data - media_inputs: - first_image_path: input_reference - parameters: + content_type: multipart/form-data # one-of: application/json, multipart/form-data. + media_inputs: # Mapping from dataset field to request field. + first_image_path: input_reference # Example: local image path becomes multipart input_reference. + parameters: # Mapping from benchmark parameter names to wire payload fields. prompt: prompt negative_prompt: negative_prompt seed: seed size: size seconds: seconds result_delivery: - polling_path: /v1/videos/{id} - content_path: /v1/videos/{id}/content + polling_path: /v1/videos/{id} # Async status polling path. + content_path: /v1/videos/{id}/content # Generated media download path. workload: - resolution: 832x480 + resolution: 832x480 # Example WxH string. Keep aligned across compared targets. frames: 81 inference_steps: 40 fps: 16 guidance_scale: 5.0 -metrics: +metrics: # one-or-more. Choose all metrics emitted by this benchmark mode. - request_latency - completion_latency - download_latency - request_throughput - success_rate -artifacts: +artifacts: # Paths consumed by automation and documentation. config: benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml dataset: benchmarks/telefuser_aiperf/data/video_prompts.jsonl diff --git a/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh b/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh index 16a19f2..9228ac1 100644 --- a/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh +++ b/benchmarks/baseline/diffusers_wan_i2v/scripts/run_video_bench.sh @@ -10,7 +10,7 @@ HEALTH_URL="${DIFFUSERS_WAN_AIPERF_HEALTH_URL:-${SERVER_URL}/v1/service/health}" AIPERF_BIN="${AIPERF_BIN:-aiperf}" if ! command -v "${AIPERF_BIN}" >/dev/null 2>&1; then - echo "aiperf is not installed. Set AIPERF_BIN or run: pip install -e ./benchmarks/aiperf" >&2 + echo "aiperf is not installed. Set AIPERF_BIN or run: bash scripts/setup_aiperf_repo.sh" >&2 exit 1 fi diff --git a/benchmarks/baseline/sglang_lingbot_stream/README.md b/benchmarks/baseline/sglang_lingbot_stream/README.md new file mode 100644 index 0000000..802d93e --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/README.md @@ -0,0 +1,217 @@ +# SGLang-Diffusion LingBot Stream Baseline + +This directory contains the SGLang-Diffusion stream-world baseline for the +TeleFuser benchmark suite. + +SGLang-Diffusion is served through the diffusion runtime in the main +`sgl-project/sglang` package (`sglang.multimodal_gen`). There is no separate +`sgl-project/SGLang-Diffusion` repository required for this adapter. + +The goal is to compare framework/runtime behavior for realtime world-model +serving: + +- TeleFuser `stream-serve` with `LingBot-World-Fast` +- SGLang-Diffusion `sglang serve` with `robbyant/lingbot-world-fast-diffusers` +- the same prompt, first frame, output size, FPS target, and timed control trace +- while adapting only the transport protocol and runtime framework + +SGLang-Diffusion uses WebSocket + MessagePack for this path, not WebRTC. The +adapter is implemented in AIPerf and maps the shared control trace into SGLang +`camera_actions` events before writing the common stream artifacts. + +## Layout + +- `benchmark_contract.yaml`: stream-world baseline contract +- `configs/stream_lingbot_world_fast_quick.json`: default quick benchmark config +- `configs/stream_lingbot_world_fast_h100_gpu_resident.json`: 1xH100 tuned + GPU-resident config +- `scripts/run_service.sh`: SGLang-Diffusion service launch helper +- `scripts/run_mock_stream_service.sh`: SGLang-style WebSocket mock stream helper +- `scripts/run_stream_bench.py`: compatibility launcher for AIPerf +- `scripts/run_stream_bench.sh`: thin AIPerf launch helper + +## Start The Service + +Install SGLang-Diffusion in the service environment first, then run: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh +``` + +For an offline model zoo path, override the model path: + +```bash +SGLANG_LINGBOT_MODEL_PATH= \ + bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh +``` + +If the `sglang` console script points at a stale Python path, run through the +environment's Python executable instead: + +```bash +SGLANG_PYTHON=/path/to/venv/bin/python \ +SGLANG_LINGBOT_MODEL_PATH= \ + bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh +``` + +The launcher prepends `python_shims/` to `PYTHONPATH` so the benchmark can run +against SGLang-Diffusion environments that omitted the small `addict.Dict` +configuration dependency. + +For a fair performance comparison against the TeleFuser default LingBot service, +run SGLang-Diffusion GPU-resident on one GPU first. Do not enable +`--dit-cpu-offload`, `--dit-layerwise-offload`, `--text-encoder-cpu-offload`, +`--vae-cpu-offload`, or `SGLANG_LINGBOT_NATIVE_NORM_FALLBACK=1` for the +performance baseline. The launcher defaults to `--performance-mode speed`; +`auto` may silently enable VAE layerwise offload even when the individual CPU +offload flags are false. If the GPU-resident configuration OOMs, record that +failure instead of relabeling an auto-offloaded run. CPU/offload settings are +useful for smoke testing the adapter and protocol path, but their metrics are +not directly comparable with TeleFuser's default GPU-resident result. + +If the SGLang service environment is missing an import-time dependency that is +available elsewhere, place only that dependency in a narrow directory and pass +it through `SGLANG_EXTRA_PYTHONPATH`. Avoid pointing this at a full +site-packages directory because it can override SGLang's own `torch`, `sglang`, +or CUDA packages. + +```bash +SGLANG_EXTRA_PYTHONPATH=/path/to/websockets-only \ +SGLANG_PYTHON=/path/to/venv/bin/python \ +SGLANG_LINGBOT_MODEL_PATH= \ + bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh +``` + +If the environment lacks `cuda.bindings`, set +`SGLANG_LINGBOT_NATIVE_NORM_FALLBACK=1` to force the LingBot norm shim onto +PyTorch-native scale/shift code. Keep this disabled on correctly provisioned +SGLang environments because it bypasses CuTe fused norm kernels. + +Default address: + +- `http://:30000` + +Health check: + +```bash +curl http://:30000/health +``` + +## Run The Benchmark + +Set up the AIPerf dependency repository first: + +```bash +bash scripts/setup_aiperf_repo.sh +``` + +Then run: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh +``` + +For the validated 1xH100 tuned workload, run: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh \ + benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_h100_gpu_resident.json +``` + +The launcher delegates to: + +```bash +uv run --project benchmarks/aiperf aiperf profile \ + --stream-config benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json +``` + +This uses the same control trace as the TeleFuser stream benchmark: + +- `benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json` + +The adapter maps: + +- `ArrowUp` to `w` +- `ArrowDown` to `s` +- `ArrowLeft` to `a` +- `ArrowRight` to `d` + +SGLang emits no separate control-ack message. The benchmark records control +acknowledgement latency when `chunk_stats.event_id` shows that a control event +has been sampled, and records control-to-next-frame latency when +`frame_batch.event_id` appears on a video batch. + +## Target Chunk Metrics + +AIPerf directly normalizes the native SGLang-Diffusion `chunk_stats` fields. It +maps scheduler forward time to target compute, payload-build time to encoding, +and separately retains request preparation, output pacing, header/payload/write +time, total chunk time, frames, batches, raw bytes, WebSocket payload bytes, and +content type. The same warmup exclusion, percentiles, weighted compute FPS, +JSONL artifacts, and HTML report used by TeleFuser are reused without adding an +AIPerf dependency to SGLang. + +The instrumented SGLang realtime endpoint also sends its existing reset-scoped +`OutputBatch.peak_memory_mb` fact. AIPerf maps it only to +`chunk_peak_reserved_bytes`, because SGLang obtains that value from +`max_memory_reserved()`; allocated peak remains unavailable. Pipeline/runtime +creation durations are still unavailable. The contract snapshots `/v1/models` +as descriptive model and pipeline identity. + +## Validated 1xH100 Result + +The 2026-07-13 validation used one 80GB H100, SGLang `performance_mode=speed`, +no CPU or layerwise offload, `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`, +and one warmup plus one measured session. The host's FlashInfer/CUDA compiler +headers were incompatible, so the documented RoPE fallback was enabled; this +environment qualification must remain attached to the result. + +The 9-sink / 18-frame cache geometry in the compare config failed in both +warmup and profiling with CUDA OOM at approximately 79.16 GiB process memory. +The tuned 6-sink / 9-frame config completed successfully: + +| Metric | Measured value | +|---|---:| +| Profile sessions | 1 / 1 | +| Frames / chunks | 93 / 8 | +| Steady chunks after warmup exclusion | 7 | +| Target compute FPS | 5.1906 | +| Client stream FPS | 5.6788 | +| First-frame latency | 3076.18 ms | +| Session runtime | 19.277 s | +| Chunk compute mean / p90 | 2.3119 / 2.3362 s | +| Chunk encode mean | 0.0551 s | +| Chunk total mean | 2.3699 s | +| Peak reserved allocator bytes | 76,919,341,056 (73,356 MiB) | + +The OOM and tuned success are different cache configurations and must not be +combined into a same-configuration comparison. Their AIPerf artifacts are under +`work_dirs/benchmarks/sglang_lingbot_stream/` in +`h100_gpu_resident_speed_official_cache_oom_20260713` and +`h100_gpu_resident_speed_rope_fallback_peak_20260713`, respectively. + +## Mock Stream Transport + +The mock service does not load SGLang or any diffusion model. It exposes the +same `/health`, `/v1/models`, and `/v1/realtime_video/generate` shape used by the +stream benchmark adapter, sends synthetic `frame_batch` payloads at a fixed FPS, +acknowledges controls, and emits synthetic native `chunk_stats` to validate the +mapping path. + +Start it with: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.sh \ + --host \ + --port +``` + +Then run: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh \ + benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json \ + --stream-server-url http://: +``` + +Use this only for streaming/transport overhead comparisons. diff --git a/benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml b/benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml new file mode 100644 index 0000000..9b7274b --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml @@ -0,0 +1,103 @@ +# Benchmark contract example for a WebSocket stream-world baseline. +# Enum comments use "one-of". List comments use "one-or-more". +contract_version: v1 # one-of: v1. Bump only when the contract schema changes. +name: sglang_lingbot_world_stream # Stable benchmark target id used in reports and automation. +mode: stream_world # one-of: batch_video, stream_world. +implementation: sglang_diffusion # one-of for this PR: telefuser, diffusers, sglang_diffusion. +model_family: lingbot_world_fast # Example values: wan_video, lingbot_world_fast, hunyuan_video, ltx_video. +model: robbyant/lingbot-world-fast-diffusers # Concrete model or service profile under test. +supported_tasks: # one-or-more: t2v, i2v, ti2v, bidirectional. + - bidirectional +transport: websocket # one-of: http, http_polling, websocket, sse, webrtc. +adapter: sglang_websocket # Built-in AIPerf adapter; target repository owns no transport implementation. +endpoint: + health_path: /health # Service readiness path. + metadata_path: /v1/models # Optional SGLang model and pipeline identity snapshot. + websocket_path: /v1/realtime_video/generate # WebSocket path for realtime video sessions. + models_path: /v1/models # Optional model metadata path. +request_encoding: + message_format: msgpack # one-of for this baseline: msgpack. + init_required_fields: # one-or-more. Required fields in the first WebSocket message. + - type + - prompt + - first_frame + - size + - fps + - num_frames + init_parameters: # Mapping from benchmark parameter names to SGLang init payload fields. + model: model + prompt: prompt + image_path: first_frame + size: size + fps: fps + num_frames: num_frames + control_channel: + transport: websocket_message # one-of for WebSocket: websocket_message. + message_type: event # Runtime control messages use type=event. + kind: camera_actions # one-of for LingBot realtime controls: camera_actions, prompt. + payload_mode: state # one-of for camera_actions: script, state. + action_tokens: # one-or-more. SGLang LingBot camera action tokens. + - w + - a + - s + - d + - i + - j + - k + - l + key_mapping: + ArrowUp: w + ArrowDown: s + ArrowLeft: a + ArrowRight: d +result_delivery: + media: websocket_frame_batch # one-of for this baseline: websocket_frame_batch. + metadata: websocket_chunk_stats # one-of for this baseline: websocket_chunk_stats. + session_log: sessions.jsonl # Per-session result records. + event_log: events/{phase}_{logical_session_index}_{session_id}.jsonl # Per-session event trace template. +workload: + mode: bidirectional # one-of: server_push, bidirectional. + task: bidirectional # one-of for this service: bidirectional. + size: 832x480 + fps: 16 + session_count: 1 + warmup_sessions: 0 + session_duration_s: 90.0 + control_trace: benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json # Timed control-event workload. + request_extra: # Service-specific request config passed through to SGLang. + num_frames: 9 + num_inference_steps: 4 + guidance_scale: 1.0 + realtime_output_format: webp # one-of: webp, jpeg, raw. + output_compression: 95 + max_chunks: 8 +metrics: # one-or-more. Choose all metrics emitted by this benchmark mode. + - offer_rtt_ms + - connected_latency_ms + - first_frame_latency_ms + - first_metadata_latency_ms + - stream_fps + - session_runtime_s + - frames_received + - control_ack_latency_ms + - control_to_next_frame_latency_ms + - chunk_request_prepare_seconds + - chunk_compute_seconds + - chunk_encode_seconds + - chunk_output_pacing_seconds + - chunk_output_header_write_seconds + - chunk_output_payload_write_seconds + - chunk_output_write_seconds + - chunk_total_seconds + - chunk_compute_fps + - chunk_raw_output_bytes + - chunk_wire_output_bytes + - chunk_output_batches + - chunk_peak_reserved_bytes + - success_rate +limits: + active_sessions: 1 # Current SGLang realtime endpoint accepts one active session for this target. +artifacts: # Paths consumed by automation and documentation. + config: benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_compare.json + control_trace: benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json + runner: aiperf profile --stream-config diff --git a/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_compare.json b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_compare.json new file mode 100644 index 0000000..725dc0e --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_compare.json @@ -0,0 +1,34 @@ +{ + "contract": "benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml", + "server_url": "http://127.0.0.1:30000", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "walk forward through the scene", + "image_path": "examples/data/1.png", + "fps": 16, + "session_count": 1, + "warmup_sessions": 1, + "warmup_chunks": 1, + "session_duration_s": 90.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "num_frames": 9, + "num_inference_steps": 4, + "guidance_scale": 1.0, + "realtime_causal_sink_size": 9, + "realtime_causal_kv_cache_num_frames": 18, + "realtime_output_format": "webp", + "output_compression": 95, + "max_chunks": 8, + "realtime_output_pacing": false + }, + "transport": { + "connect_timeout_s": 60.0, + "message_timeout_s": 180.0 + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/sglang_lingbot_stream/stream_lingbot_compare" +} diff --git a/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_h100_gpu_resident.json b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_h100_gpu_resident.json new file mode 100644 index 0000000..540e393 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_h100_gpu_resident.json @@ -0,0 +1,34 @@ +{ + "contract": "benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml", + "server_url": "http://127.0.0.1:30100", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "walk forward through the scene", + "image_path": "examples/data/1.png", + "fps": 16, + "session_count": 1, + "warmup_sessions": 1, + "warmup_chunks": 1, + "session_duration_s": 90.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "num_frames": 9, + "num_inference_steps": 4, + "guidance_scale": 1.0, + "realtime_causal_sink_size": 6, + "realtime_causal_kv_cache_num_frames": 9, + "realtime_output_format": "webp", + "output_compression": 95, + "max_chunks": 8, + "realtime_output_pacing": false + }, + "transport": { + "connect_timeout_s": 60.0, + "message_timeout_s": 180.0 + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/sglang_lingbot_stream/stream_lingbot_h100_gpu_resident" +} diff --git a/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json new file mode 100644 index 0000000..aad6492 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json @@ -0,0 +1,31 @@ +{ + "contract": "benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml", + "server_url": "http://127.0.0.1:30000", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "walk forward through the scene", + "image_path": "examples/data/1.png", + "fps": 16, + "session_count": 1, + "warmup_sessions": 0, + "session_duration_s": 12.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "num_frames": 9, + "num_inference_steps": 4, + "guidance_scale": 1.0, + "realtime_output_format": "webp", + "output_compression": 95, + "max_chunks": 8, + "realtime_output_pacing": false + }, + "transport": { + "connect_timeout_s": 30.0, + "message_timeout_s": 120.0 + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/sglang_lingbot_stream/stream_lingbot_quick" +} diff --git a/benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json new file mode 100644 index 0000000..5580310 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json @@ -0,0 +1,28 @@ +{ + "contract": "benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml", + "server_url": "http://127.0.0.1:30000", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "mock streaming transport benchmark", + "image_path": null, + "fps": 16, + "session_count": 4, + "warmup_sessions": 1, + "session_duration_s": 20.0, + "stagger_s": 0.25, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "num_frames": 1, + "max_chunks": 320, + "frame_payload_bytes": 32768, + "control_ack_delay_ms": 0.0 + }, + "transport": { + "connect_timeout_s": 30.0, + "message_timeout_s": 30.0 + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/sglang_lingbot_stream/stream_transport_mock_compare" +} diff --git a/benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_quick.json b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_quick.json new file mode 100644 index 0000000..7c535f0 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_quick.json @@ -0,0 +1,28 @@ +{ + "contract": "benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml", + "server_url": "http://127.0.0.1:30000", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "mock streaming transport smoke", + "image_path": null, + "fps": 16, + "session_count": 1, + "warmup_sessions": 0, + "session_duration_s": 6.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "num_frames": 1, + "max_chunks": 96, + "frame_payload_bytes": 32768, + "control_ack_delay_ms": 0.0 + }, + "transport": { + "connect_timeout_s": 20.0, + "message_timeout_s": 20.0 + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/sglang_lingbot_stream/stream_transport_mock_quick" +} diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/addict/__init__.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/addict/__init__.py new file mode 100644 index 0000000..164d07f --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/addict/__init__.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + + +class Dict(dict): + """Minimal addict.Dict compatibility used by SGLang-Diffusion config parsing.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__() + self.update(*args, **kwargs) + + def __getattr__(self, name: str) -> Any: + try: + return self[name] + except KeyError as exc: + raise AttributeError(name) from exc + + def __setattr__(self, name: str, value: Any) -> None: + self[name] = self._wrap(value) + + def __delattr__(self, name: str) -> None: + try: + del self[name] + except KeyError as exc: + raise AttributeError(name) from exc + + def update(self, *args: Any, **kwargs: Any) -> None: # type: ignore[override] + items = dict(*args, **kwargs) + for key, value in items.items(): + self[key] = self._wrap(value) + + @classmethod + def _wrap(cls, value: Any) -> Any: + if isinstance(value, Dict): + return value + if isinstance(value, Mapping): + return cls(value) + if isinstance(value, list): + return [cls._wrap(item) for item in value] + if isinstance(value, tuple): + return tuple(cls._wrap(item) for item in value) + return value diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/__init__.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/__init__.py new file mode 100644 index 0000000..70486b4 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/__init__.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +class BlockAdapter: + def __init__(self, *args: Any, **kwargs: Any) -> None: + self.args = args + self.kwargs = kwargs + + +class DBCacheConfig: + def __init__(self, *args: Any, **kwargs: Any) -> None: + self.args = args + self.kwargs = kwargs + + def reset(self, **kwargs: Any) -> dict[str, Any]: + return kwargs + + +class ForwardPattern: + Pattern_2 = "Pattern_2" + + +class ParamsModifier: + def __init__(self, *args: Any, **kwargs: Any) -> None: + self.args = args + self.kwargs = kwargs + + +@dataclass +class TaylorSeerCalibratorConfig: + order: int = 1 + + +def steps_mask(*, total_steps: int, **_: Any) -> list[int]: + return [1] * total_steps + + +def enable_cache(*_: Any, **__: Any) -> None: + raise RuntimeError("cache_dit is not installed; SGLang cache-dit acceleration is unavailable") + + +def refresh_context(*_: Any, **__: Any) -> None: + raise RuntimeError("cache_dit is not installed; SGLang cache-dit acceleration is unavailable") diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/__init__.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/__init__.py new file mode 100644 index 0000000..9d48db4 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/__init__.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/block_adapters.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/block_adapters.py new file mode 100644 index 0000000..42d2e86 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/block_adapters.py @@ -0,0 +1,7 @@ +from __future__ import annotations + + +class BlockAdapterRegister: + @staticmethod + def is_supported(_transformer: object) -> bool: + return False diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/cache_contexts.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/cache_contexts.py new file mode 100644 index 0000000..c67ccab --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/caching/cache_contexts.py @@ -0,0 +1,13 @@ +from __future__ import annotations + + +class _CachedContextManager: + def similarity(self, *_args: object, **_kwargs: object) -> bool: + return False + + +class _CacheManager: + CachedContextManager = _CachedContextManager + + +cache_manager = _CacheManager() diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/parallelism.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/parallelism.py new file mode 100644 index 0000000..820f8d8 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/cache_dit/parallelism.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +class ParallelismBackend: + AUTO = "auto" + + +@dataclass +class ParallelismConfig: + backend: str + ulysses_size: int | None = None + ring_size: int | None = None + tp_size: int | None = None + extra: dict[str, Any] | None = None diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/sitecustomize.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/sitecustomize.py new file mode 100644 index 0000000..58f647c --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/sitecustomize.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import os + + +def _patch_transformers_utils() -> None: + try: + import transformers.utils as transformers_utils + except Exception: + return + + defaults = { + "FLAX_WEIGHTS_NAME": "flax_model.msgpack", + } + for name, value in defaults.items(): + if not hasattr(transformers_utils, name): + setattr(transformers_utils, name, value) + + +_patch_transformers_utils() + + +def _patch_sglang_cutedsl_norm_fallback() -> None: + enabled = os.environ.get("SGLANG_LINGBOT_NATIVE_NORM_FALLBACK", "").lower() + if enabled not in {"1", "true", "yes", "on"}: + return + + try: + import torch + from sglang.multimodal_gen.runtime.layers import layernorm + except Exception: + return + + def align_modulation(value, target): + while value.dim() > target.dim(): + squeezed = False + for dim in range(1, value.dim() - 1): + if value.shape[dim] == 1: + value = value.squeeze(dim) + squeezed = True + break + if not squeezed: + break + while value.dim() < target.dim(): + value = value.unsqueeze(1) + return value + + def apply_scale_shift(normalized, scale, shift): + scale = align_modulation(scale, normalized) + shift = align_modulation(shift, normalized) + return normalized * (1 + scale) + shift + + def scale_residual_norm_scale_shift_cuda(self, residual, x, gate, shift, scale): + if isinstance(gate, int): + if gate != 1: + raise ValueError( + f"Only gate value of 1 is supported for int type, but got {gate}" + ) + residual_output = residual + x + elif isinstance(gate, torch.Tensor): + if gate.dim() == 4: + num_frames = gate.shape[1] + frame_seqlen = x.shape[1] // num_frames + gated = x.unflatten(dim=1, sizes=(num_frames, frame_seqlen)) * gate + residual_output = residual + gated.flatten(1, 2) + else: + residual_output = residual + x * gate + else: + raise ValueError(f"Gate type {type(gate)} not supported") + + normalized = self.norm(residual_output) + modulated = apply_scale_shift(normalized, scale, shift) + return modulated, residual_output + + def norm_scale_shift_cuda(self, x, shift, scale): + normalized = self.norm(x) + return apply_scale_shift(normalized, scale, shift).to(x.dtype) + + def norm_tanh_mul_add_cuda(self, x, scale, shift): + return (self.norm(x) * torch.tanh(scale) + shift).to(x.dtype) + + scale_residual_class = getattr(layernorm, "_ScaleResidualNormScaleShift", None) + if scale_residual_class is not None: + scale_residual_class.forward_cuda = scale_residual_norm_scale_shift_cuda + + norm_scale_class = getattr(layernorm, "_NormScaleShift", None) + if norm_scale_class is not None: + norm_scale_class.forward_cuda = norm_scale_shift_cuda + + norm_tanh_class = getattr(layernorm, "_NormTanhMulAdd", None) + if norm_tanh_class is not None: + norm_tanh_class.forward_cuda = norm_tanh_mul_add_cuda + + def apply_rmsnorm_tanh_mul_add_native(x, gate, residual, norm): + return residual + torch.tanh(gate) * norm(x) + + layernorm.apply_rmsnorm_tanh_mul_add = apply_rmsnorm_tanh_mul_add_native + + +_patch_sglang_cutedsl_norm_fallback() + + +def _patch_sglang_flashinfer_rope_fallback() -> None: + enabled = os.environ.get("SGLANG_LINGBOT_DISABLE_FLASHINFER_ROPE", "").lower() + if enabled not in {"1", "true", "yes", "on"}: + return + + try: + from sglang.multimodal_gen.runtime.layers.rotary_embedding import utils + except Exception: + return + + utils._flashinfer_apply_rope_inplace = None + utils.flashinfer_apply_rope_inplace = None + + +_patch_sglang_flashinfer_rope_fallback() + + +def _patch_lingbot_optional_components() -> None: + try: + from sglang.multimodal_gen.runtime.pipelines.lingbot_world_causal_dmd_pipeline import ( + LingBotWorldCausalDMDPipeline, + ) + except Exception: + return + + LingBotWorldCausalDMDPipeline._required_config_modules = [ + module + for module in LingBotWorldCausalDMDPipeline._required_config_modules + if module not in {"image_encoder", "image_processor"} + ] + + +_patch_lingbot_optional_components() diff --git a/benchmarks/baseline/sglang_lingbot_stream/python_shims/trimesh/__init__.py b/benchmarks/baseline/sglang_lingbot_stream/python_shims/trimesh/__init__.py new file mode 100644 index 0000000..f4430aa --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/python_shims/trimesh/__init__.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from typing import Any + + +class Trimesh: + def __init__(self, vertices: Any = None, faces: Any = None, **kwargs: Any) -> None: + self.vertices = vertices + self.faces = faces + self.kwargs = kwargs + self.visual = None + + +class Scene: + def dump(self, *, concatenate: bool = False) -> Any: + raise RuntimeError("trimesh is not installed; 3D mesh utilities are unavailable") + + +class _SimpleMaterial: + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +class _TextureVisuals: + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +class _MaterialModule: + SimpleMaterial = _SimpleMaterial + + +class _VisualModule: + TextureVisuals = _TextureVisuals + material = _MaterialModule() + + +visual = _VisualModule() diff --git a/benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.py b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.py new file mode 100755 index 0000000..86d53d5 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""Run a SGLang-style mock realtime stream server without model inference.""" + +from __future__ import annotations + +import argparse +import asyncio +import time +from dataclasses import dataclass +from typing import Any + +try: + import msgspec.msgpack +except ImportError as exc: # pragma: no cover - runtime dependency check + msgpack = None + MSGPACK_IMPORT_ERROR = exc +else: # pragma: no cover + msgpack = msgspec.msgpack + MSGPACK_IMPORT_ERROR = None + + +@dataclass +class MockServerConfig: + fps: int = 16 + frames_per_chunk: int = 1 + max_chunks: int = 192 + frame_payload_bytes: int = 32768 + control_ack_delay_ms: float = 0.0 + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=30000) + parser.add_argument("--fps", type=int, default=16) + parser.add_argument("--frames-per-chunk", type=int, default=1) + parser.add_argument("--max-chunks", type=int, default=192) + parser.add_argument("--frame-payload-bytes", type=int, default=32768) + parser.add_argument("--control-ack-delay-ms", type=float, default=0.0) + return parser + + +def _int_option(payload: dict[str, Any], key: str, default: int) -> int: + value = payload.get(key, default) + try: + return int(value) + except (TypeError, ValueError): + return default + + +def _float_option(payload: dict[str, Any], key: str, default: float) -> float: + value = payload.get(key, default) + try: + return float(value) + except (TypeError, ValueError): + return default + + +def create_app(config: MockServerConfig): + try: + from fastapi import FastAPI, WebSocket, WebSocketDisconnect + except ImportError as exc: # pragma: no cover - runtime dependency check + raise RuntimeError("fastapi is required for the mock stream server.") from exc + + globals()["WebSocket"] = WebSocket + globals()["WebSocketDisconnect"] = WebSocketDisconnect + app = FastAPI(title="SGLang Mock Stream Transport") + + @app.get("/health") + async def health() -> dict[str, Any]: + return {"status": "ok", "model_loaded": False, "server": "mock_stream_transport"} + + @app.get("/v1/models") + async def models() -> dict[str, Any]: + return { + "object": "list", + "data": [ + { + "id": "mock-sglang-diffusion", + "object": "model", + "owned_by": "aiperf-mock", + "pipeline_class": "MockRealtimePipeline", + "model_loaded": False, + } + ], + } + + @app.websocket("/v1/realtime_video/generate") + async def realtime_video(websocket: WebSocket) -> None: + if msgpack is None: + raise RuntimeError("msgspec is required for the mock stream server.") from MSGPACK_IMPORT_ERROR + + await websocket.accept() + send_lock = asyncio.Lock() + pending_frame_event_ids: asyncio.Queue[int] = asyncio.Queue() + disconnected = asyncio.Event() + raw_init = await websocket.receive_bytes() + init_payload = msgpack.decode(raw_init) + if not isinstance(init_payload, dict): + init_payload = {} + + fps = _int_option(init_payload, "fps", config.fps) + frames_per_chunk = max(1, _int_option(init_payload, "num_frames", config.frames_per_chunk)) + max_chunks = max(1, _int_option(init_payload, "max_chunks", config.max_chunks)) + payload_bytes = max(0, _int_option(init_payload, "frame_payload_bytes", config.frame_payload_bytes)) + ack_delay_ms = max(0.0, _float_option(init_payload, "control_ack_delay_ms", config.control_ack_delay_ms)) + frame_payload = b"0" * payload_bytes + + async def _safe_send(payload: dict[str, Any]) -> None: + async with send_lock: + await websocket.send_bytes(msgpack.encode(payload)) + + async def _receive_controls() -> None: + while not disconnected.is_set(): + try: + raw_message = await websocket.receive_bytes() + except WebSocketDisconnect: + disconnected.set() + return + except Exception: + disconnected.set() + return + try: + message = msgpack.decode(raw_message) + except Exception: + continue + if not isinstance(message, dict): + continue + event_id = message.get("event_id") + if event_id is None: + continue + event_id = int(event_id) + await pending_frame_event_ids.put(event_id) + if ack_delay_ms > 0: + await asyncio.sleep(ack_delay_ms / 1000.0) + await _safe_send( + { + "type": "chunk_stats", + "event_id": event_id, + "stage": "control_state", + "model_loaded": False, + "timestamp": time.time(), + } + ) + + receiver_task = asyncio.create_task(_receive_controls()) + frame_interval_s = frames_per_chunk / max(fps, 1) + try: + for chunk_index in range(max_chunks): + if disconnected.is_set(): + break + await asyncio.sleep(frame_interval_s) + event_id = None + try: + event_id = pending_frame_event_ids.get_nowait() + except asyncio.QueueEmpty: + pass + await _safe_send( + { + "type": "frame_batch", + "chunk_index": chunk_index, + "event_id": event_id, + "num_frames": frames_per_chunk, + "is_final_frame_batch": True, + "content_type": "application/octet-stream", + "total_size": len(frame_payload), + "payload": frame_payload, + "model_loaded": False, + "timestamp": time.time(), + } + ) + await _safe_send( + { + "type": "chunk_stats", + "session_id": "mock-sglang-session", + "chunk_index": chunk_index, + "event_id": event_id, + "request_prepare_ms": 1, + "scheduler_forward_ms": 1, + "pace_wait_ms": 0, + "header_write_ms": 1, + "raw_payload_build_ms": 1, + "raw_write_ms": 1, + "ws_write_ms": 2, + "chunk_total_ms": 5, + "num_batches": 1, + "num_frames": frames_per_chunk, + "raw_bytes": len(frame_payload), + "ws_payload_bytes": len(frame_payload), + "content_type": "application/octet-stream", + "memory_device": "cuda:0", + "peak_memory_mb": 256, + "model_loaded": False, + "timestamp": time.time(), + } + ) + finally: + disconnected.set() + receiver_task.cancel() + try: + await receiver_task + except asyncio.CancelledError: + pass + try: + await websocket.close() + except Exception: + pass + + return app + + +def main() -> None: + args = _build_parser().parse_args() + try: + import uvicorn + except ImportError as exc: # pragma: no cover - runtime dependency check + raise RuntimeError("uvicorn is required for the mock stream server.") from exc + config = MockServerConfig( + fps=args.fps, + frames_per_chunk=args.frames_per_chunk, + max_chunks=args.max_chunks, + frame_payload_bytes=args.frame_payload_bytes, + control_ack_delay_ms=args.control_ack_delay_ms, + ) + uvicorn.run(create_app(config), host=args.host, port=args.port, log_level="info") + + +if __name__ == "__main__": + main() diff --git a/benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.sh b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.sh new file mode 100755 index 0000000..54211a0 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" +cd "${ROOT_DIR}" +AIPERF_SRC="${AIPERF_SRC:-${ROOT_DIR}/benchmarks/aiperf/src}" +if [[ ! -d "${AIPERF_SRC}/aiperf" ]]; then + echo "AIPerf source checkout not found at ${AIPERF_SRC}." >&2 + echo "Run: bash scripts/setup_aiperf_repo.sh" >&2 + exit 1 +fi +export PYTHONPATH="${AIPERF_SRC}:${ROOT_DIR}${PYTHONPATH:+:${PYTHONPATH}}" + +PYTHON_BIN="${SGLANG_MOCK_STREAM_PYTHON:-python3}" +HOST="${SGLANG_MOCK_STREAM_HOST:-127.0.0.1}" +PORT="${SGLANG_MOCK_STREAM_PORT:-30000}" + +exec "${PYTHON_BIN}" benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.py \ + --host "${HOST}" \ + --port "${PORT}" \ + "$@" diff --git a/benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh new file mode 100755 index 0000000..fac0e55 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" +cd "${ROOT_DIR}" + +SHIM_DIR="${ROOT_DIR}/benchmarks/baseline/sglang_lingbot_stream/python_shims" +PYTHONPATH_PREFIX="${SHIM_DIR}" +if [[ -n "${SGLANG_EXTRA_PYTHONPATH:-}" ]]; then + PYTHONPATH_PREFIX="${PYTHONPATH_PREFIX}:${SGLANG_EXTRA_PYTHONPATH}" +fi +export PYTHONPATH="${PYTHONPATH_PREFIX}${PYTHONPATH:+:${PYTHONPATH}}" + +SGLANG_BIN="${SGLANG_BIN:-sglang}" +SGLANG_PYTHON="${SGLANG_PYTHON:-}" +SERVICE_PORT="${SGLANG_LINGBOT_PORT:-30000}" +MODEL_PATH="${SGLANG_LINGBOT_MODEL_PATH:-robbyant/lingbot-world-fast-diffusers}" +MODEL_ID="${SGLANG_LINGBOT_MODEL_ID:-lingbot-world-fast-diffusers}" +MODEL_TYPE="${SGLANG_LINGBOT_MODEL_TYPE:-diffusion}" +PIPELINE_CLASS="${SGLANG_LINGBOT_PIPELINE_CLASS:-LingBotWorldCausalDMDPipeline}" +PERFORMANCE_MODE="${SGLANG_LINGBOT_PERFORMANCE_MODE:-speed}" +NUM_GPUS="${SGLANG_LINGBOT_NUM_GPUS:-4}" +ULYSSES_DEGREE="${SGLANG_LINGBOT_ULYSSES_DEGREE:-4}" +DIT_CPU_OFFLOAD="${SGLANG_LINGBOT_DIT_CPU_OFFLOAD:-false}" +TEXT_ENCODER_CPU_OFFLOAD="${SGLANG_LINGBOT_TEXT_ENCODER_CPU_OFFLOAD:-false}" + +if [[ -n "${SGLANG_PYTHON}" ]]; then + SGLANG_CMD=("${SGLANG_PYTHON}" -c "from sglang.cli.main import main; main()") +else + read -r -a SGLANG_CMD <<< "${SGLANG_BIN}" +fi + +exec "${SGLANG_CMD[@]}" serve \ + --model-type "${MODEL_TYPE}" \ + --model-path "${MODEL_PATH}" \ + --model-id "${MODEL_ID}" \ + --pipeline-class-name "${PIPELINE_CLASS}" \ + --performance-mode "${PERFORMANCE_MODE}" \ + --port "${SERVICE_PORT}" \ + --num-gpus "${NUM_GPUS}" \ + --ulysses-degree "${ULYSSES_DEGREE}" \ + --dit-cpu-offload "${DIT_CPU_OFFLOAD}" \ + --text-encoder-cpu-offload "${TEXT_ENCODER_CPU_OFFLOAD}" \ + "$@" diff --git a/benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.py b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.py new file mode 100644 index 0000000..74a7b20 --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Compatibility launcher that delegates SGLang stream profiling to AIPerf.""" + +from __future__ import annotations + +import argparse +import os +from pathlib import Path + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--config", + default=( + "benchmarks/baseline/sglang_lingbot_stream/configs/" + "stream_lingbot_world_fast_quick.json" + ), + ) + parser.add_argument("--server-url") + parser.add_argument("--artifacts-dir") + args, remainder = parser.parse_known_args() + + root = Path(__file__).resolve().parents[4] + aiperf_repo = Path( + os.environ.get("AIPERF_REPO", root / "benchmarks" / "aiperf") + ) + command = [ + os.environ.get("AIPERF_UV_BIN", "uv"), + "run", + "--project", + str(aiperf_repo), + "aiperf", + "profile", + "--stream-config", + args.config, + ] + if args.server_url: + command.extend(["--stream-server-url", args.server_url]) + if args.artifacts_dir: + command.extend(["--stream-artifacts-dir", args.artifacts_dir]) + os.execvp(command[0], [*command, *remainder]) + + +if __name__ == "__main__": + main() diff --git a/benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh new file mode 100644 index 0000000..61c62ba --- /dev/null +++ b/benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" +cd "${ROOT_DIR}" + +AIPERF_REPO="${AIPERF_REPO:-${ROOT_DIR}/benchmarks/aiperf}" +UV_BIN="${AIPERF_UV_BIN:-uv}" +CONFIG_PATH="${1:-benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json}" +if [[ $# -gt 0 ]]; then + shift +fi + +SERVER_URL="${SGLANG_STREAM_BENCH_URL:-http://127.0.0.1:30000}" +SERVER_ARGS=(--stream-server-url "${SERVER_URL}") +for argument in "$@"; do + if [[ "${argument}" == "--stream-server-url" || "${argument}" == --stream-server-url=* ]]; then + SERVER_ARGS=() + break + fi +done + +exec "${UV_BIN}" run --project "${AIPERF_REPO}" \ + aiperf profile \ + --stream-config "${CONFIG_PATH}" \ + "${SERVER_ARGS[@]}" \ + "$@" diff --git a/benchmarks/telefuser_aiperf/README.md b/benchmarks/telefuser_aiperf/README.md deleted file mode 100644 index 795ecd7..0000000 --- a/benchmarks/telefuser_aiperf/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# TeleFuser AIPerf Benchmarks - -This directory contains TeleFuser-specific benchmark assets for running end-to-end video serving tests with AIPerf. - -Current scope: -- `telefuser serve` batch video generation over the OpenAI-compatible `/v1/videos` API -- `telefuser stream-serve` WebRTC end-to-end session benchmarking -- End-to-end latency, throughput, HTTP trace, frame-level latency, and optional server-metrics scraping - -## Layout - -- `configs/`: AIPerf YAML configs for batch video serving and JSON configs for stream benchmarking -- `data/`: Prompt files and control traces -- `scripts/`: Helper scripts for launching batch and stream benchmarks - -## Prerequisites - -1. Install TeleFuser and start a batch video server. -2. Install AIPerf from the vendored clone under `benchmarks/aiperf`. - -Example TeleFuser startup: - -```bash -telefuser serve \ - examples/wan_video/wan21_14b_image_to_video_h100.py \ - --port 8000 \ - --task i2v -``` - -Example AIPerf install: - -```bash -pip install -e ./benchmarks/aiperf -``` - -## Quick Start - -### Batch Video - -Run a small benchmark against TeleFuser `/v1/videos`: - -```bash -bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh -``` - -This uses: -- `configs/video_generation_quick.yaml` -- file-backed prompts from `data/video_prompts.jsonl` -- TeleFuser server at `http://127.0.0.1:8000` - -### Stream Serve - -Start a TeleFuser stream server first: - -```bash -telefuser stream-serve \ - examples/lingbot/stream_lingbot_world_fast.py \ - -p 8088 \ - --skip-validation -``` - -Then run the WebRTC benchmark: - -```bash -bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh -``` - -This uses: -- `configs/stream_lingbot_world_fast_quick.json` -- control trace from `data/stream_lingbot_controls.json` -- TeleFuser stream server at `http://127.0.0.1:8088` - -If no explicit ICE allowlist is provided, `run_stream_bench.py` will auto-detect a non-loopback local IP for WebRTC host candidate gathering. Only pin `--ice-host-ip` or `TELEFUSER_WEBRTC_ICE_HOST_IPS` when you need a specific address. - -The script records: -- SDP offer / answer RTT -- WebRTC connect latency -- first-frame latency -- steady-state stream FPS -- control acknowledge latency -- control-to-next-frame latency -- the helper raises the open-file limit to `8192` by default; override with `TELEFUSER_BENCH_NOFILE_LIMIT` if needed - -## Config Notes - -TeleFuser and AIPerf line up well for batch video serving: -- TeleFuser exposes `/v1/videos`, `/v1/videos/{id}`, and `/v1/videos/{id}/content` -- AIPerf `video_generation` already speaks the same async submit/poll/download flow - -The provided configs intentionally: -- use `endpoint.type: video_generation` -- enable `downloadVideoContent` only in the full E2E variant -- point `serverMetrics.urls` at `/v1/service/metrics` only when metrics scraping is desired -- do not enable AIPerf's built-in readiness probe, because TeleFuser batch video serving does not expose a stable `/v1/models` or chat-style readiness path for `video_generation` - -Instead, the helper script checks: - -```bash -curl http://127.0.0.1:8000/v1/service/health -``` - -## Stream Scope Notes - -The current stream benchmark is a TeleFuser-specific harness layered next to AIPerf, not inside AIPerf core yet. - -Reasons: -- `telefuser stream-serve` is WebRTC session based, not plain HTTP request / response -- world-model streaming needs frame-level and control-loop metrics -- current `LingBotWorldFastService` only allows one active session at a time, so the harness supports concurrency parameters but this pipeline is effectively single-session today -- `python3 scripts/remote_bench_sync.py stream-bootstrap` will auto-detect the usable model zoo root and create a repo-local `model_zoo` symlink on the remote host, so the default relative path works without extra manual env setup diff --git a/benchmarks/telefuser_aiperf/benchmark_contract.yaml b/benchmarks/telefuser_aiperf/benchmark_contract.yaml index ef0110f..54a89a9 100644 --- a/benchmarks/telefuser_aiperf/benchmark_contract.yaml +++ b/benchmarks/telefuser_aiperf/benchmark_contract.yaml @@ -1,40 +1,42 @@ -contract_version: v1 -name: telefuser_aiperf_video -mode: batch_video -implementation: telefuser -model_family: wan_video -model: Wan2.1-I2V-14B-480P -supported_tasks: +# Benchmark contract example for a batch-video target. +# Enum comments use "one-of". List comments use "one-or-more". +contract_version: v1 # one-of: v1. Bump only when the contract schema changes. +name: telefuser_aiperf_video # Stable benchmark target id used in reports and automation. +mode: batch_video # one-of: batch_video, stream_world. +implementation: telefuser # one-of for this PR: telefuser, diffusers, sglang_diffusion. Add new runtimes as new baselines land. +model_family: wan_video # Example values: wan_video, lingbot_world_fast, hunyuan_video, ltx_video. +model: Wan2.1-I2V-14B-480P # Concrete model or service profile under test. +supported_tasks: # one-or-more: t2v, i2v, ti2v, bidirectional. - i2v -transport: http +transport: http # one-of: http, http_polling, websocket, sse, webrtc. endpoint: - path: /v1/videos - protocol: openai_video_generation + path: /v1/videos # HTTP path used by the benchmark runner. + protocol: openai_video_generation # one-of for batch video: openai_video_generation. request_encoding: - content_type: multipart/form-data - media_inputs: - first_image_path: input_reference - parameters: + content_type: multipart/form-data # one-of: application/json, multipart/form-data. + media_inputs: # Mapping from dataset field to request field. + first_image_path: input_reference # Example: local image path becomes multipart input_reference. + parameters: # Mapping from benchmark parameter names to wire payload fields. prompt: prompt negative_prompt: negative_prompt seed: seed size: size seconds: seconds result_delivery: - polling_path: /v1/videos/{id} - content_path: /v1/videos/{id}/content + polling_path: /v1/videos/{id} # Async status polling path. + content_path: /v1/videos/{id}/content # Generated media download path. workload: - resolution: 832x480 + resolution: 832x480 # Example WxH string. Keep aligned across compared targets. frames: 81 inference_steps: 40 fps: 16 guidance_scale: 5.0 -metrics: +metrics: # one-or-more. Choose all metrics emitted by this benchmark mode. - request_latency - completion_latency - download_latency - request_throughput - success_rate -artifacts: +artifacts: # Paths consumed by automation and documentation. config: benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml dataset: benchmarks/telefuser_aiperf/data/video_prompts.jsonl diff --git a/benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json b/benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json new file mode 100644 index 0000000..9aa5a58 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json @@ -0,0 +1,42 @@ +{ + "contract": "benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml", + "server_url": "http://127.0.0.1:8088", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "walk forward through the scene", + "image_path": "examples/data/1.png", + "fps": 16, + "session_count": 1, + "warmup_sessions": 0, + "warmup_chunks": 1, + "session_duration_s": 90.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "chunk_size": 3, + "frame_num": 81, + "sample_shift": 5.0, + "control_mode": "cam", + "show_control_hud": true, + "benchmark_metrics": true + }, + "transport": { + "connect_timeout_s": 60.0, + "frame_timeout_s": 180.0, + "ice_gather_timeout_s": 5.0, + "shutdown_timeout_s": 5.0, + "receive_audio": false + }, + "server_metrics": { + "enabled": true, + "urls": [ + "http://127.0.0.1:8088/v1/service/metrics" + ], + "collection_interval_s": 1.0, + "export_raw_jsonl": true + }, + "observability": { + "mapping": "builtin:telefuser" + }, + "artifacts_dir": "artifacts/telefuser_aiperf/stream_lingbot_compare" +} diff --git a/benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json b/benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json new file mode 100644 index 0000000..4060c68 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json @@ -0,0 +1,42 @@ +{ + "contract": "benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml", + "server_url": "http://127.0.0.1:8088", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "walk forward through the scene", + "image_path": "examples/data/1.png", + "fps": 16, + "session_count": 1, + "warmup_sessions": 0, + "warmup_chunks": 1, + "session_duration_s": 12.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "chunk_size": 3, + "frame_num": 81, + "sample_shift": 5.0, + "control_mode": "cam", + "show_control_hud": true, + "benchmark_metrics": true + }, + "transport": { + "connect_timeout_s": 30.0, + "frame_timeout_s": 60.0, + "ice_gather_timeout_s": 5.0, + "shutdown_timeout_s": 5.0, + "receive_audio": false + }, + "server_metrics": { + "enabled": true, + "urls": [ + "http://127.0.0.1:8088/v1/service/metrics" + ], + "collection_interval_s": 1.0, + "export_raw_jsonl": true + }, + "observability": { + "mapping": "builtin:telefuser" + }, + "artifacts_dir": "artifacts/telefuser_aiperf/stream_lingbot_quick" +} diff --git a/benchmarks/telefuser_aiperf/configs/stream_transport_mock_compare.json b/benchmarks/telefuser_aiperf/configs/stream_transport_mock_compare.json new file mode 100644 index 0000000..8503ae6 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/stream_transport_mock_compare.json @@ -0,0 +1,33 @@ +{ + "contract": "benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml", + "server_url": "http://127.0.0.1:8088", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "mock streaming transport benchmark", + "fps": 16, + "session_count": 4, + "warmup_sessions": 1, + "session_duration_s": 20.0, + "stagger_s": 0.25, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "frame_width": 320, + "frame_height": 180, + "frame_num": 320, + "frames_per_chunk": 1, + "jpeg_quality": 80, + "preencoded_frames": 16, + "ack_controls": true + }, + "transport": { + "connect_timeout_s": 30.0, + "frame_timeout_s": 30.0, + "ice_gather_timeout_s": 5.0, + "shutdown_timeout_s": 5.0, + "receive_audio": false + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/telefuser_aiperf/stream_transport_mock_compare" +} diff --git a/benchmarks/telefuser_aiperf/configs/stream_transport_mock_quick.json b/benchmarks/telefuser_aiperf/configs/stream_transport_mock_quick.json new file mode 100644 index 0000000..bc36549 --- /dev/null +++ b/benchmarks/telefuser_aiperf/configs/stream_transport_mock_quick.json @@ -0,0 +1,33 @@ +{ + "contract": "benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml", + "server_url": "http://127.0.0.1:8088", + "mode": "bidirectional", + "task": "bidirectional", + "prompt": "mock streaming transport smoke", + "fps": 16, + "session_count": 1, + "warmup_sessions": 0, + "session_duration_s": 6.0, + "stagger_s": 0.0, + "control_trace_path": "benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json", + "request_extra": { + "frame_width": 320, + "frame_height": 180, + "frame_num": 96, + "frames_per_chunk": 1, + "jpeg_quality": 80, + "preencoded_frames": 16, + "ack_controls": true + }, + "transport": { + "connect_timeout_s": 20.0, + "frame_timeout_s": 20.0, + "ice_gather_timeout_s": 5.0, + "shutdown_timeout_s": 5.0, + "receive_audio": false + }, + "server_metrics": { + "enabled": false + }, + "artifacts_dir": "artifacts/telefuser_aiperf/stream_transport_mock_quick" +} diff --git a/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml b/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml index be4d057..a61e103 100644 --- a/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml +++ b/benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml @@ -49,5 +49,5 @@ benchmark: serverMetrics: enabled: ${TELEFUSER_AIPERF_SERVER_METRICS:true} urls: - - ${TELEFUSER_AIPERF_METRICS_URL:http://127.0.0.1:8000/metrics} + - ${TELEFUSER_AIPERF_METRICS_URL:http://127.0.0.1:8000/v1/service/metrics} formats: [json, csv] diff --git a/benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json b/benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json new file mode 100644 index 0000000..1b5b5d3 --- /dev/null +++ b/benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json @@ -0,0 +1,52 @@ +{ + "events": [ + { + "delay_s": 1.0, + "message": { + "type": "control", + "key": "ArrowUp", + "action": "press" + } + }, + { + "delay_s": 1.8, + "message": { + "type": "control", + "key": "ArrowUp", + "action": "release" + } + }, + { + "delay_s": 2.8, + "message": { + "type": "control", + "key": "ArrowLeft", + "action": "press" + } + }, + { + "delay_s": 3.6, + "message": { + "type": "control", + "key": "ArrowLeft", + "action": "release" + } + }, + { + "delay_s": 4.6, + "message": { + "type": "control", + "key": "ArrowRight", + "action": "press" + } + }, + { + "delay_s": 5.4, + "message": { + "type": "control", + "key": "ArrowRight", + "action": "release" + } + } + ] +} diff --git a/benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.py b/benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.py new file mode 100755 index 0000000..ced5bf9 --- /dev/null +++ b/benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +"""Run a TeleFuser-style mock WebRTC stream server without model inference.""" + +from __future__ import annotations + +import argparse +import asyncio +import fractions +import json +import time +from dataclasses import dataclass +from typing import Any + +import av +import numpy as np +import uvicorn +from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack +from aiortc.mediastreams import MediaStreamError +from fastapi import FastAPI, HTTPException, Request + +_RTP_CLOCK_RATE = 90_000 + + +@dataclass +class MockWebRTCConfig: + fps: int = 16 + frame_width: int = 320 + frame_height: int = 180 + frame_num: int = 320 + ack_controls: bool = True + + +class SyntheticVideoTrack(VideoStreamTrack): + """Synthetic video track paced at a fixed FPS.""" + + def __init__(self, *, fps: int, width: int, height: int, frame_num: int) -> None: + super().__init__() + self._fps = max(1, int(fps)) + self._width = int(width) + self._height = int(height) + self._frame_num = int(frame_num) + self._frame_interval_s = 1.0 / self._fps + self._pts_per_frame = _RTP_CLOCK_RATE // self._fps + self._last_frame_at: float | None = None + self._frame_index = 0 + + async def recv(self) -> av.VideoFrame: + if self._frame_index >= self._frame_num: + raise MediaStreamError("Synthetic stream ended") + if self._last_frame_at is not None: + delay = self._frame_interval_s - (time.perf_counter() - self._last_frame_at) + if delay > 0: + await asyncio.sleep(delay) + + image = np.zeros((self._height, self._width, 3), dtype=np.uint8) + image[:, :, 0] = (24 + self._frame_index) % 255 + image[: max(8, self._height // 8), :, 1] = 180 + image[:, : max(8, self._width // 12), 2] = 220 + frame = av.VideoFrame.from_ndarray(image, format="rgb24") + frame.pts = self._frame_index * self._pts_per_frame + frame.time_base = fractions.Fraction(1, _RTP_CLOCK_RATE) + self._frame_index += 1 + self._last_frame_at = time.perf_counter() + return frame + + +def _get_option(payload: dict[str, Any], key: str, default: Any) -> Any: + nested = payload.get("config") + if isinstance(nested, dict) and key in nested: + return nested[key] + return payload.get(key, default) + + +def create_app(default_config: MockWebRTCConfig) -> FastAPI: + app = FastAPI(title="TeleFuser Mock WebRTC Stream") + sessions: dict[str, RTCPeerConnection] = {} + + @app.get("/v1/service/health") + async def health() -> dict[str, Any]: + return { + "status": "ok", + "stream_ready": True, + "model_loaded": False, + "active_sessions": len(sessions), + } + + @app.post("/v1/stream/webrtc/offer") + async def offer(request: Request) -> dict[str, Any]: + body = await request.json() + session_id = str(body.get("session_id") or f"mock-{int(time.time() * 1000)}") + if session_id in sessions: + raise HTTPException(status_code=409, detail=f"Session already exists: {session_id}") + + fps = int(_get_option(body, "fps", default_config.fps)) + width = int(_get_option(body, "frame_width", default_config.frame_width)) + height = int(_get_option(body, "frame_height", default_config.frame_height)) + frame_num = int(_get_option(body, "frame_num", default_config.frame_num)) + ack_controls = bool(_get_option(body, "ack_controls", default_config.ack_controls)) + + pc = RTCPeerConnection() + sessions[session_id] = pc + pc.addTrack(SyntheticVideoTrack(fps=fps, width=width, height=height, frame_num=frame_num)) + + @pc.on("datachannel") + def _on_datachannel(channel) -> None: + @channel.on("open") + def _on_open() -> None: + try: + channel.send( + json.dumps( + { + "type": "chunk", + "session_id": session_id, + "data": {"stage": "session_started", "model_loaded": False}, + "timestamp": time.time(), + } + ) + ) + except Exception: + pass + + @channel.on("message") + def _on_message(message) -> None: + if not ack_controls: + return + try: + payload = json.loads(message) if isinstance(message, str) else {} + except json.JSONDecodeError: + payload = {} + try: + channel.send( + json.dumps( + { + "type": "chunk", + "session_id": session_id, + "data": { + "stage": "control_state", + "control": payload, + "model_loaded": False, + }, + "timestamp": time.time(), + } + ) + ) + except Exception: + pass + + @pc.on("connectionstatechange") + async def _on_connectionstatechange() -> None: + if pc.connectionState in {"failed", "closed", "disconnected"}: + await _close_session(session_id) + + try: + await pc.setRemoteDescription(RTCSessionDescription(sdp=body["sdp"], type=body.get("type", "offer"))) + answer = await pc.createAnswer() + await pc.setLocalDescription(answer) + except Exception: + await _close_session(session_id) + raise + + return { + "session_id": session_id, + "sdp": pc.localDescription.sdp, + "type": pc.localDescription.type, + } + + async def _close_session(session_id: str) -> bool: + pc = sessions.pop(session_id, None) + if pc is None: + return False + await pc.close() + return True + + @app.delete("/v1/stream/webrtc/{session_id}") + async def delete(session_id: str) -> dict[str, Any]: + closed = await _close_session(session_id) + if not closed: + raise HTTPException(status_code=404, detail=f"Session not found: {session_id}") + return {"session_id": session_id, "status": "closed"} + + return app + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=8088) + parser.add_argument("--fps", type=int, default=16) + parser.add_argument("--frame-width", type=int, default=320) + parser.add_argument("--frame-height", type=int, default=180) + parser.add_argument("--frame-num", type=int, default=320) + parser.add_argument("--no-ack-controls", action="store_true") + return parser + + +def main() -> None: + args = _build_parser().parse_args() + config = MockWebRTCConfig( + fps=args.fps, + frame_width=args.frame_width, + frame_height=args.frame_height, + frame_num=args.frame_num, + ack_controls=not args.no_ack_controls, + ) + uvicorn.run(create_app(config), host=args.host, port=args.port, log_level="info") + + +if __name__ == "__main__": + main() diff --git a/benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.sh b/benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.sh new file mode 100755 index 0000000..6ebee37 --- /dev/null +++ b/benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "${ROOT_DIR}" +AIPERF_SRC="${AIPERF_SRC:-${ROOT_DIR}/benchmarks/aiperf/src}" +if [[ ! -d "${AIPERF_SRC}/aiperf" ]]; then + echo "AIPerf source checkout not found at ${AIPERF_SRC}." >&2 + echo "Run: bash scripts/setup_aiperf_repo.sh" >&2 + exit 1 +fi +export PYTHONPATH="${AIPERF_SRC}:${ROOT_DIR}${PYTHONPATH:+:${PYTHONPATH}}" + +PYTHON_BIN="${TELEFUSER_MOCK_WEBRTC_PYTHON:-python3}" +HOST="${TELEFUSER_MOCK_WEBRTC_HOST:-127.0.0.1}" +PORT="${TELEFUSER_MOCK_WEBRTC_PORT:-8088}" + +exec "${PYTHON_BIN}" benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.py \ + --host "${HOST}" \ + --port "${PORT}" \ + "$@" diff --git a/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py index 6b3bda8..c67ca2d 100644 --- a/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py +++ b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.py @@ -1,871 +1,44 @@ #!/usr/bin/env python3 -"""Run an end-to-end WebRTC stream benchmark against ``telefuser stream-serve``. - -This benchmark is intentionally separate from the vendored AIPerf core because -TeleFuser world-model streaming uses WebRTC session lifecycles instead of plain -HTTP request/response flows. -""" +"""Compatibility launcher that delegates stream profiling to AIPerf.""" from __future__ import annotations import argparse -import asyncio -import json import os -import subprocess -import sys -import time -import urllib.error -import urllib.request -import uuid -from collections import deque -from dataclasses import asdict, dataclass, field from pathlib import Path -from typing import Any - -try: - from aiortc import RTCConfiguration, RTCIceServer, RTCPeerConnection, RTCSessionDescription -except ImportError as exc: # pragma: no cover - runtime dependency check - RTCConfiguration = None - RTCIceServer = None - RTCPeerConnection = None - RTCSessionDescription = None - AIORTC_IMPORT_ERROR = exc -else: # pragma: no cover - exercised only when dependency exists - AIORTC_IMPORT_ERROR = None - -from telefuser.webrtc_ice import configure_ice_host_addresses - - -DEFAULT_CONTROL_TRACE = [ - {"delay_s": 1.0, "message": {"type": "control", "key": "ArrowUp", "action": "press"}}, - {"delay_s": 1.8, "message": {"type": "control", "key": "ArrowUp", "action": "release"}}, - {"delay_s": 2.8, "message": {"type": "control", "key": "ArrowLeft", "action": "press"}}, - {"delay_s": 3.6, "message": {"type": "control", "key": "ArrowLeft", "action": "release"}}, - {"delay_s": 4.6, "message": {"type": "control", "key": "ArrowRight", "action": "press"}}, - {"delay_s": 5.4, "message": {"type": "control", "key": "ArrowRight", "action": "release"}}, -] - - -@dataclass -class BenchConfig: - server_url: str = "http://127.0.0.1:8088" - offer_path: str = "/v1/stream/webrtc/offer" - delete_path_template: str = "/v1/stream/webrtc/{session_id}" - mode: str = "bidirectional" - task: str = "bidirectional" - prompt: str = "walk forward through the scene" - image_path: str | None = None - fps: int = 16 - session_count: int = 1 - warmup_sessions: int = 0 - session_duration_s: float = 12.0 - stagger_s: float = 0.0 - connect_timeout_s: float = 30.0 - frame_timeout_s: float = 60.0 - ice_gather_timeout_s: float = 5.0 - shutdown_timeout_s: float = 5.0 - receive_audio: bool = False - control_trace_path: str | None = None - ice_host_ips: list[str] | None = None - request_extra: dict[str, Any] = field(default_factory=dict) - artifacts_dir: str = "artifacts/telefuser_aiperf/stream_bench" - turn_url: str | None = None - turn_username: str | None = None - turn_credential: str | None = None - force_turn_relay: bool = False - print_events: bool = False - - -@dataclass -class ControlEventResult: - index: int - scheduled_delay_s: float - message: dict[str, Any] - sent_offset_s: float - ack_latency_ms: float | None = None - next_frame_latency_ms: float | None = None - - -@dataclass -class SessionResult: - logical_session_index: int - phase: str - mode: str - planned_session_id: str - session_id: str - success: bool = False - error: str | None = None - offer_rtt_ms: float | None = None - connected_latency_ms: float | None = None - first_frame_latency_ms: float | None = None - first_metadata_latency_ms: float | None = None - session_runtime_s: float | None = None - frames_received: int = 0 - metadata_messages: int = 0 - status_messages: int = 0 - done_received: bool = False - stream_fps: float | None = None - last_status_stage: str | None = None - control_events: list[ControlEventResult] = field(default_factory=list) - artifacts_event_file: str | None = None - - -def _parse_json_value(raw: str) -> Any: - try: - return json.loads(raw) - except json.JSONDecodeError: - return raw -def _build_parser() -> argparse.ArgumentParser: +def main() -> None: parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--config", help="Optional JSON config file") - parser.add_argument("--server-url", default=argparse.SUPPRESS, help="TeleFuser stream server base URL") - parser.add_argument("--mode", choices=("server_push", "bidirectional"), default=argparse.SUPPRESS) - parser.add_argument("--task", default=argparse.SUPPRESS, help="Request task field sent in the WebRTC offer body") - parser.add_argument("--prompt", default=argparse.SUPPRESS, help="Prompt sent in the initial offer body") - parser.add_argument("--image-path", default=argparse.SUPPRESS, help="Image path visible to the server process") - parser.add_argument("--fps", type=int, default=argparse.SUPPRESS, help="Requested stream FPS") - parser.add_argument("--session-count", type=int, default=argparse.SUPPRESS, help="Number of profiled sessions") - parser.add_argument("--warmup-sessions", type=int, default=argparse.SUPPRESS, help="Warmup sessions before profile") - parser.add_argument("--session-duration-s", type=float, default=argparse.SUPPRESS) - parser.add_argument("--stagger-s", type=float, default=argparse.SUPPRESS) - parser.add_argument("--connect-timeout-s", type=float, default=argparse.SUPPRESS) - parser.add_argument("--frame-timeout-s", type=float, default=argparse.SUPPRESS) - parser.add_argument("--ice-gather-timeout-s", type=float, default=argparse.SUPPRESS) - parser.add_argument("--shutdown-timeout-s", type=float, default=argparse.SUPPRESS) - parser.add_argument("--control-trace-path", default=argparse.SUPPRESS, help="JSON file with timed control events") parser.add_argument( - "--ice-host-ip", - action="append", - default=argparse.SUPPRESS, - help="Allowlisted ICE host IP for candidate gathering. Repeat to allow multiple addresses.", - ) - parser.add_argument("--request-extra-json", default=argparse.SUPPRESS, help="Inline JSON merged into request body") - parser.add_argument("--artifacts-dir", default=argparse.SUPPRESS, help="Artifact root directory") - parser.add_argument("--turn-url", default=argparse.SUPPRESS, help="TURN server URL for WebRTC ICE") - parser.add_argument("--turn-username", default=argparse.SUPPRESS) - parser.add_argument("--turn-credential", default=argparse.SUPPRESS) - parser.add_argument("--receive-audio", action=argparse.BooleanOptionalAction, default=argparse.SUPPRESS) - parser.add_argument("--force-turn-relay", action=argparse.BooleanOptionalAction, default=argparse.SUPPRESS) - parser.add_argument("--print-events", action=argparse.BooleanOptionalAction, default=argparse.SUPPRESS) - return parser - - -def _load_config(args: argparse.Namespace) -> BenchConfig: - merged: dict[str, Any] = asdict(BenchConfig()) - if args.config: - file_data = json.loads(Path(args.config).read_text(encoding="utf-8")) - if not isinstance(file_data, dict): - raise ValueError(f"Config file must contain a JSON object: {args.config}") - merged.update(file_data) - - cli_values = vars(args).copy() - cli_values.pop("config", None) - request_extra_json = cli_values.pop("request_extra_json", None) - if request_extra_json is not None: - cli_values["request_extra"] = _parse_json_value(request_extra_json) - if "ice_host_ip" in cli_values: - cli_values["ice_host_ips"] = cli_values.pop("ice_host_ip") - - merged.update(cli_values) - request_extra = merged.get("request_extra") or {} - if not isinstance(request_extra, dict): - raise ValueError("request_extra must be a JSON object") - merged["request_extra"] = request_extra - return BenchConfig(**merged) - - -def _detect_default_ice_host_ips() -> list[str] | None: - """Return a small list of non-loopback host IPs for local ICE gathering.""" - - try: - result = subprocess.run( - ["hostname", "-I"], - check=True, - capture_output=True, - text=True, - ) - except Exception: - return None - - ips: list[str] = [] - for raw_ip in result.stdout.split(): - ip = raw_ip.strip() - if not ip or ip.startswith("127.") or ip == "::1": - continue - if ip not in ips: - ips.append(ip) - - return ips or None - - -def _build_rtc_configuration(config: BenchConfig): - if RTCConfiguration is None or RTCIceServer is None: - raise RuntimeError( - "aiortc is required for stream benchmarking. Install TeleFuser with `pip install -e \".[webrtc]\"`." - ) from AIORTC_IMPORT_ERROR - - ice_servers: list[Any] = [] - if config.turn_url: - ice_server_kwargs = {"urls": config.turn_url} - if config.turn_username: - ice_server_kwargs["username"] = config.turn_username - if config.turn_credential: - ice_server_kwargs["credential"] = config.turn_credential - ice_servers.append(RTCIceServer(**ice_server_kwargs)) - # aiortc 1.14.0 stalls on this benchmark's recvonly video + DataChannel - # flow when MAX_BUNDLE is forced. Use the default bundle policy instead. - return RTCConfiguration(iceServers=ice_servers or None) - - -async def _http_json_request(url: str, *, method: str, timeout_s: float, payload: dict | None = None) -> dict: - headers = {"Content-Type": "application/json"} - data = None if payload is None else json.dumps(payload).encode("utf-8") - request = urllib.request.Request(url=url, data=data, headers=headers, method=method) - - def _run() -> dict: - try: - with urllib.request.urlopen(request, timeout=timeout_s) as response: - body = response.read() - return json.loads(body.decode("utf-8")) if body else {} - except urllib.error.HTTPError as exc: - body = exc.read().decode("utf-8", errors="replace") - raise RuntimeError(f"HTTP {exc.code} {exc.reason}: {body}") from exc - except urllib.error.URLError as exc: - raise RuntimeError(f"Failed to reach {url}: {exc.reason}") from exc - - return await asyncio.to_thread(_run) - - -async def _wait_for_ice_complete(peer_connection, timeout_s: float) -> bool: - deadline = time.perf_counter() + timeout_s - while time.perf_counter() < deadline: - if peer_connection.iceGatheringState == "complete": - return True - await asyncio.sleep(0.05) - return peer_connection.iceGatheringState == "complete" - - -def _load_control_trace(path: str | None, mode: str) -> list[dict[str, Any]]: - if mode != "bidirectional": - return [] - if path is None: - return DEFAULT_CONTROL_TRACE - - payload = json.loads(Path(path).read_text(encoding="utf-8")) - events = payload.get("events", payload) if isinstance(payload, dict) else payload - if not isinstance(events, list): - raise ValueError(f"Control trace must be a list or {{\"events\": [...]}}: {path}") - normalised: list[dict[str, Any]] = [] - for idx, event in enumerate(events): - if not isinstance(event, dict): - raise ValueError(f"Control trace event #{idx} must be a JSON object") - delay_s = float(event.get("delay_s", 0.0)) - message = event.get("message") - if not isinstance(message, dict): - raise ValueError(f"Control trace event #{idx} is missing object field `message`") - normalised.append({"delay_s": delay_s, "message": message}) - return sorted(normalised, key=lambda item: item["delay_s"]) - - -def _percentile(values: list[float], percentile: float) -> float: - if not values: - raise ValueError("percentile() requires at least one value") - ordered = sorted(values) - if len(ordered) == 1: - return ordered[0] - position = (len(ordered) - 1) * percentile - lower = int(position) - upper = min(lower + 1, len(ordered) - 1) - fraction = position - lower - return ordered[lower] + (ordered[upper] - ordered[lower]) * fraction - - -def _summarise(values: list[float]) -> dict[str, float] | None: - if not values: - return None - return { - "count": float(len(values)), - "min": min(values), - "mean": sum(values) / len(values), - "p50": _percentile(values, 0.50), - "p90": _percentile(values, 0.90), - "p99": _percentile(values, 0.99), - "max": max(values), - } - - -class StreamBenchSession: - """Single WebRTC session benchmark runner.""" - - def __init__( - self, - *, - config: BenchConfig, - logical_session_index: int, - phase: str, - artifacts_dir: Path, - rtc_configuration, - control_trace: list[dict[str, Any]], - ) -> None: - self.config = config - self.logical_session_index = logical_session_index - self.phase = phase - self.artifacts_dir = artifacts_dir - self.rtc_configuration = rtc_configuration - self.control_trace = control_trace - self.planned_session_id = f"{phase}-{logical_session_index:03d}-{uuid.uuid4().hex[:10]}" - self.session_id = self.planned_session_id - self.result = SessionResult( - logical_session_index=logical_session_index, - phase=phase, - mode=config.mode, - planned_session_id=self.planned_session_id, - session_id=self.session_id, - ) - self._events: list[dict[str, Any]] = [] - self._pc = None - self._data_channel = None - self._session_started_at = 0.0 - self._offer_sent_at = 0.0 - self._first_frame_at: float | None = None - self._last_frame_at: float | None = None - self._first_metadata_at: float | None = None - self._connected_at: float | None = None - self._connected_event = asyncio.Event() - self._done_event = asyncio.Event() - self._state_monitor_task: asyncio.Task | None = None - self._track_tasks: list[asyncio.Task] = [] - self._control_sender_task: asyncio.Task | None = None - self._pending_control_ack_indices: deque[int] = deque() - self._pending_control_frame_indices: deque[int] = deque() - - def _record_event(self, event_type: str, **payload: Any) -> None: - event = { - "event": event_type, - "session_id": self.session_id, - "logical_session_index": self.logical_session_index, - "phase": self.phase, - "timestamp": time.time(), - } - event.update(payload) - self._events.append(event) - if self.config.print_events: - print(json.dumps(event, ensure_ascii=False)) - - def _build_request_body(self) -> dict[str, Any]: - request_options = dict(self.config.request_extra) - request_fps = int(request_options.get("fps", self.config.fps)) - body = { - "session_id": self.planned_session_id, - "sdp": self._pc.localDescription.sdp, - "type": self._pc.localDescription.type, - "task": self.config.task, - "prompt": self.config.prompt, - "fps": request_fps, - "config": dict(request_options), - } - body.update(request_options) - if self.config.image_path: - body["image_path"] = self.config.image_path - return body - - def _current_state_snapshot(self) -> dict[str, str]: - if self._pc is None: - return {} - return { - "signaling_state": self._pc.signalingState, - "ice_gathering_state": self._pc.iceGatheringState, - "ice_connection_state": self._pc.iceConnectionState, - "connection_state": self._pc.connectionState, - } - - async def _monitor_peer_states(self) -> None: - """Record state transitions even when aiortc callbacks stay quiet.""" - - last_snapshot: dict[str, str] | None = None - while not self._done_event.is_set(): - if self._pc is None: - return - snapshot = self._current_state_snapshot() - if snapshot and snapshot != last_snapshot: - self._record_event("peer_state_snapshot", **snapshot) - last_snapshot = snapshot - if snapshot.get("connection_state") == "connected": - return - await asyncio.sleep(0.5) - - async def _consume_video_track(self, track) -> None: - try: - while True: - await track.recv() - now = time.perf_counter() - self.result.frames_received += 1 - if self._first_frame_at is None: - self._first_frame_at = now - self.result.first_frame_latency_ms = (now - self._session_started_at) * 1000.0 - self._record_event("first_frame") - self._last_frame_at = now - if self._pending_control_frame_indices: - control_index = self._pending_control_frame_indices.popleft() - control_event = self.result.control_events[control_index] - if control_event.next_frame_latency_ms is None: - control_event.next_frame_latency_ms = (now - self._session_started_at) * 1000.0 - ( - control_event.sent_offset_s * 1000.0 - ) - except Exception as exc: - self._record_event("video_track_ended", detail=str(exc)) - - async def _consume_audio_track(self, track) -> None: - try: - while True: - await track.recv() - except Exception as exc: - self._record_event("audio_track_ended", detail=str(exc)) - - def _handle_datachannel_message(self, raw_message: str) -> None: - now = time.perf_counter() - self.result.metadata_messages += 1 - if self._first_metadata_at is None: - self._first_metadata_at = now - self.result.first_metadata_latency_ms = (now - self._session_started_at) * 1000.0 - - try: - payload = json.loads(raw_message) - except json.JSONDecodeError: - self._record_event("datachannel_message_invalid", raw_message=raw_message) - return - - message_type = payload.get("type") - if message_type == "done": - self.result.done_received = True - self._done_event.set() - self._record_event("done_message", payload=payload) - return - - data = payload.get("data") if isinstance(payload.get("data"), dict) else payload - stage = data.get("stage") - if stage is not None: - self.result.status_messages += 1 - self.result.last_status_stage = str(stage) - if self._pending_control_ack_indices and stage in {"control_state", "applying_direction_control"}: - control_index = self._pending_control_ack_indices.popleft() - control_event = self.result.control_events[control_index] - if control_event.ack_latency_ms is None: - control_event.ack_latency_ms = (now - self._session_started_at) * 1000.0 - ( - control_event.sent_offset_s * 1000.0 - ) - self._record_event("datachannel_message", payload=payload) - - async def _send_control_trace(self) -> None: - if self._data_channel is None: - return - - trace_started_at = time.perf_counter() - for event_index, entry in enumerate(self.control_trace): - deadline = trace_started_at + float(entry["delay_s"]) - delay = deadline - time.perf_counter() - if delay > 0: - await asyncio.sleep(delay) - if self._data_channel.readyState != "open": - self._record_event("control_trace_aborted", reason=f"datachannel_state={self._data_channel.readyState}") - return - message = dict(entry["message"]) - payload = json.dumps(message) - sent_at = time.perf_counter() - control_event = ControlEventResult( - index=event_index, - scheduled_delay_s=float(entry["delay_s"]), - message=message, - sent_offset_s=sent_at - self._session_started_at, - ) - self.result.control_events.append(control_event) - self._pending_control_ack_indices.append(event_index) - self._pending_control_frame_indices.append(event_index) - self._data_channel.send(payload) - self._record_event("control_sent", payload=message) - - def _register_peer_callbacks(self) -> None: - @self._pc.on("signalingstatechange") - async def _on_signalingstatechange() -> None: - self._record_event("signaling_state", state=self._pc.signalingState) - - @self._pc.on("icegatheringstatechange") - async def _on_icegatheringstatechange() -> None: - self._record_event("ice_gathering_state", state=self._pc.iceGatheringState) - - @self._pc.on("iceconnectionstatechange") - async def _on_iceconnectionstatechange() -> None: - state = self._pc.iceConnectionState - self._record_event("ice_connection_state", state=state) - if state in {"failed", "closed", "disconnected"}: - self._done_event.set() - - @self._pc.on("connectionstatechange") - async def _on_connectionstatechange() -> None: - state = self._pc.connectionState - self._record_event("connection_state", state=state) - if state == "connected" and self._connected_at is None: - self._connected_at = time.perf_counter() - self.result.connected_latency_ms = (self._connected_at - self._session_started_at) * 1000.0 - self._connected_event.set() - if state in {"failed", "closed", "disconnected"}: - self._done_event.set() - - @self._pc.on("track") - def _on_track(track) -> None: - self._record_event("remote_track", kind=track.kind) - if track.kind == "video": - self._track_tasks.append(asyncio.create_task(self._consume_video_track(track))) - elif track.kind == "audio": - self._track_tasks.append(asyncio.create_task(self._consume_audio_track(track))) - - def _create_bidirectional_channel(self) -> None: - self._data_channel = self._pc.createDataChannel("telefuser") - - @self._data_channel.on("open") - def _on_open() -> None: - self._record_event("datachannel_open") - if self._control_sender_task is None and self.control_trace: - self._control_sender_task = asyncio.create_task(self._send_control_trace()) - - @self._data_channel.on("message") - def _on_message(message) -> None: - if isinstance(message, bytes): - message = message.decode("utf-8", errors="replace") - self._handle_datachannel_message(str(message)) - - @self._data_channel.on("close") - def _on_close() -> None: - self._record_event("datachannel_close") - self._done_event.set() - - async def run(self) -> SessionResult: - self._session_started_at = time.perf_counter() - self._record_event("session_start", mode=self.config.mode) - - self._pc = RTCPeerConnection(configuration=self.rtc_configuration or RTCConfiguration()) - self._register_peer_callbacks() - self._state_monitor_task = asyncio.create_task(self._monitor_peer_states()) - - if self.config.mode == "bidirectional": - self._create_bidirectional_channel() - - self._pc.addTransceiver("video", direction="recvonly") - if self.config.receive_audio: - self._pc.addTransceiver("audio", direction="recvonly") - - try: - offer = await self._pc.createOffer() - await self._pc.setLocalDescription(offer) - ice_complete = await _wait_for_ice_complete(self._pc, self.config.ice_gather_timeout_s) - self._record_event("ice_gathering_complete", complete=ice_complete) - - request_body = self._build_request_body() - self._record_event("offer_request", body=request_body) - self._offer_sent_at = time.perf_counter() - answer = await _http_json_request( - f"{self.config.server_url.rstrip('/')}{self.config.offer_path}", - method="POST", - timeout_s=self.config.connect_timeout_s, - payload=request_body, - ) - answered_at = time.perf_counter() - self.result.offer_rtt_ms = (answered_at - self._offer_sent_at) * 1000.0 - self.session_id = str(answer.get("session_id", self.session_id)) - self.result.session_id = self.session_id - self._record_event("offer_answer", answer=answer) - await self._pc.setRemoteDescription(RTCSessionDescription(sdp=answer["sdp"], type=answer["type"])) - self._record_event("post_answer_state", **self._current_state_snapshot()) - - try: - await asyncio.wait_for(self._connected_event.wait(), timeout=self.config.connect_timeout_s) - except asyncio.TimeoutError: - raise TimeoutError( - f"WebRTC connection did not reach connected state within {self.config.connect_timeout_s:.1f}s" - ) - - try: - await asyncio.wait_for(self._done_event.wait(), timeout=self.config.session_duration_s) - except asyncio.TimeoutError: - self._record_event("session_duration_elapsed", duration_s=self.config.session_duration_s) - - if self._first_frame_at is None: - wait_remaining = self.config.frame_timeout_s - while self._first_frame_at is None and wait_remaining > 0: - started_wait = time.perf_counter() - try: - await asyncio.wait_for(self._done_event.wait(), timeout=min(wait_remaining, 0.25)) - except asyncio.TimeoutError: - pass - wait_remaining -= time.perf_counter() - started_wait - if self._first_frame_at is not None: - break - - if self._first_frame_at is not None: - self.result.success = True - if self._last_frame_at is not None and self._last_frame_at > self._first_frame_at: - self.result.stream_fps = (self.result.frames_received - 1) / (self._last_frame_at - self._first_frame_at) - elif self.result.error is None: - self.result.error = "No video frame received before session shutdown" - except Exception as exc: - self.result.error = str(exc) - self._record_event("session_error", error=str(exc)) - finally: - await self._shutdown() - finished_at = time.perf_counter() - self.result.session_runtime_s = finished_at - self._session_started_at - self._write_event_artifact() - return self.result - - async def _shutdown(self) -> None: - if self._data_channel is not None and self._data_channel.readyState == "open": - try: - self._data_channel.send(json.dumps({"type": "stop"})) - self._record_event("stop_sent") - except Exception as exc: - self._record_event("stop_send_failed", error=str(exc)) - - delete_url = f"{self.config.server_url.rstrip('/')}{self.config.delete_path_template.format(session_id=self.session_id)}" - try: - await _http_json_request(delete_url, method="DELETE", timeout_s=self.config.shutdown_timeout_s) - self._record_event("session_delete") - except Exception as exc: - self._record_event("session_delete_failed", error=str(exc)) - - if self._control_sender_task is not None and not self._control_sender_task.done(): - self._control_sender_task.cancel() - try: - await self._control_sender_task - except asyncio.CancelledError: - pass - except Exception: - pass - - if self._state_monitor_task is not None and not self._state_monitor_task.done(): - self._state_monitor_task.cancel() - try: - await self._state_monitor_task - except asyncio.CancelledError: - pass - except Exception: - pass - - for task in self._track_tasks: - if not task.done(): - task.cancel() - try: - await task - except asyncio.CancelledError: - pass - except Exception: - pass - - if self._pc is not None: - try: - await self._pc.close() - except Exception as exc: - self._record_event("peer_close_failed", error=str(exc)) - - def _write_event_artifact(self) -> None: - events_dir = self.artifacts_dir / "events" - events_dir.mkdir(parents=True, exist_ok=True) - event_file = events_dir / f"{self.phase}_{self.logical_session_index:03d}_{self.session_id}.jsonl" - with event_file.open("w", encoding="utf-8") as handle: - for event in self._events: - handle.write(json.dumps(event, ensure_ascii=False) + "\n") - self.result.artifacts_event_file = str(event_file) - - -async def _run_phase( - *, - config: BenchConfig, - phase: str, - session_count: int, - artifacts_dir: Path, - rtc_configuration, - control_trace: list[dict[str, Any]], -) -> list[SessionResult]: - sessions: list[StreamBenchSession] = [] - tasks: list[asyncio.Task] = [] - for index in range(session_count): - session = StreamBenchSession( - config=config, - logical_session_index=index, - phase=phase, - artifacts_dir=artifacts_dir, - rtc_configuration=rtc_configuration, - control_trace=control_trace, - ) - sessions.append(session) - - async def _start_session(current_session: StreamBenchSession, delay_s: float) -> SessionResult: - if delay_s > 0: - await asyncio.sleep(delay_s) - return await current_session.run() - - tasks.append(asyncio.create_task(_start_session(session, config.stagger_s * index))) - return list(await asyncio.gather(*tasks)) - - -def _result_to_dict(result: SessionResult) -> dict[str, Any]: - payload = asdict(result) - payload["control_events"] = [asdict(event) for event in result.control_events] - return payload - - -def _build_summary( - *, - config: BenchConfig, - warmup_results: list[SessionResult], - profile_results: list[SessionResult], - started_at_iso: str, -) -> dict[str, Any]: - successful_profile_results = [result for result in profile_results if result.success] - all_control_events = [ - control_event - for result in profile_results - for control_event in result.control_events - if control_event.ack_latency_ms is not None or control_event.next_frame_latency_ms is not None - ] - summary = { - "started_at_utc": started_at_iso, - "config": asdict(config), - "warmup": { - "attempted_sessions": len(warmup_results), - "successful_sessions": sum(1 for result in warmup_results if result.success), - "failed_sessions": sum(1 for result in warmup_results if not result.success), - }, - "profile": { - "attempted_sessions": len(profile_results), - "successful_sessions": len(successful_profile_results), - "failed_sessions": len(profile_results) - len(successful_profile_results), - "success_rate": (len(successful_profile_results) / len(profile_results)) if profile_results else 0.0, - "metrics": { - "offer_rtt_ms": _summarise([result.offer_rtt_ms for result in profile_results if result.offer_rtt_ms]), - "connected_latency_ms": _summarise( - [result.connected_latency_ms for result in profile_results if result.connected_latency_ms] - ), - "first_frame_latency_ms": _summarise( - [result.first_frame_latency_ms for result in profile_results if result.first_frame_latency_ms] - ), - "first_metadata_latency_ms": _summarise( - [ - result.first_metadata_latency_ms - for result in profile_results - if result.first_metadata_latency_ms is not None - ] - ), - "stream_fps": _summarise([result.stream_fps for result in profile_results if result.stream_fps]), - "session_runtime_s": _summarise( - [result.session_runtime_s for result in profile_results if result.session_runtime_s] - ), - "frames_received": _summarise([float(result.frames_received) for result in profile_results]), - "control_ack_latency_ms": _summarise( - [event.ack_latency_ms for event in all_control_events if event.ack_latency_ms is not None] - ), - "control_to_next_frame_latency_ms": _summarise( - [ - event.next_frame_latency_ms - for event in all_control_events - if event.next_frame_latency_ms is not None - ] - ), - }, - }, - } - return summary - - -def _print_console_summary(summary: dict[str, Any]) -> None: - profile = summary["profile"] - metrics = profile["metrics"] - print( - f"Profile sessions: {profile['successful_sessions']}/{profile['attempted_sessions']} succeeded " - f"(success_rate={profile['success_rate']:.2%})" + "--config", + default="benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json", ) - for metric_name in ( - "offer_rtt_ms", - "connected_latency_ms", - "first_frame_latency_ms", - "stream_fps", - "control_ack_latency_ms", - "control_to_next_frame_latency_ms", - ): - metric = metrics.get(metric_name) - if metric is None: - print(f"{metric_name}: n/a") - continue - print( - f"{metric_name}: count={int(metric['count'])} " - f"mean={metric['mean']:.2f} p50={metric['p50']:.2f} p90={metric['p90']:.2f} max={metric['max']:.2f}" - ) - - -async def _run_benchmark(config: BenchConfig) -> dict[str, Any]: - if RTCPeerConnection is None: - raise RuntimeError( - "aiortc is required for stream benchmarking. Install TeleFuser with `pip install -e \".[webrtc]\"`." - ) from AIORTC_IMPORT_ERROR + parser.add_argument("--server-url") + parser.add_argument("--artifacts-dir") + args, remainder = parser.parse_known_args() - resolved_ice_host_ips = config.ice_host_ips - if not resolved_ice_host_ips: - resolved_ice_host_ips = _detect_default_ice_host_ips() - config.ice_host_ips = resolved_ice_host_ips - configure_ice_host_addresses(resolved_ice_host_ips) - started_at_iso = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) - control_trace = _load_control_trace(config.control_trace_path, config.mode) - artifacts_root = Path(config.artifacts_dir) / time.strftime("%Y%m%d_%H%M%S", time.gmtime()) - artifacts_root.mkdir(parents=True, exist_ok=True) - rtc_configuration = _build_rtc_configuration(config) - - warmup_results = await _run_phase( - config=config, - phase="warmup", - session_count=config.warmup_sessions, - artifacts_dir=artifacts_root, - rtc_configuration=rtc_configuration, - control_trace=control_trace, - ) - profile_results = await _run_phase( - config=config, - phase="profile", - session_count=config.session_count, - artifacts_dir=artifacts_root, - rtc_configuration=rtc_configuration, - control_trace=control_trace, + root = Path(__file__).resolve().parents[3] + aiperf_repo = Path( + os.environ.get("AIPERF_REPO", root / "benchmarks" / "aiperf") ) - - summary = _build_summary( - config=config, - warmup_results=warmup_results, - profile_results=profile_results, - started_at_iso=started_at_iso, - ) - summary_path = artifacts_root / "summary.json" - sessions_path = artifacts_root / "sessions.jsonl" - summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - with sessions_path.open("w", encoding="utf-8") as handle: - for result in [*warmup_results, *profile_results]: - handle.write(json.dumps(_result_to_dict(result), ensure_ascii=False) + "\n") - - summary["artifacts_dir"] = str(artifacts_root) - summary["summary_path"] = str(summary_path) - summary["sessions_path"] = str(sessions_path) - return summary - - -def main() -> None: - parser = _build_parser() - args = parser.parse_args() - config = _load_config(args) - summary = asyncio.run(_run_benchmark(config)) - _print_console_summary(summary) - print(f"Artifacts: {summary['artifacts_dir']}") - print(f"Summary JSON: {summary['summary_path']}") - print(f"Session records: {summary['sessions_path']}") + command = [ + os.environ.get("AIPERF_UV_BIN", "uv"), + "run", + "--project", + str(aiperf_repo), + "--extra", + "streaming-webrtc", + "aiperf", + "profile", + "--stream-config", + args.config, + ] + if args.server_url: + command.extend(["--stream-server-url", args.server_url]) + if args.artifacts_dir: + command.extend(["--stream-artifacts-dir", args.artifacts_dir]) + os.execvp(command[0], [*command, *remainder]) if __name__ == "__main__": diff --git a/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh index 3dea4f0..1060fc9 100644 --- a/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh +++ b/benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh @@ -3,40 +3,42 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" cd "${ROOT_DIR}" -export PYTHONPATH="${ROOT_DIR}${PYTHONPATH:+:${PYTHONPATH}}" +AIPERF_REPO="${AIPERF_REPO:-${ROOT_DIR}/benchmarks/aiperf}" +UV_BIN="${AIPERF_UV_BIN:-uv}" CONFIG_PATH="${1:-benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json}" if [[ $# -gt 0 ]]; then shift fi -NOFILE_LIMIT="${TELEFUSER_BENCH_NOFILE_LIMIT:-8192}" -if ! ulimit -n "${NOFILE_LIMIT}" >/dev/null 2>&1; then - echo "Warning: failed to raise open-file limit to ${NOFILE_LIMIT}" >&2 -fi - SERVER_URL="${TELEFUSER_STREAM_BENCH_URL:-http://127.0.0.1:8088}" -HEALTH_URL="${TELEFUSER_STREAM_BENCH_HEALTH_URL:-${SERVER_URL}/v1/service/health}" -PYTHON_BIN="${TELEFUSER_STREAM_BENCH_PYTHON:-python3}" +SERVER_ARGS=(--stream-server-url "${SERVER_URL}") +for argument in "$@"; do + if [[ "${argument}" == "--stream-server-url" || "${argument}" == --stream-server-url=* ]]; then + SERVER_ARGS=() + break + fi +done ICE_HOST_IPS="${TELEFUSER_STREAM_BENCH_ICE_HOST_IPS:-}" ICE_HOST_ARGS=() if [[ -n "${ICE_HOST_IPS}" ]]; then IFS=',' read -r -a _ICE_HOST_IP_ARRAY <<< "${ICE_HOST_IPS}" for ice_host_ip in "${_ICE_HOST_IP_ARRAY[@]}"; do if [[ -n "${ice_host_ip}" ]]; then - ICE_HOST_ARGS+=(--ice-host-ip "${ice_host_ip}") + ICE_HOST_ARGS+=(--stream-ice-host-ip "${ice_host_ip}") fi done fi -if command -v curl >/dev/null 2>&1; then - echo "Checking TeleFuser stream health: ${HEALTH_URL}" - curl --fail --silent --show-error "${HEALTH_URL}" >/dev/null +METRICS_ARGS=() +if [[ -n "${TELEFUSER_STREAM_BENCH_METRICS_URL:-}" ]]; then + METRICS_ARGS+=(--stream-server-metrics-url "${TELEFUSER_STREAM_BENCH_METRICS_URL}") fi -echo "Running stream benchmark with config: ${CONFIG_PATH}" -"${PYTHON_BIN}" benchmarks/telefuser_aiperf/scripts/run_stream_bench.py \ - --config "${CONFIG_PATH}" \ - --server-url "${SERVER_URL}" \ +exec "${UV_BIN}" run --project "${AIPERF_REPO}" --extra streaming-webrtc \ + aiperf profile \ + --stream-config "${CONFIG_PATH}" \ + "${SERVER_ARGS[@]}" \ "${ICE_HOST_ARGS[@]}" \ + "${METRICS_ARGS[@]}" \ "$@" diff --git a/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh b/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh index 02163f2..7aaf2a7 100755 --- a/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh +++ b/benchmarks/telefuser_aiperf/scripts/run_video_bench.sh @@ -15,7 +15,7 @@ if ! ulimit -n "${NOFILE_LIMIT}" >/dev/null 2>&1; then fi if ! command -v "${AIPERF_BIN}" >/dev/null 2>&1; then - echo "aiperf is not installed. Set AIPERF_BIN or run: pip install -e ./benchmarks/aiperf" >&2 + echo "aiperf is not installed. Set AIPERF_BIN or run: bash scripts/setup_aiperf_repo.sh" >&2 exit 1 fi diff --git a/benchmarks/telefuser_aiperf/scripts/run_video_smoke.py b/benchmarks/telefuser_aiperf/scripts/run_video_smoke.py new file mode 100644 index 0000000..94c9fd2 --- /dev/null +++ b/benchmarks/telefuser_aiperf/scripts/run_video_smoke.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +"""Run a dependency-light OpenAI video API smoke benchmark.""" + +from __future__ import annotations + +import argparse +import json +import time +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path +from typing import Any + + +TERMINAL_STATUSES = {"completed", "failed", "cancelled"} + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--server-url", default="http://127.0.0.1:8000") + parser.add_argument("--health-path", default="/v1/service/health") + parser.add_argument( + "--prompt", + default="The character turns slightly toward the camera and breathes naturally, cinematic motion, consistent identity", + ) + parser.add_argument("--reference-url", default="examples/data/101235-video-720_0.png") + parser.add_argument("--model", default="telefuser-wan21-i2v-480p") + parser.add_argument("--size", default="832x480") + parser.add_argument("--seconds", type=int, default=5) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--negative-prompt", default="") + parser.add_argument("--poll-timeout-s", type=float, default=3600.0) + parser.add_argument("--poll-interval-s", type=float, default=5.0) + parser.add_argument("--artifacts-dir", default="artifacts/video_smoke") + parser.add_argument("--download", action=argparse.BooleanOptionalAction, default=True) + return parser + + +def _url(base_url: str, path: str) -> str: + return f"{base_url.rstrip('/')}/{path.lstrip('/')}" + + +def _read_json(request: urllib.request.Request, timeout_s: float) -> dict[str, Any]: + try: + with urllib.request.urlopen(request, timeout=timeout_s) as response: + body = response.read() + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"HTTP {exc.code} {exc.reason}: {body}") from exc + except urllib.error.URLError as exc: + raise RuntimeError(f"Failed to reach {request.full_url}: {exc.reason}") from exc + payload = json.loads(body.decode("utf-8")) if body else {} + if not isinstance(payload, dict): + raise RuntimeError(f"Expected JSON object from {request.full_url}, got {type(payload).__name__}") + return payload + + +def _get_json(url: str, timeout_s: float = 30.0) -> dict[str, Any]: + return _read_json(urllib.request.Request(url, method="GET"), timeout_s) + + +def _post_form(url: str, fields: dict[str, Any], timeout_s: float = 30.0) -> dict[str, Any]: + data = urllib.parse.urlencode(fields).encode("utf-8") + request = urllib.request.Request( + url, + data=data, + method="POST", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + return _read_json(request, timeout_s) + + +def _download(url: str, output_path: Path, timeout_s: float = 300.0) -> int: + request = urllib.request.Request(url, method="GET") + try: + with urllib.request.urlopen(request, timeout=timeout_s) as response: + data = response.read() + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"HTTP {exc.code} {exc.reason}: {body}") from exc + except urllib.error.URLError as exc: + raise RuntimeError(f"Failed to reach {url}: {exc.reason}") from exc + output_path.write_bytes(data) + return len(data) + + +def _reference_path(raw_path: str) -> str: + path = Path(raw_path) + if path.exists(): + return str(path.resolve()) + return raw_path + + +def main() -> None: + args = _build_parser().parse_args() + artifacts_dir = Path(args.artifacts_dir) / time.strftime("%Y%m%d_%H%M%S", time.gmtime()) + artifacts_dir.mkdir(parents=True, exist_ok=True) + + started = time.perf_counter() + health = _get_json(_url(args.server_url, args.health_path)) + submit_started = time.perf_counter() + create_response = _post_form( + _url(args.server_url, "/v1/videos"), + { + "prompt": args.prompt, + "reference_url": _reference_path(args.reference_url), + "model": args.model, + "size": args.size, + "seconds": args.seconds, + "seed": args.seed, + "negative_prompt": args.negative_prompt, + }, + ) + submit_latency_ms = (time.perf_counter() - submit_started) * 1000.0 + video_id = create_response.get("id") + if not isinstance(video_id, str) or not video_id: + raise RuntimeError(f"Create video response did not include an id: {create_response}") + + deadline = time.perf_counter() + args.poll_timeout_s + statuses: list[dict[str, Any]] = [] + final_status: dict[str, Any] | None = None + while time.perf_counter() < deadline: + status = _get_json(_url(args.server_url, f"/v1/videos/{video_id}")) + statuses.append(status) + if status.get("status") in TERMINAL_STATUSES: + final_status = status + break + time.sleep(args.poll_interval_s) + if final_status is None: + raise TimeoutError(f"Timed out waiting for video {video_id} after {args.poll_timeout_s}s") + + output_bytes = 0 + output_path = None + if args.download and final_status.get("status") == "completed": + output_path = artifacts_dir / f"{video_id}.mp4" + output_bytes = _download(_url(args.server_url, f"/v1/videos/{video_id}/content"), output_path) + if output_bytes <= 0: + raise RuntimeError(f"Downloaded empty video content for {video_id}") + + summary = { + "server_url": args.server_url, + "health": health, + "request": { + "prompt": args.prompt, + "reference_url": _reference_path(args.reference_url), + "model": args.model, + "size": args.size, + "seconds": args.seconds, + "seed": args.seed, + }, + "create_response": create_response, + "final_status": final_status, + "status_samples": statuses, + "metrics": { + "submit_latency_ms": submit_latency_ms, + "end_to_end_latency_ms": (time.perf_counter() - started) * 1000.0, + "poll_count": len(statuses), + "output_bytes": output_bytes, + }, + "output_path": str(output_path) if output_path is not None else None, + } + summary_path = artifacts_dir / "summary.json" + summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + print(f"Video smoke status: {final_status.get('status')}") + print(f"Video id: {video_id}") + print(f"Artifacts: {artifacts_dir}") + print(f"Summary JSON: {summary_path}") + if final_status.get("status") != "completed": + raise RuntimeError(f"Video smoke did not complete successfully: {final_status}") + + +if __name__ == "__main__": + main() diff --git a/benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml b/benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml new file mode 100644 index 0000000..4148edb --- /dev/null +++ b/benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml @@ -0,0 +1,82 @@ +# Benchmark contract example for a WebRTC stream-world target. +# Enum comments use "one-of". List comments use "one-or-more". +contract_version: v1 # one-of: v1. Bump only when the contract schema changes. +name: telefuser_lingbot_world_fast_stream # Stable benchmark target id used in reports and automation. +mode: stream_world # one-of: batch_video, stream_world. +implementation: telefuser # one-of for this PR: telefuser, diffusers, sglang_diffusion. Add new runtimes as new baselines land. +model_family: lingbot_world_fast # Example values: wan_video, lingbot_world_fast, hunyuan_video, ltx_video. +model: LingBot-World-Fast # Concrete model or service profile under test. +supported_tasks: # one-or-more: t2v, i2v, ti2v, bidirectional. + - bidirectional +transport: webrtc # one-of: http, http_polling, websocket, sse, webrtc. +adapter: telefuser_webrtc # Built-in AIPerf adapter; target repository owns no transport implementation. +endpoint: + health_path: /v1/service/health # Service readiness path. + metadata_path: /v1/service/metadata # Optional target environment and startup phase facts. + offer_path: /v1/stream/webrtc/offer # WebRTC SDP offer/answer path. + delete_path_template: /v1/stream/webrtc/{session_id} # Session cleanup path template. +request_encoding: + offer_content_type: application/json # one-of: application/json. + offer_required_fields: # one-or-more. Required JSON fields in the offer payload. + - sdp + - type + - task + offer_parameters: # Mapping from benchmark parameter names to wire payload fields. + session_id: session_id + prompt: prompt + fps: fps + image_path: image_path + config: config + control_channel: + transport: datachannel # one-of for WebRTC: datachannel. + label: telefuser # DataChannel label expected by the service. + message_types: # one-or-more. Message types understood by the stream benchmark semantics. + - control + - status + - chunk + - done + - error +result_delivery: + media: rtp_video_track # one-of for WebRTC: rtp_video_track. + metadata: datachannel # one-of for WebRTC: datachannel. + session_log: sessions.jsonl # Per-session result records. + event_log: events/{phase}_{logical_session_index}_{session_id}.jsonl # Per-session event trace template. +workload: + mode: bidirectional # one-of: server_push, bidirectional. + task: bidirectional # one-of for this service: bidirectional. + fps: 16 + session_count: 1 + warmup_sessions: 0 + session_duration_s: 90.0 + control_trace: benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json # Timed control-event workload. + request_extra: # Service-specific request config passed through to the stream service. + chunk_size: 3 + frame_num: 81 + sample_shift: 5.0 + control_mode: cam # one-of for LingBotWorldFast: cam. + show_control_hud: true + benchmark_metrics: true # Emit synchronized runtime/chunk facts for AIPerf aggregation. +metrics: # one-or-more. Choose all metrics emitted by this benchmark mode. + - offer_rtt_ms + - connected_latency_ms + - first_frame_latency_ms + - first_metadata_latency_ms + - stream_fps + - session_runtime_s + - frames_received + - control_ack_latency_ms + - control_to_next_frame_latency_ms + - pipeline_init_seconds + - runtime_creation_seconds + - chunk_compute_seconds + - chunk_encode_seconds + - chunk_compute_fps + - chunk_peak_allocated_bytes + - chunk_peak_reserved_bytes + - success_rate +limits: + active_sessions: 1 # Service limit for this target. The harness still exposes session_count. +artifacts: # Paths consumed by automation and documentation. + config: benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json + control_trace: benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json + runner: aiperf profile --stream-config diff --git a/docs/en/benchmark_aiperf.md b/docs/en/benchmark_aiperf.md index fdebe3b..35f8ff3 100644 --- a/docs/en/benchmark_aiperf.md +++ b/docs/en/benchmark_aiperf.md @@ -1,31 +1,21 @@ # TeleFuser and AIPerf Benchmark -This page describes how to benchmark TeleFuser batch video serving with AIPerf. +This page describes how to benchmark TeleFuser batch video serving, TeleFuser WebRTC stream serving, and the SGLang-Diffusion LingBot stream baseline through AIPerf. Current scope: - `telefuser serve` - OpenAI-compatible `/v1/videos` API -- end-to-end request latency and throughput +- AIPerf end-to-end request latency and throughput - optional HTTP trace and TeleFuser metrics scraping - -Not covered yet: - - `telefuser stream-serve` - WebRTC / DataChannel world-model sessions +- SGLang-Diffusion LingBot World WebSocket baseline - frame-level and control-latency metrics -For remote stream setup, use: - -```bash -python3 scripts/remote_bench_sync.py stream-bootstrap -``` - -That helper syncs the stream assets, installs WebRTC dependencies, auto-detects a usable `model_zoo` root, and writes a non-destructive `model_zoo` symlink inside the remote repo for later `telefuser stream-serve` startup. - -Both `run_stream_bench.sh` and `run_video_bench.sh` raise the open-file limit to `8192` by default to avoid AIPerf/WebRTC startup failures; override with `TELEFUSER_BENCH_NOFILE_LIMIT` if your environment needs a different value. +Stream sessions are now executed by AIPerf's native `profile --stream-config` path. The TeleFuser and SGLang scripts only select the target config and URL. -For stream runs, `run_stream_bench.py` also auto-detects non-loopback local IPs for ICE candidate gathering when no explicit allowlist is provided. Use `--ice-host-ip` or `TELEFUSER_WEBRTC_ICE_HOST_IPS` only when you need to pin a specific address. +AIPerf defaults to route-based ICE selection (`ice_host_ips: ["auto"]`) so a multi-homed host advertises the source address used to reach the stream target. Set the comma-separated `TELEFUSER_STREAM_BENCH_ICE_HOST_IPS` variable or repeat `--stream-ice-host-ip` only for container, TURN, or special-routing overrides; an empty list restores all aioice-discovered addresses. ## Assets @@ -33,7 +23,8 @@ The benchmark assets live in: ```text benchmarks/telefuser_aiperf/ -├── README.md +├── benchmark_contract.yaml +├── stream_benchmark_contract.yaml ├── configs/ ├── data/ └── scripts/ @@ -42,7 +33,7 @@ benchmarks/telefuser_aiperf/ ## Prerequisites 1. Start a TeleFuser batch video server. -2. Install AIPerf from the vendored clone. +2. Set up the AIPerf dependency repository. Example: @@ -52,9 +43,11 @@ telefuser serve \ --port 8000 \ --task i2v -pip install -e ./benchmarks/aiperf +bash scripts/setup_aiperf_repo.sh ``` +The setup script defaults to the `teleai` branch of `https://github.com/ActivePeter/aiperf`. + ## Quick Start Run the default benchmark: @@ -93,13 +86,110 @@ Reason: So the helper script uses TeleFuser's `/v1/service/health` instead. -## Limitation +## Stream Benchmark + +The stream benchmark path uses AIPerf's contract, runner, WebRTC adapter, observability, and artifact layers: + +- `benchmarks/aiperf/src/aiperf/streaming/` +- `benchmarks/aiperf/src/aiperf/streaming/adapters/telefuser_webrtc.py` +- `benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json` +- `benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json` +- `benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json` + +It records offer RTT, connection latency, first-frame latency, stream FPS, control acknowledgement latency, and control-to-next-frame latency. With `benchmark_metrics: true`, it also captures target pipeline/runtime initialization, per-chunk compute and encoding time, allocator peaks, steady-state compute FPS, runtime/cache dimensions, and target environment identity. `warmup_chunks` controls the leading chunks excluded from steady-state aggregates. +Each completed stream run also writes `target_metadata.json` and a self-contained `stream_report.html` from AIPerf. The same report UI renders TeleFuser, SGLang, and registered third-party adapters without target-specific frontend code. + +The thin launcher is equivalent to: + +```bash +uv run --project benchmarks/aiperf --extra streaming-webrtc \ + aiperf profile \ + --stream-config benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json +``` + +## SGLang-Diffusion Stream Baseline + +This adapter uses the diffusion runtime in the main `sgl-project/sglang` +package (`sglang.multimodal_gen`); it does not require a separate +`SGLang-Diffusion` repository. + +Start SGLang-Diffusion: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh +``` + +Then run: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh +``` + +This baseline uses `robbyant/lingbot-world-fast-diffusers`, WebSocket + MessagePack, and the same `stream_lingbot_controls.json` trace as the TeleFuser stream benchmark. + +The compare configs use a 90 second session window so the baseline has enough time to emit first frames and control-loop metrics; the `quick` configs remain local smoke checks. + +Fairness note: TeleFuser's LingBot world-model service runs in its default 1-GPU GPU-resident configuration. The SGLang-Diffusion performance baseline must therefore use `--performance-mode speed` and disable CPU, DiT layerwise, VAE, and text-encoder offload. SGLang's `auto` mode may enable VAE layerwise offload even when the individual CPU-offload flags are false. If the GPU-resident configuration OOMs, record that failure instead of relabeling an auto-offloaded result. + +AIPerf normalizes SGLang's native `chunk_stats` into the shared target chunk +schema: scheduler compute, request preparation, output encoding/pacing/writes, +total chunk duration, frame/batch counts, and raw/wire bytes appear in +`summary.json`, `sessions.jsonl`, and `stream_report.html`. An instrumented SGLang +endpoint also sends its reset-scoped `OutputBatch.peak_memory_mb`; AIPerf maps +this only to `chunk_peak_reserved_bytes`, matching SGLang's +`max_memory_reserved()` source. Initialization phase durations and allocated +peaks remain unavailable instead of being inferred. -This is a **batch video serving benchmark**, not yet a **real-time world-model streaming benchmark**. +On 2026-07-13, a true 1xH100 GPU-resident run established two separate outcomes. +The 9-sink / 18-frame cache workload failed with CUDA OOM at approximately +79.16 GiB process memory. The tuned 6-sink / 9-frame workload completed one +warmup and one measured session: 93 frames in 8 chunks, 5.1906 target compute +FPS, 5.6788 client stream FPS, 3076.18 ms first-frame latency, and a maximum +reset-scoped reserved peak of 76,919,341,056 bytes (73,356 MiB). The host +required the documented RoPE fallback because its FlashInfer CUDA compiler and +headers were incompatible. These two cache geometries must not be reported as a +same-configuration comparison. -For `stream-serve`, the next layer should add: +## Historical Metrics Service (GreptimeDB) + +Cross-run storage, APIs, and visualization are owned by AIPerf. TeleFuser and SGLang +produce canonical artifacts; neither target repository contains a database client or a +target-specific history frontend. + +With GreptimeDB running, import existing profile and stream artifacts: + +```bash +uv run --project benchmarks/aiperf aiperf history ingest \ + --greptime-url http://127.0.0.1:4000 \ + --greptime-database public \ + --artifact-root artifacts \ + --artifact-root work_dirs/benchmarks +``` + +Then start the API and bundled Vue application: + +```bash +uv run --project benchmarks/aiperf aiperf history serve \ + --greptime-url http://127.0.0.1:4000 \ + --greptime-database public \ + --artifact-root artifacts \ + --artifact-root work_dirs/benchmarks \ + --host 127.0.0.1 \ + --port 8095 +``` -1. a WebRTC transport -2. a world-model session endpoint -3. an interactive trace workload -4. frame-level latency metrics +Open `http://127.0.0.1:8095/` to browse runs, compare metrics across runs, and inspect +session, control, phase, chunk, timeslice, GPU, Prometheus, and normalized points. Warmup +and profiling points retain separate phase values. Client delivery `stream_fps` and +target `chunk_compute_fps` remain separate metrics. + +GreptimeDB is mandatory. A connection or table-creation failure stops startup, query +failures return HTTP 503, and the API/UI never switch to SQLite, an in-memory index, or +direct artifact queries. JSON and JSONL artifacts remain replayable inputs only. + +See the AIPerf +[deployment guide](https://github.com/ActivePeter/aiperf/blob/teleai/docs/tutorials/history-dashboard.md), the +[history service design](https://github.com/ActivePeter/aiperf/blob/teleai/docs/dev/history-service-design.md), +and the TeleFuser +[benchmark design](/TeleFuser/zh/benchmark_aiperf_design/) for the complete ownership and +metric-boundary rules. diff --git a/docs/en/index.md b/docs/en/index.md index d13aaad..e53bc84 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -56,7 +56,7 @@ telefuser stream-serve examples/stream_server/stream_lingbot_world_fast.py -p 80 - **[Service Guide](service.md)** — Batch serving, task APIs, and SDK - **[Stream Server](stream_server.md)** — WebRTC streaming and bidirectional control -- **[TeleFuser and AIPerf Benchmark](benchmark_aiperf.md)** — Batch video serving benchmark with AIPerf +- **[TeleFuser and AIPerf Benchmark](benchmark_aiperf.md)** — Batch, stream, baseline, and GreptimeDB history workflows - **[Configuration](configuration.md)** — Runtime and model configuration - **[Parallel Inference](parallel.md)** — Distributed processing strategies - **[Adding New Model](adding_new_model.md)** — Integrate new models diff --git a/docs/zh/benchmark_aiperf.md b/docs/zh/benchmark_aiperf.md index a6f218c..cbae8c2 100644 --- a/docs/zh/benchmark_aiperf.md +++ b/docs/zh/benchmark_aiperf.md @@ -4,10 +4,11 @@ - 批处理视频服务的压测运行方式 - AIPerf 配置、参数和结果文件 -- 远程同步、部署和复现实验脚本 +- 本地与 baseline benchmark 的运行说明 -更细的 benchmark 设计、分层、对标 shortlist 和 `stream-serve` 规划放在: +更细的 benchmark 设计、接口边界和已实现资产清单放在: +- [当前 Benchmark 对比快照](benchmark_current_comparison.md) - [TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md) ## 目录结构 @@ -16,18 +17,35 @@ ```text benchmarks/telefuser_aiperf/ -├── README.md +├── benchmark_contract.yaml +├── stream_benchmark_contract.yaml ├── configs/ │ ├── video_generation_quick.yaml │ ├── video_generation_e2e.yaml │ ├── video_generation_rate.yaml -│ └── stream_lingbot_world_fast_quick.json +│ ├── stream_lingbot_world_fast_compare.json +│ ├── stream_lingbot_world_fast_quick.json +│ ├── stream_transport_mock_compare.json +│ └── stream_transport_mock_quick.json ├── data/ │ ├── video_prompts.jsonl │ └── stream_lingbot_controls.json └── scripts/ + ├── run_mock_webrtc_service.sh ├── run_video_bench.sh └── run_stream_bench.sh + +benchmarks/baseline/sglang_lingbot_stream/ +├── benchmark_contract.yaml +├── configs/ +│ ├── stream_lingbot_world_fast_compare.json +│ ├── stream_lingbot_world_fast_quick.json +│ ├── stream_transport_mock_compare.json +│ └── stream_transport_mock_quick.json +└── scripts/ + ├── run_service.sh + ├── run_mock_stream_service.sh + └── run_stream_bench.sh ``` ## 前置条件 @@ -46,17 +64,19 @@ telefuser serve \ 确认服务健康检查可访问: ```bash -curl http://127.0.0.1:8000/v1/service/health +curl http://:8000/v1/service/health ``` ### 2. 安装 AIPerf -当前仓库已经 vendored 一份 AIPerf clone 到 `benchmarks/aiperf`,可直接本地安装: +TeleFuser 不直接 vendored AIPerf 源码。通过 setup 脚本拉取依赖仓库: ```bash -pip install -e ./benchmarks/aiperf +bash scripts/setup_aiperf_repo.sh ``` +脚本默认使用 `https://github.com/ActivePeter/aiperf` 的 `teleai` 分支,并把 checkout 放在 `benchmarks/aiperf` 后做 editable install。 + 安装后确认 `aiperf` 命令可用: ```bash @@ -76,13 +96,13 @@ bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh 默认使用: - 配置文件:`benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml` -- 服务地址:`http://127.0.0.1:8000` +- 服务地址:`http://:8000` - prompt 文件:`benchmarks/telefuser_aiperf/data/video_prompts.jsonl` 脚本会先检查: ```bash -curl http://127.0.0.1:8000/v1/service/health +curl http://:8000/v1/service/health ``` 然后执行: @@ -105,7 +125,7 @@ telefuser stream-serve \ 确认流式服务健康检查可访问: ```bash -curl http://127.0.0.1:8088/v1/service/health +curl http://:8088/v1/service/health ``` 然后运行最小流式 benchmark: @@ -118,23 +138,102 @@ bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh - 配置文件:`benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json` - 控制 trace:`benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json` -- 服务地址:`http://127.0.0.1:8088` +- 服务地址:`http://:8088` + +这条路径由 AIPerf 原生 `profile --stream-config` 模式执行;TeleFuser 脚本只是薄启动器。等价的直接命令是: + +```bash +uv run --project benchmarks/aiperf --extra streaming-webrtc \ + aiperf profile \ + --stream-config benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json +``` + +AIPerf 默认使用 `ice_host_ips: ["auto"]`,按到 stream target 的系统路由自动选择本机源地址,适用于常见多网卡主机。 +容器、TURN 或特殊路由需要固定 candidate 时,设置逗号分隔的 `TELEFUSER_STREAM_BENCH_ICE_HOST_IPS`, +或直接向 AIPerf 重复传入 `--stream-ice-host-ip`;配置空列表才恢复全部地址枚举。 + +Quick/compare 配置启用 `benchmark_metrics: true`,除客户端首帧、FPS 和控制延迟外,还采集 pipeline/runtime 初始化、 +逐 chunk compute/encode、分阶段 allocator peak、KV-cache/runtime 参数及 target 环境信息。`warmup_chunks` 按每个 measured +session 排除开头 chunk 后再计算 mean、p50、p90、std 和加权 compute FPS;raw chunk 仍保留在 `sessions.jsonl`。 + +### SGLang-Diffusion stream baseline + +SGLang-Diffusion 对接的是 `sgl-project/sglang` 主仓库里的 `sglang.multimodal_gen` diffusion runtime,不依赖单独的 `SGLang-Diffusion` 仓库。 + +先启动 SGLang-Diffusion LingBot World 服务: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_service.sh +``` + +确认服务健康检查可访问: + +```bash +curl http://:30000/health +``` + +然后运行 SGLang stream baseline: + +```bash +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh +``` + +默认使用: + +- 配置文件:`benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json` +- 控制 trace:`benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json` +- 服务地址:`http://:30000` + +这条 baseline 使用 `robbyant/lingbot-world-fast-diffusers`,协议是 WebSocket + MessagePack。AIPerf adapter 将 `ArrowUp/ArrowDown/ArrowLeft/ArrowRight` 映射为 SGLang LingBot 的 `w/s/a/d` camera actions,结果写入和 TeleFuser stream benchmark 相同的 AIPerf streaming summary 结构。 + +AIPerf 同时直接归一化 SGLang 原生 `chunk_stats`:scheduler compute、request prepare、输出编码/pacing/write、chunk 总时长、帧数、batch 数及 raw/wire bytes 都进入统一的 `summary.json`、`sessions.jsonl` 和 `stream_report.html`。插桩后的 SGLang endpoint 还会上报已有的 reset-scoped `OutputBatch.peak_memory_mb`;由于它来自 `max_memory_reserved()`,AIPerf 只映射到 `chunk_peak_reserved_bytes`,不会伪造 allocated peak。初始化 phase 时长仍保持不可用。 + +正式 GPU-resident baseline 必须使用 `--performance-mode speed`。SGLang 的 `auto` 模式即使收到三个 `*-cpu-offload=false`,仍可能自动启用 VAE layerwise offload;这类结果只能单独标为 auto-offload,不能并入 TeleFuser 默认 GPU-resident 公平对比。 -如果远端机器有很多网卡或容器 veth,`run_stream_bench.py` 现在会默认自动探测本机的非 loopback 地址并用于 ICE host candidate 收敛。 +### 纯流式 mock 压测 -只有在你明确知道要锁定某个地址时,才手动传 `--ice-host-ip` 或设置 `TELEFUSER_WEBRTC_ICE_HOST_IPS`。不要默认写 `127.0.0.1`,否则在远程主机 / 容器网络里很容易把 WebRTC 连接锁死在本机回环地址。 +这组 benchmark 只测 streaming 层,不加载底层生成模型。它适合回答: -这条路径当前不是 AIPerf 原生 endpoint,而是 TeleFuser 侧独立的 WebRTC benchmark harness。 +- WebRTC offer/answer 和连接建立本身需要多久 +- WebRTC media track 按固定 FPS 推帧时能否稳定 +- DataChannel 控制消息的 ack 和下一帧反馈延迟 +- WebSocket + MessagePack frame batch 协议在同样 session/count 下的开销 + +TeleFuser mock target 走 TeleFuser-compatible WebRTC mock service,服务端只生成合成视频帧: + +```bash +bash benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.sh \ + --host \ + --port + +bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ + benchmarks/telefuser_aiperf/configs/stream_transport_mock_compare.json \ + --stream-server-url http://: +``` -如果在远程环境执行,建议直接用: +SGLang mock target 只启动兼容 `/health` 和 `/v1/realtime_video/generate` 的 WebSocket mock server,不调用 SGLang runtime 或任何模型: ```bash -python3 scripts/remote_bench_sync.py stream-bootstrap +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.sh \ + --host \ + --port + +bash benchmarks/baseline/sglang_lingbot_stream/scripts/run_stream_bench.sh \ + benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json \ + --stream-server-url http://: ``` -它会同步代码、安装 WebRTC 依赖、自动探测可用的 `model_zoo` 根目录,并在远程仓库里写回一个非破坏性的 `model_zoo` 软链,方便后续直接起 `telefuser stream-serve`。 +默认 compare 配置使用: + +- `session_count=4` +- `warmup_sessions=1` +- `session_duration_s=20` +- `fps=16` +- 同一份 `stream_lingbot_controls.json` 控制 trace +- TeleFuser mock:`320x180` 合成帧,经 WebRTC media track 发送 +- SGLang mock:每个 `frame_batch` 携带固定大小二进制 payload,经 WebSocket + MessagePack 发送 -另外,`run_stream_bench.sh` 和 `run_video_bench.sh` 会默认把 open-file limit 提升到 `8192`,避免 AIPerf/WebRTC 在启动时撞到文件描述符上限;如果你的环境需要不同值,可以用 `TELEFUSER_BENCH_NOFILE_LIMIT` 覆盖。 +这组结果只能说明流式协议、harness 和控制消息路径开销,不能说明真实模型推理速度。 ## 可用配置 @@ -218,8 +317,8 @@ bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ 这些配置支持通过环境变量覆盖: ```bash -export TELEFUSER_AIPERF_URL=http://127.0.0.1:8000 -export TELEFUSER_AIPERF_METRICS_URL=http://127.0.0.1:8000/v1/service/metrics +export TELEFUSER_AIPERF_URL=http://:8000 +export TELEFUSER_AIPERF_METRICS_URL=http://:8000/v1/service/metrics export TELEFUSER_AIPERF_CONCURRENCY=4 export TELEFUSER_AIPERF_REQUESTS=12 export TELEFUSER_AIPERF_SIZE=1280x720 @@ -247,305 +346,6 @@ bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ --print-events ``` -## 远程一键同步与校验 - -为了方便把这套 benchmark 资产同步到远程测试机,仓库中新增了: - -```bash -python3 scripts/remote_bench_sync.py --help -``` - -这个脚本默认面向当前测试机: - -- `root@116.238.240.2` -- SSH 端口 `30724` -- 远程仓库路径 `/workspace/TeleFuser` -- TeleFuser 服务 Python `/root/venv/cu128/bin/python` -- 独立 benchmark Python `/root/venv/telefuser-bench/bin/python` - -推荐模式是**分离部署**: - -- 已经部署好的 TeleFuser 服务环境继续保留 -- 额外创建一个独立的 benchmark venv,只安装 AIPerf 和压测依赖 - -这样可以避免 benchmark 依赖覆盖 TeleFuser 现有运行环境里的: - -- `transformers` -- `protobuf` -- `pillow` -- `huggingface_hub` - -如果你要跑 `telefuser stream-serve` 的世界模型流式 benchmark,推荐直接走一键自动化预备: - -```bash -python3 scripts/remote_bench_sync.py stream-bootstrap -``` - -这条命令会自动完成: - -- 同步流式 benchmark 资产 -- 安装 AIPerf 和 `aiortc` / `opencv-python-headless` -- 安装 TeleFuser 到独立服务环境 -- 校验 `examples/lingbot/stream_lingbot_world_fast.py` -- 校验 `model_zoo` 路径 -- 打印远程 GPU 状态 - -如果你只想更新安装而不重新同步: - -```bash -python3 scripts/remote_bench_sync.py install --install-webrtc --install-telefuser -``` - -如果目标机器变化,可以通过参数覆盖: - -```bash -python3 scripts/remote_bench_sync.py \ - --host \ - --port \ - --user \ - --remote-repo \ - --remote-python \ - --remote-bench-python \ - gpu-status -``` - -### 1. 查看远程 GPU 状态 - -```bash -python3 scripts/remote_bench_sync.py gpu-status -``` - -这个命令会通过 SSH 执行 `nvidia-smi`,输出: - -- 每张卡的利用率 -- 显存占用 -- 当前活跃计算进程 - -### 2. 首次全量同步 - -```bash -python3 scripts/remote_bench_sync.py bootstrap -``` - -这个命令会把当前管理的 benchmark 资产全量同步到远程,包括: - -- `benchmarks/aiperf` -- `benchmarks/telefuser_aiperf` -- `docs/en/benchmark_aiperf.md` -- `docs/zh/benchmark_aiperf.md` -- `docs/en/index.md` -- `docs/zh/index.md` -- `mkdocs.yml` - -如果你还希望同步完成后直接安装并校验: - -```bash -python3 scripts/remote_bench_sync.py bootstrap --install --verify -``` - -默认行为是: - -- 创建独立 benchmark venv -- 在独立环境中安装 vendored `benchmarks/aiperf` -- 复用同一个 benchmark venv 运行流式 benchmark Python 脚本 -- 不修改当前 TeleFuser 服务环境 - -如果你明确希望同时刷新 TeleFuser 服务环境: - -```bash -python3 scripts/remote_bench_sync.py bootstrap --install --verify --install-telefuser -``` - -如果你想强制重建独立 benchmark venv: - -```bash -python3 scripts/remote_bench_sync.py bootstrap --install --verify --recreate-bench-venv -``` - -如果当前只想演练命令而不真正执行: - -```bash -python3 scripts/remote_bench_sync.py --dry-run bootstrap --install --verify -``` - -### 3. 后续增量同步 - -首次同步后,脚本会在本地写一个状态文件,用于记录上次已同步的文件哈希: - -```text -~/.cache/telefuser/remote_bench_sync/.json -``` - -后续直接运行: - -```bash -python3 scripts/remote_bench_sync.py sync -``` - -脚本只会上传发生变化的受管文件。 - -如果你想看本次计划同步哪些文件: - -```bash -python3 scripts/remote_bench_sync.py sync --print-files -``` - -如果你想忽略状态文件并强制重新全量上传: - -```bash -python3 scripts/remote_bench_sync.py sync --full -``` - -### 4. 远程安装 - -```bash -python3 scripts/remote_bench_sync.py install -``` - -默认会在远程执行: - -- 创建独立 venv:`/root/venv/telefuser-bench` -- `pip install -e /workspace/TeleFuser/benchmarks/aiperf` - -也就是说,默认**不会**修改现有 TeleFuser 服务环境。 - -如果你确实要刷新 TeleFuser 服务环境: - -```bash -python3 scripts/remote_bench_sync.py install --install-telefuser -``` - -如果你想强制重建独立 benchmark venv: - -```bash -python3 scripts/remote_bench_sync.py install --recreate-bench-venv -``` - -如果你只想更新源码,不想拉依赖,可以使用: - -```bash -python3 scripts/remote_bench_sync.py install --no-deps -``` - -如果你当前不想安装 AIPerf,只想做其它步骤: - -```bash -python3 scripts/remote_bench_sync.py install --skip-aiperf -``` - -### 5. 远程校验 - -```bash -python3 scripts/remote_bench_sync.py verify -``` - -校验内容包括: - -- benchmark 配置文件是否存在 -- TeleFuser 服务环境中的 `import telefuser` -- TeleFuser 服务环境中的 `telefuser --help` -- 独立 benchmark 环境中的 `import aiperf` -- 独立 benchmark 环境中的 `aiperf --help` -- 独立 benchmark 环境中的 `python benchmarks/telefuser_aiperf/scripts/run_stream_bench.py --help` - -注意: - -- 如果你之前使用的是 `install --no-deps`,那么对应环境的 CLI 校验可能失败 -- 如果你只想检查 benchmark 环境,可以使用: - -```bash -python3 scripts/remote_bench_sync.py verify --skip-telefuser -``` - -### 6. 运行 benchmark - -#### 6.1 一键跑 `Wan2.1-I2V-14B-480P` 对比 - -如果远程机器上的 benchmark 资产和隔离环境已经准备好,可以直接用一条命令顺序跑: - -- `TeleFuser` -- `Diffusers` baseline - -```bash -python3 scripts/remote_bench_sync.py batch-compare \ - --framework both \ - --gpu 0 -``` - -这条命令会自动完成: - -- 校验远程 `telefuser` / `aiperf` 环境 -- 解析 TeleFuser batch 模型目录 -- 对指定 GPU 执行 `gpu_burner.sh stop ` -- 检查该 GPU 是否真的空闲 -- 拉起 TeleFuser 480P 服务 -- 跑对齐后的 TeleFuser AIPerf config -- 停掉 TeleFuser 服务 -- 拉起 Diffusers baseline 服务 -- 跑对齐后的 Diffusers AIPerf config -- 输出两边最新 `summary.json` 路径和关键指标 - -默认行为是**非破坏性的**: - -- 只会停止指定 GPU 上的 burner 占位 -- 不会主动杀掉远程机器上已有的其它计算任务 -- 如果 GPU 在停止 burner 之后仍然被真实任务占用,会直接失败 - -如果你希望它等待 GPU 空闲,而不是立即失败,可以显式给等待窗口: - -```bash -python3 scripts/remote_bench_sync.py batch-compare \ - --framework both \ - --gpu 0 \ - --gpu-idle-timeout 600 -``` - -如果你只想单独跑一边: - -```bash -python3 scripts/remote_bench_sync.py batch-compare --framework telefuser --gpu 0 -python3 scripts/remote_bench_sync.py batch-compare --framework diffusers --gpu 0 -``` - -这条自动化入口当前固定使用: - -- TeleFuser config: - `benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml` -- Diffusers config: - `benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml` - -如果远程环境还没准备好,先执行: - -```bash -python3 scripts/remote_bench_sync.py sync -python3 scripts/remote_bench_sync.py install --install-telefuser -python3 scripts/remote_bench_sync.py verify -``` - -#### 6.2 手工跑 TeleFuser batch benchmark - -当 TeleFuser 服务已经在远程机器上启动后,可以直接指定独立 benchmark 环境中的 `aiperf` 可执行文件: - -```bash -export AIPERF_BIN=/root/venv/telefuser-bench/bin/aiperf - -bash benchmarks/telefuser_aiperf/scripts/run_video_bench.sh \ - benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml -``` - -这样 benchmark 走独立环境,TeleFuser 服务继续走原来的运行环境,两边互不覆盖。 - -#### 6.3 手工跑流式 benchmark - -流式 benchmark 则直接走独立环境里的 Python: - -```bash -export TELEFUSER_STREAM_BENCH_PYTHON=/root/venv/telefuser-bench/bin/python - -bash benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh \ - benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json -``` - ## 为什么没有启用 AIPerf 自带 readiness probe 当前这套配置没有使用 AIPerf 的 `waitForModelTimeout` / `waitForModelMode`。 @@ -595,6 +395,10 @@ artifacts/telefuser_aiperf/ - `artifacts/telefuser_aiperf/video_e2e` - `artifacts/telefuser_aiperf/video_rate` - `artifacts/telefuser_aiperf/stream_lingbot_quick/` +- `artifacts/telefuser_aiperf/stream_lingbot_compare/` +- `artifacts/telefuser_aiperf/stream_transport_mock_compare/` +- `artifacts/sglang_lingbot_stream/stream_lingbot_compare/` +- `artifacts/sglang_lingbot_stream/stream_transport_mock_compare/` 通常可以关注: @@ -603,3 +407,269 @@ artifacts/telefuser_aiperf/ - server metrics JSON / CSV - stream benchmark 的逐 session JSONL - stream benchmark 的逐事件 JSONL +- stream benchmark 的 `target_metadata.json`,包含 target 启动 phase 和软件/硬件环境 snapshot +- stream benchmark 的 `stream_report.html`;这是 AIPerf 生成的跨系统统一查看界面,可直接用浏览器打开 + +## 历史指标服务(GreptimeDB) + +跨任务历史存储、API 和前端统一由 AIPerf 提供。TeleFuser 与 SGLang 只生成标准产物,不在 target 仓库实现数据库 client、历史 API +或专用前端分支。 + +GreptimeDB 可用后,先导入已有的 profile 和 stream 产物: + +```bash +uv run --project benchmarks/aiperf aiperf history ingest \ + --greptime-url http://127.0.0.1:4000 \ + --greptime-database public \ + --artifact-root artifacts \ + --artifact-root work_dirs/benchmarks +``` + +再启动只读 API 和内置 Vue 前端: + +```bash +uv run --project benchmarks/aiperf aiperf history serve \ + --greptime-url http://127.0.0.1:4000 \ + --greptime-database public \ + --artifact-root artifacts \ + --artifact-root work_dirs/benchmarks \ + --host 127.0.0.1 \ + --port 8095 +``` + +打开 `http://127.0.0.1:8095/` 查看历史任务、跨 Run 指标曲线,以及单 Run 的 session、control、phase、chunk、timeslice、GPU、 +Prometheus 和 normalized 指标。warmup 与 profiling 通过 `phase` 区分;客户端 `stream_fps` 与 target +`chunk_compute_fps` 保持为两个指标。 + +GreptimeDB 是强依赖。连接或建表失败会终止启动,运行期查询失败返回 HTTP 503;API 和 UI 不会切换到 SQLite、内存索引、文件直查 +或缓存结果。JSON/JSONL 仍是可重放输入,但不属于在线查询路径。 + +完整部署说明见 [AIPerf Benchmark History Dashboard](https://github.com/ActivePeter/aiperf/blob/teleai/docs/tutorials/history-dashboard.md),实现边界见 +[AIPerf Benchmark History Service Design](https://github.com/ActivePeter/aiperf/blob/teleai/docs/dev/history-service-design.md) 和 +[TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md)。 + +## 实测记录 + +### 2026-07-09,Wan2.1 I2V 480P batch benchmark + +运行环境: + +- 机器:远端 H100 测试环境,具体机器信息已脱敏 +- GPU:4 卡可见配置 +- 模型:Wan2.1-I2V-14B-480P +- 服务入口:TeleFuser Wan2.1 I2V 480P 固定 workload 服务 +- benchmark 配置:TeleFuser Wan2.1 I2V 480P compare 配置 +- artifact:已归档,具体路径不在公开文档中暴露 + +结果: + +| 指标 | 数值 | +| --- | ---: | +| warmup 请求数 | 1 | +| profiling 请求数 | 2 | +| profiling errors | 0 | +| benchmark duration | 325.49 s | +| request latency avg | 163084.80 ms | +| request latency p50 | 163084.80 ms | +| request latency p90 | 163373.28 ms | +| request latency p99 | 163438.19 ms | +| request latency min / max | 162724.20 / 163445.40 ms | +| request throughput | 0.00614 requests/s | + +备注:该配置的 `server_metrics.enabled=false`,所以 AIPerf 没有导出 GPU telemetry;运行过程中 GPU 4/5/6/7 处于高负载。 + +### 2026-07-09,LingBot-World-Fast stream benchmark + +运行环境: + +- 机器:远端 H100 测试环境,具体机器信息已脱敏 +- GPU:4 卡可见配置;LingBot-World-Fast 当前默认单卡 GPU-resident +- 模型:Wan2.2-I2V-A14B + LingBot-World-Fast +- 服务入口:TeleFuser LingBot stream 服务 +- benchmark 配置:TeleFuser LingBot stream compare 配置 +- artifact:已归档,具体路径不在公开文档中暴露 + +结果: + +| 指标 | 数值 | +| --- | ---: | +| profile sessions | 1 / 1 | +| failed sessions | 0 | +| success rate | 100% | +| configured session duration | 90.0 s | +| actual session runtime | 40.15 s | +| frames received | 406 | +| offer RTT | 6783.97 ms | +| WebRTC connected latency | 14432.81 ms | +| first metadata latency | 14540.01 ms | +| first frame latency | 14598.90 ms | +| stream FPS | 16.05 | +| control ack latency avg / p50 / p90 / max | 8.88 / 2.11 / 22.56 / 42.69 ms | +| control-to-next-frame latency avg / p50 / p90 / max | 37.60 / 36.63 / 53.76 / 55.50 ms | + +备注: + +- 本次是单 session benchmark,没有并发;当前 `LingBotWorldFastService` 也只允许一个 active session。 +- `stream_lingbot_world_fast_compare.json` 的 `session_duration_s=90` 是等待上限;本次服务在完成生成后发送 `done`,所以实际 session runtime 为 40.15 s。 +- 运行结束后 stream 服务仍为 `stream_ready=true`,服务所在 GPU 常驻显存约 47 GB。 + +### 2026-07-09,SGLang-Diffusion LingBot-World-Fast 早期可用性记录 + +运行环境: + +- 机器:远端 H100 测试环境,具体机器信息已脱敏 +- GPU:单卡可见配置 +- 模型:LingBot-World-Fast Diffusers layout +- 临时 Diffusers layout:已脱敏 +- benchmark 配置:SGLang LingBot stream compare 配置 +- artifact:已归档,具体路径不在公开文档中暴露 + +结果: + +| 指标 | 数值 | +| --- | ---: | +| profile sessions | 1 / 1 | +| failed sessions | 0 | +| success rate | 100% | +| configured session duration | 90.0 s | +| actual session runtime | 27.68 s | +| frames received | 93 | +| offer RTT | 81.00 ms | +| WebSocket connected latency | 81.01 ms | +| first metadata latency | 9896.79 ms | +| first frame latency | 9896.79 ms | +| stream FPS | 5.18 | +| control ack latency avg / p50 / p90 / max | 6622.36 / 6622.36 / 6622.36 / 6622.36 ms | +| control-to-next-frame latency avg / p50 / p90 / max | 6621.65 / 6621.65 / 6621.65 / 6621.65 ms | + +备注: + +- 本次是单 session benchmark,没有并发。 +- 该记录早于 `performance_mode=speed` 公平性约束,不作为当前 GPU-resident 正式对比表输入。 +- `request_extra` 显式设置 `realtime_causal_sink_size=9` 和 `realtime_causal_kv_cache_num_frames=18`;未设置时 SGLang 默认 45 帧 causal KV cache,在单张 H100 上 runtime OOM。 +- `SGLANG_LINGBOT_DISABLE_FLASHINFER_ROPE=1` 让 SGLang 使用 RoPE fallback。当前远端只有 pip CUDA 13 toolkit,`flashinfer` 首次 JIT RoPE kernel 会因为 CUDA compiler/header 不兼容失败。 +- 服务启动后常驻显存约 48 GB;运行过程中日志记录 peak memory usage 约 73522 MB。 + +### 2026-07-13,SGLang 1xH100 GPU-resident 正式 baseline + +运行条件: + +- 单张 80GB H100,`performance_mode=speed` +- DiT、text encoder、VAE 均驻留 GPU,无 CPU 或 layerwise offload +- `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` +- warmup 1 session,profile 1 session,每个 session 8 chunks +- 远端 FlashInfer 的 CUDA compiler/header 不兼容,因此启用了已记录的 PyTorch RoPE fallback + +同一服务得到两个必须分开解释的结果: + +| Cache 配置 | 结果 | 解释 | +| --- | --- | --- | +| sink 9 / window 18 | warmup 0/1、profile 0/1,CUDA OOM | 进程显存约 79.16 GiB,不能作为成功性能样本 | +| sink 6 / window 9 | warmup 1/1、profile 1/1 | 单卡 GPU-resident tuned baseline | + +6/9 tuned profile 指标: + +| 指标 | 数值 | +| --- | ---: | +| frames / chunks | 93 / 8 | +| warmup 排除后的 chunks | 7 | +| target compute FPS | 5.1906 | +| client stream FPS | 5.6788 | +| first frame latency | 3076.18 ms | +| session runtime | 19.277 s | +| chunk compute mean / p90 | 2.3119 / 2.3362 s | +| chunk encode mean | 0.0551 s | +| chunk total mean | 2.3699 s | +| control ack mean | 3333.92 ms | +| peak reserved allocator | 76,919,341,056 bytes(73,356 MiB) | + +成功产物位于 `work_dirs/benchmarks/sglang_lingbot_stream/h100_gpu_resident_speed_rope_fallback_peak_20260713/20260713_115343_a2eae617`;9/18 OOM 产物位于 `work_dirs/benchmarks/sglang_lingbot_stream/h100_gpu_resident_speed_official_cache_oom_20260713/20260713_115523_418b0f5e`。两者 cache 几何不同,不能合并成同配置对比结论。 + +### 2026-07-10,纯流式 mock benchmark + +运行环境: + +- 机器:本地/远端测试环境信息已脱敏 +- 模型:不加载真实模型 +- TeleFuser target:TeleFuser-compatible WebRTC mock service +- SGLang target:SGLang-style WebSocket + MessagePack mock service +- benchmark 配置:纯流式 mock compare 配置 +- artifact:已归档,具体路径不在公开文档中暴露 + +运行口径: + +- `session_count=4` +- `warmup_sessions=1` +- `session_duration_s=20` +- `fps=16` +- 同一份控制 trace + +结果: + +| 指标 | TeleFuser WebRTC mock | SGLang WebSocket mock | +| --- | ---: | ---: | +| profile sessions | 4 / 4 | 4 / 4 | +| failed sessions | 0 | 0 | +| offer RTT avg | 5009.97 ms | 1.99 ms | +| connected latency avg | 10518.66 ms | 2.00 ms | +| first frame latency avg | 10583.69 ms | 64.73 ms | +| stream FPS avg | 16.00 | 16.05 | +| frames received avg | 319 | 320 | +| control ack latency avg | 0.70 ms | 0.35 ms | +| control-to-next-frame latency avg | 25.63 ms | 21.93 ms | + +备注: + +- 这组 benchmark 不加载任何生成模型,只测 streaming / transport / control path。 +- 两边 steady-state FPS 和 control-to-next-frame 都接近目标帧率下的帧间隔,说明纯流式层不是真实世界模型低 FPS 的主要瓶颈。 +- TeleFuser mock 的首帧和 connected latency 主要受当前 WebRTC SDP/ICE 建连路径影响;它影响首帧,不代表模型推理慢。 + +### 2026-07-10,纯流式 mock 极限 FPS sweep + +运行环境: + +- 机器:本地/远端测试环境信息已脱敏 +- 模型:不加载真实模型 +- TeleFuser target:TeleFuser-compatible WebRTC mock service +- SGLang target:SGLang-style WebSocket + MessagePack mock service +- benchmark 配置:纯流式 mock 配置,逐次覆盖目标 FPS +- artifact:已归档,具体路径不在公开文档中暴露 + +运行口径: + +- 单 session sweep,每个 target 单独运行 +- `session_duration_s=6` +- TeleFuser mock 使用 `320x180` 合成视频帧 +- SGLang mock 每个 `frame_batch` 携带固定大小二进制 payload +- 两边都不加载 LingBot、Wan 或 SGLang diffusion runtime + +结果: + +| Target FPS | TeleFuser WebRTC mock 实测 FPS | SGLang WebSocket mock 实测 FPS | +| ---: | ---: | ---: | +| `30` | `30.02` | `28.82` | +| `60` | `60.18` | `54.04` | +| `120` | `120.05` | `104.58` | +| `240` | `241.25` | `179.87` | +| `480` | `355.92` | `260.40` | +| `960` | `394.97` | `394.82` | +| `1920` | 未运行 | `350.64` | + +补充指标: + +| Target | TeleFuser first-frame | TeleFuser control ack | SGLang first-frame | SGLang control ack | +| ---: | ---: | ---: | ---: | ---: | +| `30` | `10635.24 ms` | `1.79 ms` | `275.56 ms` | `4.60 ms` | +| `60` | `10673.16 ms` | `8.73 ms` | `73.53 ms` | `3.56 ms` | +| `120` | `10990.64 ms` | `5.34 ms` | `52.43 ms` | `2.23 ms` | +| `240` | `10728.59 ms` | `0.97 ms` | `36.18 ms` | `4.29 ms` | +| `480` | `10708.52 ms` | `4.53 ms` | `91.68 ms` | `0.66 ms` | +| `960` | `10768.45 ms` | `2.95 ms` | `48.53 ms` | `3.01 ms` | +| `1920` | 未运行 | 未运行 | `60.51 ms` | `3.09 ms` | + +备注: + +- TeleFuser WebRTC mock 在 `240 FPS` 前基本贴住目标,`480/960 FPS` 后开始饱和,本次最高观测约 `395 FPS`。 +- SGLang WebSocket mock 本次最高观测也约 `395 FPS`,出现在 `960 FPS` target;继续提高到 `1920 FPS` 后下降到约 `351 FPS`。 +- TeleFuser first-frame 仍约 `10.6s-11.0s`,主要反映当前 WebRTC SDP/ICE 建连和 harness 等待路径,不是模型耗时。 +- SGLang 这里统计的是 WebSocket + MessagePack `frame_batch`,不是 RTP video frame;这组 sweep 只能解释纯 streaming 层上限。 diff --git a/docs/zh/benchmark_aiperf_design.md b/docs/zh/benchmark_aiperf_design.md index ec5dc8a..5129671 100644 --- a/docs/zh/benchmark_aiperf_design.md +++ b/docs/zh/benchmark_aiperf_design.md @@ -1,311 +1,270 @@ # TeleFuser 与 AIPerf Benchmark 设计 -本文档记录 benchmark 的设计决策、对标边界和后续扩展方向。 - -具体跑法、脚本、参数和结果文件说明请看: - -- [TeleFuser 与 AIPerf Benchmark](benchmark_aiperf.md) - -## 1. Benchmark 分层 - -### 1.1 批处理视频服务 benchmark - -这类场景指的是: - -- 用户提交一次文本或图像条件 -- 服务端异步生成一个完整视频 -- 客户端轮询任务状态 -- 最终拿到视频文件 - -这一类当前最适合: - -- `telefuser serve` -- OpenAI 兼容 `/v1/videos` -- `AIPerf` 端到端 HTTP benchmark - -### 1.2 世界模型流式系统 benchmark - -这类场景指的是: - -- 长生命周期 session -- 持续输入控制信号 -- 持续输出视频帧或媒体流 -- 更关注控制反馈闭环,而不是单次完整视频生成 - -这一类更接近: - -- `telefuser stream-serve` -- WebRTC + DataChannel -- 世界模型在线交互推理 - -这两类负载的评价指标不同,不能混在同一套 benchmark 结论里。 - -### 1.3 世界模型实时流式系统框架 shortlist - -如果 TeleFuser 后面要做 `stream-serve`,更合适的对标对象应该是系统框架 / serving runtime,而不是模型本体。 - -当前更接近的开源系统层候选是: - -- `SGLang-Omni` -- `vLLM-Omni` -- `vLLM` -- `SGLang` -- `Ray Serve` -- `Triton Inference Server` - -如果需要实时媒体传输层,还可以单独观察: - -- `LiveKit` -- `Janus` -- `mediasoup` -- `aiortc` -- `Pion` - -这些是 transport 组件,不是推理框架本身。 - -需要明确区分的是: - -- `Matrix-Game`、`MineWorld`、`LongLive`、`CausVid`、`Helios` 更偏“模型 / 研究系统 / reference system” -- 它们适合用来定义负载和行为模式 -- 但不应混进“系统框架 shortlist” - -真正的世界模型流式端到端 benchmark 仍需要我们自己定义请求协议、会话状态和指标。 - -### 1.4 `stream-serve` 第一批系统框架对比顺序 - -如果后面要真的落地 `stream-serve` 的端到端 benchmark,第一批建议先比: - -1. `TeleFuser stream-serve` -2. `SGLang-Omni` -3. `vLLM-Omni` - -优先级原因很简单: - -- `SGLang-Omni` 更贴近我们现在想做的“长会话、多阶段、低延迟在线生成”系统形态 -- `vLLM-Omni` 可以作为更通用的第二基线 -- 两者都比通用 `Ray Serve` / `FastAPI` 更接近推理系统本体 - -如果需要把 transport 也纳入对比,再单独固定 WebRTC / SSE / WebSocket 实现,不要和推理框架混算。 - -### 1.5 批处理视频模型对标顺序 - -如果目标是评估 **TeleFuser 与其它视频推理框架** 的差异,那么第一轮 benchmark 必须满足下面这些约束: - -1. **模型相同** - - 例如都跑 `Wan2.1-I2V-14B-480P` - - 或都跑 `HunyuanVideo-1.5` - - 或都跑 `LTX-2` - -2. **任务相同** - - `t2v` 只和 `t2v` 比 - - `i2v` 只和 `i2v` 比 - - 不把 `t2v`、`i2v`、`ti2v` 混在一起 - -3. **输入相同** - - 同 prompt - - 同输入图 - - 同输出分辨率 - - 同帧数或等价生成长度 - - 同 diffusion steps - -4. **硬件相同** - - 同 GPU 型号 - - 同卡数 - - 同显存约束 - - 尽量控制一致的软件栈 - -5. **只变推理框架** - - 可以比较 `TeleFuser`、`Diffusers`、`xDiT`、`LightX2V`、`FastVideo`、`SGLang Diffusion` - - 但不能把“更换模型蒸馏版权重”误当成“只变框架” - -只要不满足这些条件,就不应被视为第一轮主 benchmark。 - -推荐优先级: - -1. `Wan2.1-I2V-14B-480P` -2. `HunyuanVideo-1.5` -3. `LTX-2` - -### 1.6 当前已落地的第一版可执行基线 - -仓库里已经补齐了第一版“同模型、只变框架”的可执行闭环,当前不是只停留在规划层面了: - -- TeleFuser 侧固定 480P 服务示例:[examples/wan_video/wan21_14b_image_to_video_480p_service.py](../../examples/wan_video/wan21_14b_image_to_video_480p_service.py) -- Diffusers 侧独立基线服务:[benchmarks/baseline/diffusers_wan_i2v/service.py](../../benchmarks/baseline/diffusers_wan_i2v/service.py) - -这版首个可直接开跑的对照组合固定为: - -- 模型:`Wan2.1-I2V-14B-480P` -- 分辨率:`832x480` -- 帧数:`81` -- 推理步数:`40` -- `guidance_scale=5.0` -- `fps=16` - -这里刻意把 TeleFuser 侧的 480P 服务从通用面积启发式改成了固定 `832x480`,目的是避免与官方 Diffusers 基线出现宽高不一致,保证第一轮对照可比。 - -### 1.7 AIPerf 对批处理视频负载的支持情况 - -AIPerf 对 `telefuser serve` 这类批处理视频服务已经有明确支持,当前覆盖的核心能力是: - -- OpenAI 兼容 `/v1/videos` 的异步提交 / 轮询 / 下载流程 -- `video_generation` endpoint -- `--download-video-content` 把下载时间纳入端到端时延 -- `multipart/form-data` 请求编码 -- `--extra-inputs` 传递视频生成参数 -- `single_turn` / `multi_turn` 这类输入组织方式 - -对 TeleFuser 这条线来说,AIPerf 已经能支撑两类最重要的批处理视频负载: - -1. **T2V** - - 直接用 `video_generation` endpoint - - 用文本 prompt 驱动 `/v1/videos` - -2. **I2V** - - 同样走 `video_generation` endpoint - - 通过 `reference_url` 或 `input_reference` 传入参考图 - - 我们当前的 `telefuser_aiperf` prompt 文件已经在用 `reference_url` 这条路 - -也就是说: - -- AIPerf 不是只能测纯文本视频生成 -- 它已经可以覆盖我们当前 TeleFuser 批处理视频 benchmark 里的 `T2V / I2V` 主线 -- 但它的上游文档和教程仍然更偏通用视频生成,不是专门为 TeleFuser 的 `I2V` 场景定制 - -当前对我们最有用的落点是: - -- `telefuser serve` + `AIPerf` 已足够做 batch video E2E benchmark -- `Wan2.1-I2V-14B-480P` 这条线已经可以直接跑 -- 后续若要扩展到更复杂的 `I2V` 变体,只需要继续补 prompt/schema 适配,而不是重做 benchmark 框架 - -### 1.8 Baseline 接口协议标准 - -随着后面要逐步接入更多对照实现,例如: - -- `Diffusers` -- `xDiT` -- `FastVideo` -- `LightX2V` -- `SGLang Diffusion` -- `vLLM-Omni` - -仓库里需要一套统一的 **baseline benchmark contract**,否则每接一个新 baseline,就会重复发明一套: - -- 目录布局 -- 启动脚本 -- 健康检查方式 -- HTTP / WebRTC 适配层 -- 指标采集字段 -- 文档说明格式 - -这里要标准化的不是 baseline 的内部实现,而是 **benchmark 视角下的最小可测协议**。 - -#### 1.8.1 标准化目标 - -第一版建议定义成 **versioned contract**: - -- `contract_version: v1` -- `mode: batch_video | stream_world` - -第一版只覆盖两类主负载: - -1. `batch_video` - - 对应 `telefuser serve` - - 对应 AIPerf 当前已经能覆盖的 `/v1/videos` 异步完整视频生成 - -2. `stream_world` - - 对应 `telefuser stream-serve` - - 对应 WebRTC 长会话、持续控制、持续输出的视频流 benchmark - -后续如果再扩展: - -- `audio_video_realtime` -- `multi_camera_world` -- `tool_augmented_agent_stream` - -应在 `v1` 之外继续演进,而不是一开始把所有可能性都揉进同一套协议。 - -#### 1.8.2 目录协议 - -第三方或对照实现统一放在: - -- `benchmarks/baseline//` - -推荐最小目录结构: - -- `README.md` -- `service.py` 或 `launcher.py` -- `configs/` -- `scripts/` -- `benchmark_contract.yaml` - -其中: +本文档记录 benchmark 的设计决策、架构边界、协议契约和资产组织。运行方式、环境变量和结果查看方法见[用户文档](benchmark_aiperf.md)。 + +## 1. 设计目标与对标原则 + +本设计将离线异步生成和实时流式交互拆成两种模式。两者的生命周期、并发单位和指标语义不同,因此分别定义 contract、harness 和结果产物。 + +| 模式 | 设计目标 | 核心观测 | 对标对象 | +| --- | --- | --- | --- | +| `batch_video` | 评估异步视频生成链路 | submit、poll、download 的端到端时延、吞吐和成功率 | 固定 Wan2.1-I2V-14B-480P,对比 TeleFuser 与 Diffusers | +| `stream_world` | 评估长 session 的持续帧输出和双向控制 | 首帧时延、稳定 FPS、控制确认和控制到下一帧时延 | 固定 LingBot world-model 语义,对比 TeleFuser WebRTC 与 SGLang-Diffusion WebSocket | + +核心设计原则: + +- **控制变量**:正式对标必须固定模型、任务、输入、输出形状和控制 trace,只替换推理框架及其必要的协议适配。 +- **场景隔离**:`batch_video` 与 `stream_world` 独立计量,完整视频吞吐与实时 session FPS 不交叉解释。 +- **契约与黑盒**:contract 定义可测协议;target service 的并行、batching 和调度实现只通过结果指标与 server metrics 观测。 +- **传输归一化**:stream adapter 隔离 WebRTC 和 WebSocket 的 wire protocol 差异,输出统一的 session result。 +- **证据分级**:正式配置用于性能对比;降级配置和 mock 只用于连通性或传输层验证。 + +## 2. 整体架构与依赖边界 + +Benchmark 体系包含 contract、runner、harness、target service、input data 和 artifacts 六类组件。 + +```mermaid +flowchart LR + Runner["Runner"] --> BatchContract["Batch Contract"] + Runner --> BatchHarness["AIPerf Batch Harness"] + BatchContract -.-> BatchHarness + BatchData["Prompt + Reference Image"] --> BatchHarness + BatchHarness --> TFVideo["TeleFuser /v1/videos"] + BatchHarness --> DiffusersVideo["Diffusers /v1/videos"] + BatchHarness --> BatchArtifacts["summary / records / HTTP trace"] + + Runner --> StreamContract["Stream Contract"] + Runner --> StreamHarness["Stream Harness + Adapter"] + StreamContract -.-> StreamHarness + StreamData["First Frame + Control Trace"] --> StreamHarness + StreamHarness --> TFStream["TeleFuser WebRTC"] + StreamHarness --> SGLangStream["SGLang WebSocket"] + StreamHarness --> StreamArtifacts["summary / sessions / events"] +``` -- `telefuser_aiperf/` 不是 baseline,它是 TeleFuser 自己的 benchmark harness -- `baseline/` 下面放“只变推理框架”的对照实现 -- `benchmark_contract.yaml` 用来描述 baseline 能力,而不是替代 README -- 当前 TeleFuser benchmark harness 和 Diffusers baseline 已经分别提供了第一版薄 contract: - - [benchmarks/telefuser_aiperf/benchmark_contract.yaml](../../benchmarks/telefuser_aiperf/benchmark_contract.yaml) - - [benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml](../../benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml) +组件定义: + +- **Contract** + - 定位:benchmark target 的协议契约,是 harness 与 target service 之间的稳定边界。 + - 声明内容: + - `contract_version`:contract schema 版本。 + - `name`:target contract 的唯一名称。 + - `mode`:benchmark 场景类型,例如 `batch_video` 或 `stream_world`。 + - `implementation`:被测实现类型,例如 `telefuser`、`diffusers` 或 `sglang_diffusion`。 + - `model_family`、`model` 和 `supported_tasks`:模型语义与任务边界。 + - `transport`:harness 与 target service 的通信方式,例如 HTTP、WebRTC 或 WebSocket。 + - `endpoint`、`request_encoding` 和 `result_delivery`:服务路径、请求编码与结果返回方式。 + - `workload`:固定负载参数,包括模型、任务、分辨率、帧数、FPS、steps、seed 或 control trace。 + - `metrics`:该 target 允许解释的指标集合,例如 latency、throughput、FPS、control latency 或 success rate。 + - `limits`:active sessions 等 target 能力约束。 + - `artifacts`:配置、数据、脚本和结果产物的索引,用于自动化和结果复查。 + - 边界:只描述“如何被测”和“产出什么”,不描述服务部署流程或模型内部实现。 + +- **Runner** + - 定位:benchmark 编排入口。 + - 职责: + - 选择 target 并加载 contract。 + - 确认 target service 可测后启动 harness。 + - 返回执行状态和结果目录。 + - 边界:不解释模型内部行为,不直接承担协议适配和指标计算。 + +- **Harness** + - 定位:实际发起请求或 session 的测试执行器。 + - 职责: + - 控制请求或 session 并发及其生命周期。 + - 记录事件时间并调用 transport adapter。 + - 将观测结果归一化为 mode 对应的指标和产物。 + - 边界:不加载模型,不改变 workload 语义。 + +- **Target service** + - 定位:被测服务或 baseline 服务。 + - 职责: + - 加载模型并执行调度和推理。 + - 维护 task 或 session 状态并生成媒体。 + - 边界:在 benchmark 视角下被当作黑盒,只通过 contract 暴露的协议面交互。 + +- **Input data** + - 定位:固定 workload 的输入资产。 + - 内容: + - Batch 使用的 prompt、参考图和请求顺序。 + - Stream 使用的 first frame、timed control trace 和 session 顺序。 + - 边界:只保证不同 target 的输入语义一致,不携带 transport-specific 计时逻辑。 + +- **Artifacts** + - 定位:benchmark 输出产物。 + - 内容: + - `summary`:benchmark 级聚合结果,记录吞吐、时延、成功率、FPS 等可用于横向对比的汇总指标。 + - `records`:Batch 场景的请求级明细,记录单次请求的生命周期、状态、时延和错误信息。 + - `sessions`:Stream 场景的 session 级明细,记录连接状态、持续时间、帧统计和控制统计。 + - `events`:Stream 场景的事件级时间线,记录帧接收、控制发送、确认、状态变化和异常事件。 + - `HTTP trace`:Batch 场景的 HTTP 交互轨迹,记录 submit、poll、download 等协议阶段的请求与响应信息。 + - `server metrics`:target service 的服务端观测数据,记录资源使用率、runtime 状态以及框架特有指标。 + - 边界:按 benchmark mode 独立落盘,不混合 Batch 与 Stream 的结果语义。 + +仓库边界: + +- **指标观测工具的唯一实现边界是 `ActivePeter/aiperf` 的 `teleai` 分支。** 采集生命周期、Prometheus + 解析、原始记录模型、聚合、导出、统一语义映射和流式事件关联均在该分支实现,不在 TeleFuser 中维护第二套实现。 +- `benchmarks/telefuser_aiperf/` 只保留 TeleFuser target 的声明式 contract、配置、输入数据和薄启动入口。 + `run_stream_bench.py` 与 `run_stream_bench.sh` 仅将目标配置转交给 `aiperf profile --stream-config`;session + 执行、指标汇总和产物导出均由 AIPerf 完成。 +- `benchmarks/baseline/` 只承载 baseline service wrapper、声明式 contract 和必要的 wire adapter 配置;不同 baseline + 必须对齐同一 workload 与可观测协议,不能各自实现指标计算器。 +- `benchmarks/aiperf/` 是 [setup 脚本](../../scripts/setup_aiperf_repo.sh)拉取的外部 checkout,不 vendor 到 + TeleFuser。正式结果必须记录并固定 `teleai` commit,不能只记录可移动的分支名。 + +### 2.1 外部依赖与接入形式 + +外部依赖按源码影响分为三类: + +- **外部包装**:在 AIPerf 中实现跨系统 adapter;TeleFuser 仓库只写 service wrapper、contract、config 或 mock,不改变第三方库行为。 +- **运行时 shim**:不改第三方文件,但通过 `PYTHONPATH`、`sitecustomize.py` 或 monkey patch 改变其行为。 +- **上游源码修改**:直接修改第三方源码,或维护带 patch 的外部 fork。 + +| 外部依赖 | 接入形式 | 源码影响 | 版本与 Fork 决策 | +| --- | --- | --- | --- | +| AIPerf | [setup 脚本](../../scripts/setup_aiperf_repo.sh)将 `ActivePeter/aiperf` 的 `teleai` 分支拉取到 `benchmarks/aiperf/`;Batch 复用 `video_generation` 和 `server_metrics`,Stream 能力在同一分支内演进 | **有**:benchmark 与指标观测功能只修改外部 fork;TeleFuser 主仓只保留声明式接入资产 | 当前唯一落点为 `teleai`;正式运行 pin commit,接口稳定后再单独决定是否回流 upstream | +| Diffusers | wrapper 将 `WanImageToVideoPipeline` 暴露为 `/v1/videos` | **否**:仅新增外部包装 | 不需要 fork;改变 pipeline 语义时再升级为 fork | +| SGLang-Diffusion | AIPerf adapter 接入 `sglang.multimodal_gen` 和 WebSocket endpoint | 窄协议插桩:原生 `chunk_stats` 增加已有 `OutputBatch.peak_memory_mb`;环境 shim 仍只处理缺失依赖或不兼容 kernel | 指标字段应落入版本化 SGLang 分支或 upstream PR;SGLang 不依赖 AIPerf 包 | +| WebRTC / aiortc | AIPerf adapter 执行 offer/answer、media track 和 DataChannel;TeleFuser 只暴露服务面 | **否**:客户端 ICE 策略位于 AIPerf,服务端策略位于 TeleFuser | 不需要 fork | +| WebSocket / MessagePack | adapter 编解码 `init`、`camera_actions`、`chunk_stats` 和 `frame_batch` | **否**:不修改 `websockets` 或 `msgspec` | 不需要 fork | +| 模型权重与输入数据 | 各 runtime 加载模型,benchmark 固定 workload 和输入 | **否**:不修改权重 | 配置修正单独版本化,必要时维护模型侧 fork | + +Fork 决策以性能语义为界。Wrapper、contract、config 和 mock 不需要 fork;改变 runtime、kernel fallback 或模型加载语义的改动必须进入版本化 fork 或 upstream。未版本化的 shim 不能作为正式性能结论的基础。 + +## 3. 执行时序与并发模型 + +### 3.1 Batch Video 并发模型 + +Batch 以视频生成请求为并发单位。每个请求持有独立 `video id`,poll 和可选 download 均归属该 id。 + +```mermaid +sequenceDiagram + autonumber + participant User as 用户/CI + participant Runner as Benchmark Runner + participant Contract as Benchmark Contract + participant Service as Batch Video Service + participant Harness as AIPerf video_generation + participant Data as Prompt / Reference Data + participant Artifacts as Artifacts + + User->>Runner: 选择 batch_video target + Runner->>Contract: 加载并校验 batch contract + Contract-->>Runner: 返回 HTTP 协议、workload、metrics 与产物约束 + Runner->>Service: 准备 TeleFuser 或 Diffusers batch target + Runner->>Service: GET /v1/service/health + Service-->>Runner: ready + Runner->>Harness: 进入 batch profile 流程 + Harness->>Data: 读取 prompt 与 I2V 参考图 + + loop 每个视频生成请求 + Harness->>Service: POST /v1/videos + Service-->>Harness: video id + loop 直到 completed/failed/cancelled + Harness->>Service: GET /v1/videos/{id} + Service-->>Harness: status/progress/metrics + end + opt downloadVideoContent + Harness->>Service: GET /v1/videos/{id}/content + Service-->>Harness: 生成视频文件 + end + end + + Harness->>Artifacts: 写入 summary、records、HTTP trace + Harness-->>Runner: 返回结果目录与执行状态 + Runner-->>User: 汇总 request latency、throughput、success rate +``` -#### 1.8.3 能力声明协议 +并发与计量边界: + +- 并发单位:单次 HTTP video request,由 AIPerf 的 concurrency、request count、duration 和到达率模型驱动。 +- 状态隔离:每个请求的 `video id` 独立,poll 和 download 不跨请求复用。 +- 计量范围:从 submit 覆盖到终态;启用媒体下载时,download 计入端到端时延。 + +### 3.2 Stream World 并发模型 + +Stream 以 session 为并发单位。帧接收、控制注入和状态变化都记录在同一 session 时间线内。 + +```mermaid +sequenceDiagram + autonumber + participant User as 用户/CI + participant Runner as Benchmark Runner + participant Contract as Benchmark Contract + participant Service as World Model Service + participant Harness as Stream Harness + participant Data as Session / Control Data + participant Artifacts as Artifacts + + User->>Runner: 选择 stream_world target + Runner->>Contract: 加载并校验 stream contract + Contract-->>Runner: 返回 session、transport、metrics 与产物约束 + Runner->>Service: 准备 TeleFuser stream 或 SGLang-Diffusion target + Runner->>Service: health check + Service-->>Runner: ready + Runner->>Harness: 进入 stream session 流程 + Harness->>Data: 读取 session plan、first frame 与 control trace + + alt TeleFuser WebRTC target + Harness->>Service: POST /v1/stream/webrtc/offer + Service-->>Harness: WebRTC answer + session id + Harness->>Service: 建立 media track 和 DataChannel + else SGLang-Diffusion WebSocket baseline + Harness->>Service: WebSocket /v1/realtime_video/generate + Harness->>Service: 发送 init payload + Service-->>Harness: session started + end + + loop session_duration_s 内持续采集 + Harness->>Service: 注入 timed control event + Service-->>Harness: control stats / chunk stats + Service-->>Harness: video frame / frame batch + end + + alt TeleFuser WebRTC target + Harness->>Service: DELETE /v1/stream/webrtc/{session_id} + else SGLang-Diffusion WebSocket baseline + Harness->>Service: 关闭 WebSocket session + end + + Harness->>Artifacts: 写入 summary、sessions 与 events + Harness-->>Runner: 返回结果目录与执行状态 + Runner-->>User: 汇总 first-frame latency、FPS、control latency +``` -每个 baseline 建议暴露一个能力声明文件,例如 `benchmark_contract.yaml`,最少包含: +并发与计量边界: -- `contract_version` -- `baseline_name` -- `mode` -- `model_family` -- `supported_tasks` -- `transport` -- `request_encoding` -- `result_delivery` -- `metrics` +- 并发单位:session,由 `session_count` 和 `stagger_s` 描述 session 数量与错峰策略。 +- 生命周期:`session_duration_s` 内的控制、帧和状态事件不能解释为独立请求。 +- 当前限制:`LingBotWorldFastService` 只允许一个 active session,因此当前结果按单 session 解释;多 session 参数为后续扩展保留。 -建议第一版字段示例: +## 4. 契约、协议与观测模型 -```yaml -contract_version: v1 -baseline_name: diffusers_wan_i2v -mode: batch_video -model_family: wan21_i2v_14b_480p -supported_tasks: - - i2v -transport: http -request_encoding: - - multipart_form - - json -result_delivery: - - poll_status - - download_content -capabilities: - supports_reference_url: true - supports_input_reference_upload: true - supports_cancel: true -metrics: - - request_latency - - completion_latency - - download_latency - - peak_memory_mb -``` +### 4.1 Contract 设计 -这一层的价值是: +第 2 节定义 contract schema。本节列出各 target 的 contract 实例及其协议责任。 -- benchmark 脚本能先检查 baseline 支持什么 -- 文档和自动化部署脚本可以直接消费这份声明 -- 后续接新 baseline 时,先补 contract,再补适配脚本 +| Contract | Target | Mode | Implementation | Transport | 设计责任 | +| --- | --- | --- | --- | --- | --- | +| [TeleFuser Batch](../../benchmarks/telefuser_aiperf/benchmark_contract.yaml) | Wan2.1-I2V | `batch_video` | `telefuser` | `http` | 声明 `/v1/videos` workload 与产物 | +| [Diffusers Batch](../../benchmarks/baseline/diffusers_wan_i2v/benchmark_contract.yaml) | Wan2.1-I2V baseline | `batch_video` | `diffusers` | `http` | 对齐 TeleFuser fixed workload 与 HTTP 面 | +| [TeleFuser Stream](../../benchmarks/telefuser_aiperf/stream_benchmark_contract.yaml) | LingBot stream | `stream_world` | `telefuser` | `webrtc` | 声明 offer、DataChannel、media track 与指标 | +| [SGLang Stream](../../benchmarks/baseline/sglang_lingbot_stream/benchmark_contract.yaml) | LingBot baseline | `stream_world` | `sglang_diffusion` | `websocket` | 声明 MessagePack init/control/frame batch 语义 | -#### 1.8.4 `batch_video` 最小接口协议 +### 4.2 Batch Video HTTP 协议边界 -对 `batch_video` 类型 baseline,建议统一最小 HTTP 面: +`batch_video` 的协议边界统一收敛到 OpenAI-compatible `/v1/videos` 异步任务模型。 -- `GET /v1/service/health` -- `POST /v1/videos` -- `GET /v1/videos/{id}` -- `GET /v1/videos/{id}/content` -- `DELETE /v1/videos/{id}` 可选 +| 协议面 | 路径或字段 | 设计约束 | +| --- | --- | --- | +| Health | `GET /v1/service/health` | 只表达服务可测状态,不承载 workload 参数 | +| Submit | `POST /v1/videos` | 创建独立 video task,返回 `video id` | +| Poll | `GET /v1/videos/{id}` | 读取状态、进度和任务级 metrics | +| Content | `GET /v1/videos/{id}/content` | 下载生成媒体,用于端到端 download latency 计量 | +| Cancel/Cleanup | `DELETE /v1/videos/{id}` | 释放任务状态或取消未完成任务 | -最小请求语义建议统一为: +最小请求字段: - `prompt` - `model` @@ -313,11 +272,10 @@ metrics: - `seconds` - `seed` - `negative_prompt` -- `reference_url` 或 `input_reference` +- `input_reference` +- `reference_url` -允许 baseline 自己额外扩展字段,但 benchmark 第一轮只依赖最小公共子集。 - -`GET /v1/videos/{id}` 的最小返回字段建议统一为: +最小状态返回字段: - `id` - `status` @@ -325,9 +283,12 @@ metrics: - `created_at` - `completed_at` - `error` -- `url` 或 `file_path` +- `url` +- `file_path` +- `inference_time_s` +- `peak_memory_mb` -其中状态集合建议至少覆盖: +状态集合: - `queued` - `generating` @@ -335,350 +296,512 @@ metrics: - `failed` - `cancelled` -这样 AIPerf 和后续自定义 harness 都可以复用同一套 submit / poll / download 流程。 - -#### 1.8.5 `stream_world` 最小接口协议 - -对 `stream_world` 类型 baseline,建议统一最小会话面: - -- `GET /v1/service/health` -- `POST /v1/stream/webrtc/offer` -- `DELETE /v1/stream/webrtc/{session_id}` - -如果使用 DataChannel,建议至少约定这些消息类别: - -- `control` -- `status` -- `chunk` -- `done` -- `error` - -其中: - -- `control` 由客户端发送,代表方向控制、动作控制或结构化控制输入 -- `status` 由服务端发送,代表会话阶段和状态机推进 -- `chunk` 由服务端发送,代表一批可消费的输出 -- `done` 代表本轮或整个 session 正常结束 -- `error` 代表 session 失败或不可恢复异常 - -不要求所有 baseline 都与 TeleFuser 的 DataChannel payload 完全一致,但必须有一层 adapter 能映射到这套 benchmark 语义。 - -#### 1.8.6 指标协议 - -接口统一之后,还需要统一结果侧最小指标集合。 - -`batch_video` 至少建议采: - -- `request_latency` -- `queue_latency` -- `completion_latency` -- `download_latency` -- `success_rate` -- `throughput` - -`stream_world` 至少建议采: - -- `offer_rtt` -- `connected_latency` -- `first_frame_latency` -- `first_metadata_latency` -- `steady_state_fps` -- `control_ack_latency` -- `control_to_next_frame_latency` -- `session_success_rate` - -如果 baseline 能提供服务端附加指标,也建议通过统一字段上报,例如: - -- `inference_time_s` -- `peak_memory_mb` -- `server_metrics_url` - -#### 1.8.7 非目标 - -这套标准当前 **不** 试图做下面这些事情: - -- 不统一 baseline 的内部代码结构 -- 不强制所有 baseline 使用同一种 Web 框架 -- 不强制所有 baseline 使用同一种任务队列实现 -- 不把 transport 协议细节过度收紧到无法接第三方系统 - -也就是说: - -- 我们标准化的是“怎么测” -- 不是“怎么实现” - -#### 1.8.8 第一批落地顺序 - -这套 baseline contract 第一批建议先落到: - -1. `benchmarks/baseline/diffusers_wan_i2v/` -2. 后续第二个 batch baseline -3. 再考虑 `stream_world` 类型 baseline - -原因是: - -- `diffusers_wan_i2v` 已经是现成的第一版对照实现 -- 最容易先把目录协议、能力声明和 batch_video 最小接口约束跑通 -- `stream_world` 的协议复杂度更高,适合在 batch contract 稳定后再继续抽象 +约束: + +- AIPerf `video_generation` endpoint 负责把 single-turn 数据转换成 OpenAI video payload,并执行 submit/poll/download 生命周期。 +- `extra` 字段仅允许承载生成参数,严禁覆盖 `input_reference` 或 `reference_url` 等由 endpoint 统一管理的媒体字段。 +- TeleFuser 与 Diffusers baseline 必须暴露同一异步任务语义,避免 harness 分支逻辑影响对比结论。 + +### 4.3 Stream World 协议边界 + +`stream_world` 统一 session result 语义,但允许不同 transport。Adapter 将 WebRTC/DataChannel 和 WebSocket/MessagePack 事件映射到同一时间线与指标字段。 + +| 维度 | TeleFuser Target | SGLang-Diffusion Baseline | 归一化要求 | +| --- | --- | --- | --- | +| Health | `GET /v1/service/health` | `GET /health` | 只用于 target 可测状态判断 | +| Session start | `POST /v1/stream/webrtc/offer` | WebSocket `/v1/realtime_video/generate` + `init` message | 计入连接建立和 first metadata/first frame 时延 | +| Control channel | DataChannel label `telefuser` | WebSocket `event` message / `camera_actions` | 映射到统一 control event timeline | +| Media delivery | RTP video track | MessagePack `frame_batch` | 映射到 frame receive timeline 和 stream FPS | +| Metadata delivery | DataChannel status/chunk messages | MessagePack `chunk_stats` | 映射到 session event log 和 control ack 近似 | +| Session cleanup | `DELETE /v1/stream/webrtc/{session_id}` | WebSocket close | 结束 session lifecycle,不产生 batch request 语义 | + +AIPerf streaming 目标复用层: + +| 文件 | 设计角色 | +| --- | --- | +| [contracts.py](../../benchmarks/aiperf/src/aiperf/streaming/contracts.py) | contract 加载与校验 | +| [models.py](../../benchmarks/aiperf/src/aiperf/streaming/models.py) | `StreamSessionPlan`、`SessionResult` 和 summary 聚合 | +| [protocols.py](../../benchmarks/aiperf/src/aiperf/streaming/protocols.py) | workload、adapter 和 metrics collector 协议 | +| [transports.py](../../benchmarks/aiperf/src/aiperf/streaming/transports.py) | transport descriptor | +| [config.py](../../benchmarks/aiperf/src/aiperf/streaming/config.py) | `--stream-config` 类型化配置与安全加载 | +| [workload.py](../../benchmarks/aiperf/src/aiperf/streaming/workload.py) | contract 到统一 session plan 的转换 | +| [telefuser_webrtc.py](../../benchmarks/aiperf/src/aiperf/streaming/adapters/telefuser_webrtc.py) | TeleFuser WebRTC/RTP/DataChannel adapter | +| [sglang_websocket.py](../../benchmarks/aiperf/src/aiperf/streaming/adapters/sglang_websocket.py) | SGLang MessagePack WebSocket adapter | +| [profile.py](../../benchmarks/aiperf/src/aiperf/streaming/profile.py) | health、phase、server metrics 与自动产物编排 | + +截至 2026-07-13,远端 `ActivePeter/aiperf` 的 `teleai` 分支指向 `d72160e20957013d6608afcc88ed24100cb27dc5`。 +本地 `teleai` checkout 已实现并测试 `aiperf.streaming` 与 `aiperf.observability` 公共接口,但这些改动尚未提交或推送, +因此仍不能作为远端分支已发布该能力的证据。正式使用前必须提交到 `teleai`,并在结果中记录包含这些实现的新 commit。 + +### 4.4 指标观测协议与 AIPerf 收敛边界 + +指标观测按“客户端端到端事件 → 服务端原始指标 → 统一语义派生视图 → 关联分析”分层处理。所有工具代码收敛到 +`ActivePeter/aiperf@teleai`;TeleFuser 和 baseline 只暴露被观测面,不依赖 AIPerf 的 Python 包,也不负责抓取、聚合或导出。 + +AIPerf 内的权威接口与协议说明见 [Multimodal Generation and Observability Contracts](../../benchmarks/aiperf/docs/benchmark-modes/multimodal-generation.md)。 +本节只保留 TeleFuser 接入边界;两处语义冲突时以 AIPerf 文档和对应 schema 为准。 + +#### 4.4.1 当前能力与发布状态 + +| 能力 | 远端基线与本地 `teleai` 状态 | 设计结论 | +| --- | --- | --- | +| Prometheus 多端点采集 | 已提交,位于 `src/aiperf/server_metrics/` | 直接复用,不在 TeleFuser adapter 中实现 parser 或定时抓取 | +| profiling window 聚合 | 已提交,支持 baseline、最终 scrape、时间过滤和部分端点失败 | 作为 Batch 与 Stream 共用的服务端观测底座 | +| 原始及汇总导出 | 已提交,支持 JSON、CSV、Parquet 和可选 JSONL | 原始时序保留为事实源,其他视图均可重建 | +| Stream session 模型、协议与通用 runner | 本地已实现并通过单元测试,远端 `d72160e` 尚无 | 提交到 `teleai` 后成为跨 transport 公共能力 | +| WebRTC 与 WebSocket adapter | 本地已迁入 `aiperf.streaming.adapters` | TeleFuser 与 SGLang 目标仓不再实现 session runner | +| 标准 CLI 接入 | 本地已接入 `aiperf profile --stream-config` | 同一 CLI 可选择任意注册的 stream adapter | +| Stream 与 server metrics 共用生命周期 | 本地已实现 phase callback、profiling window、baseline/final scrape 和 `ServerMetricsResults` 关联 | media connection 保留在 profile controller event loop;采集复用既有 collector/accumulator,不复制 parser | +| 跨框架统一语义映射 | 本地已实现 mapper、显式状态及 `telefuser-v1` 声明文件 | 映射位于 AIPerf,不把框架判断写入通用 collector | +| 客户端事件与服务端时序关联 | 本地已自动导出 session、event、server export、normalized metrics 与 manifest | 仍需提交并推送后才能作为远端发布能力引用 | + +#### 4.4.2 职责协议 + +| 组件 | 必须负责 | 明确不负责 | +| --- | --- | --- | +| Target service | 暴露稳定、低开销的 Prometheus 文本 endpoint;对只能在进程内测量的 phase/chunk 提供有界原始 facts;保持 type、unit 和设备语义稳定 | 不计算跨框架统一指标,不执行统计聚合,不写 benchmark 产物 | +| AIPerf collector | endpoint 发现、可达性检查、抓取、解析、时间戳、错误隔离和原始记录 | 不包含 `if framework == ...` 一类框架分支,不改变 target 行为 | +| AIPerf mode harness | 生成 request/session/event 客户端时间线,驱动统一 phase 生命周期 | 不从服务端内部指标反推端到端时延 | +| AIPerf semantic mapper | 根据版本化规则生成统一指标域和 capability 状态 | 不覆盖、重命名或删除原始指标 | +| AIPerf artifact layer | 写入 schema、run identity、时间窗口、原始数据、派生视图和完整性状态 | 不把缺失数据静默转换为零,不隐藏端点失败 | +| TeleFuser 仓库资产 | 声明 endpoint、workload、capability 和 target 参数 | 不维护 collector、mapper、aggregator 或 exporter 的副本 | + +TeleFuser 的最小服务端观测面是 `GET /v1/service/metrics` 返回的 Prometheus 文本。AIPerf 不消费 +`/v1/service/metrics/json`,因为 target-specific JSON 会把解析逻辑重新耦合到 TeleFuser。Baseline 可以使用不同路径,例如 +`/metrics`,但必须提供相同的 Prometheus exposition 语义。 + +#### 4.4.3 采集入口协议 + +服务端指标入口采用以下约束: + +- Transport:AIPerf 对每个配置的 metrics URL 执行 HTTP `GET`;多个 replica 或独立 runtime 使用多个 URL,不能在抓取后丢失来源。 +- Encoding:响应使用 Prometheus text exposition format。当前 AIPerf 解析 `counter`、`gauge`、`histogram` 和 + `untyped/unknown`;`summary` 因累计窗口无法严格限定到单次 benchmark,不进入正式聚合。 +- Identity:`endpoint_url + metric_name + 完整 labels` 唯一标识一条原始 series。URL 在日志和导出元数据中必须脱敏。 +- Timestamp:AIPerf 使用抓取请求的 `request_sent_ns`、`first_byte_ns` 和 `endpoint_latency_ns`,并以客户端观察到的 + first-byte 时间近似快照时间,避免依赖 target 主机时钟。 +- Labels:只允许有界、稳定、无敏感信息的维度。prompt、输入媒体路径、用户标识、request UUID 和 session UUID + 不得成为长期 Prometheus label,避免高基数和数据泄露。 +- Compatibility:单个 endpoint 不可达、返回非 Prometheus 内容或部分样本无效时,观测链路降级但 benchmark 请求继续; + AIPerf 必须在产物中记录端点状态和数据完整性。 + +Target 可以继续维护生产监控指标,但 benchmark 不要求其实现 AIPerf 专用 SDK。长期、跨请求的状态应定义为稳定的 +Prometheus metric;只能在单 session 内准确测量的 phase/chunk facts 使用下述通用 measurement envelope,由 AIPerf adapter +归一化,不能塞入高基数 Prometheus label。 + +#### 4.4.4 生命周期协议 + +服务端指标采集与 workload 使用同一 AIPerf run lifecycle: + +| 阶段 | AIPerf 动作 | 数据解释 | +| --- | --- | --- | +| `configure` | 解析配置、发现 URL、检查可达性 | 只决定 capability,不进入性能统计 | +| `baseline` | 在 workload 前执行初始 scrape | 为 counter/histogram delta 提供起点 | +| `warmup` | 持续抓取,但标记为 warmup | 可保留原始时序,不进入正式汇总 | +| `profiling` | 同时采集客户端事件和服务端指标 | 只有该窗口进入正式聚合和跨框架对比 | +| `complete` | workload 停止后立即执行最终 scrape,再 flush/export | 捕获末尾 counter/histogram 增量,形成完整闭区间 | +| `cancel/error` | 尽力执行最终抓取和 flush,并标记 incomplete | 不得将不完整结果伪装为成功 run | + +`stream_world` 不能绕开这套生命周期单独写 summary。每个 session 仍记录自己的 `session_id` 和相对事件时间,AIPerf +同时维护 run 级 profiling window;stream profile 通过 `StandaloneServerMetricsCollector` 复用既有 +`ServerMetricsDataCollector` 与 accumulator,一次采集 run 级服务端时序,不为每个 session 启动重复 scraper。 + +#### 4.4.5 原始记录协议 + +AIPerf 现有 `ServerMetricsRecord` 作为服务端事实记录,正式产物至少保留以下信息: + +| 字段 | 语义 | 当前状态 | +| --- | --- | --- | +| `endpoint_url` | 原始 series 的来源,导出前脱敏 | 已有 | +| `timestamp_ns` | AIPerf 观察到的快照时间 | 已有 | +| `request_sent_ns` / `first_byte_ns` / `endpoint_latency_ns` | 抓取链路时间,用于判断采样抖动和观测开销 | 已有 | +| `metric_name` / `type` / `description` | Prometheus family 元数据 | 已有 | +| `labels` | 未丢失维度的原始 label map | 已有 | +| `value` 或 `buckets` / `sum` / `count` | gauge/counter 或 histogram 原始值 | 已有 | +| `benchmark_id` / profiling window | 将抓取记录限定到本次 run | 汇总层已有,原始导出需保持可关联 | +| `schema_version` / `aiperf_version` | 消费方兼容性依据 | JSON 汇总已有;所有新产物必须携带 | +| `aiperf_commit` / `mapping_version` / contract digest | 可复现实验和派生语义 | manifest 已写入;未提交工作树的 commit 仍指向基线,因此正式结果必须在提交后生成 | + +原始记录必须先落事实模型,再生成 delta、percentile 或统一名称。Counter reset、histogram bucket 变化、非有限值丢弃和重复 scrape +都必须显式记录或在完整性状态中可见,不能由 adapter 静默修正。 + +#### 4.4.6 统一语义映射协议 + +统一指标是 AIPerf 内的派生视图。映射规则也必须位于 `ActivePeter/aiperf@teleai`,建议使用版本化声明文件而不是 Python +条件分支。每条规则至少声明: -这部分设计一旦落地,后续新增 baseline 时应优先遵守 contract,再补实现,而不是先写一套散落脚本再回头整理。 - -#### 1.8.9 Batch Compare 自动化约束 - -对于第一版 `Wan2.1-I2V-14B-480P` 对照,不应该继续靠手工 SSH 一条条拼: - -- 停 burner -- 起 TeleFuser -- 跑 AIPerf -- 停服务 -- 起 baseline -- 再跑一次 - -这条链路现在应该固化成一个总入口: - -```bash -python3 scripts/remote_bench_sync.py batch-compare +```yaml +mapping_version: v1 +framework: telefuser +rules: + - source_metric: telefuser_queue_size + target_metric: scheduler.queue_depth + source_type: gauge + target_unit: tasks + statistics: + - source: avg + target: avg + - source: max + target: max + label_allowlist: [] ``` -这条自动化入口的职责边界应该明确成下面这样: - -1. 先校验远程环境 - - `telefuser` CLI 可用 - - `aiperf` CLI 可用 - - compare config 存在 - -2. 只管理“我们自己的 benchmark 资源” - - 可以对指定 GPU 执行 burner `stop` - - 可以拉起和停止本次 benchmark 启动的服务进程 - - 不应默认杀掉远程机器上其它已在运行的真实计算任务 - -3. GPU 资源策略默认非破坏性 - - `stop burner` 之后检查目标 GPU 是否真的空闲 - - 如果还有其它 compute process,占用信息直接报出来 - - 默认立即失败,或按显式参数进入等待模式 - -4. 对比执行顺序固定 - - 同一张 GPU - - 先 TeleFuser - - 再 Diffusers baseline - - 使用同一组对齐后的 compare config - -5. 结果输出标准化 - - 返回各自最新 `summary.json` 路径 - - 汇总 `request_latency` - - 汇总 `request_throughput` - - 后续如果补 `completion_latency` / `download_latency` 也沿同一路径扩展 - -这样做的目的不是把所有逻辑塞进一个巨型脚本,而是把: - -- 资源预留 -- 服务启停 -- benchmark 执行 -- 结果采集 - -变成一条可重复、可审计、默认安全的 batch compare 链路。 - -## 2. 设计边界 - -### 2.1 不把通用 serving 框架混进第一层主对标 - -`FastAPI`、`Ray Serve`、`BentoML`、`Xinference` 这类项目当然有价值,但它们解决的问题主要是: - -- HTTP 服务封装 -- 副本管理 -- 路由与部署 -- 多租户和线上治理 - -它们不是视频推理优化本身。 - -### 2.2 世界模型流式 benchmark 仍需要独立 harness - -当前开源里仍然没有一个像 AIPerf 这样成熟、通用的 **世界模型流式系统 benchmark**。 - -`AIPerf` 更适合当前的完整视频生成 HTTP 服务 benchmark,`telefuser stream-serve` 最终仍然需要单独建设一套 benchmark harness。 - -这类 benchmark 更应该关注: - -- session 建立时延 -- 首帧时延 -- steady-state FPS -- frame jitter -- control-to-frame latency -- 长会话稳定性 -- 会话并发数 -- 断线重连与恢复 - -当前仓库里已经补了一版最小可跑 harness: - -- `benchmarks/telefuser_aiperf/scripts/run_stream_bench.py` -- `benchmarks/telefuser_aiperf/scripts/run_stream_bench.sh` -- `benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json` - -这一版的边界是: - -- 先对齐 `telefuser stream-serve` 的真实 WebRTC offer / answer 协议 -- 先对齐 `LingBotWorldFastService` 的 DataChannel 控制消息格式 -- 先把核心时延指标打通,不强行塞进 AIPerf core - -当前可直接采的指标有: - -- `offer RTT` -- `connected latency` -- `first frame latency` -- `first metadata latency` -- `stream FPS` -- `control ack latency` -- `control-to-next-frame latency` - -当前已知限制也要写清楚: - -- benchmark harness 本身支持多 session 参数 -- 但 `LingBotWorldFastService` 当前代码只允许 `1 active session` -- 所以现在这条 pipeline 还不能拿来做真正的多会话并发对比 - -### 2.2.1 自动化部署与运行链路 - -这条流式 benchmark 不应该依赖手工 SSH 拼命令。建议把准备流程固定成一条自动化链路: - -1. `sync` - - 同步 `benchmarks/telefuser_aiperf` - - 同步 `examples/lingbot/stream_lingbot_world_fast.py` - - 同步文档和 burner 脚本 - -2. `install` - - 在独立 benchmark venv 安装 AIPerf - - 安装 `aiortc` / `opencv-python-headless` - - 可选安装 TeleFuser 到独立服务 venv - -3. `verify` - - 检查脚本、配置、模型入口是否存在 - - 检查 `telefuser` / `aiperf` CLI - - 检查 WebRTC 依赖是否已装 - - 检查流式模型 zoo 路径是否可见 - -4. `start` - - 由远程服务环境启动 `telefuser stream-serve` - - 由 benchmark 环境启动 `run_stream_bench.sh` - -补充说明: - -- `stream-bootstrap` 会优先探测 `TF_MODEL_ZOO_PATH`,再回退到仓库内 `model_zoo` 和几个常见远程挂载点 -- 一旦解析到可用根目录,会在远程仓库下创建非破坏性的 `model_zoo -> 实际路径` 软链,后续启动命令可以直接用默认相对路径 -- 这样能避免每次手工记住 `TF_MODEL_ZOO_PATH`,也能保证 TeleFuser 服务和 benchmark 使用同一份权重根目录 - -建议的总入口是: - -```bash -python3 scripts/remote_bench_sync.py stream-bootstrap +映射器加载规则后先校验 source type、unit 和 labels,再输出统一名称;原始 metric name 与 labels 始终保留。框架新增指标时只扩展 +映射目录,通用 collector 不感知 `telefuser`、`vllm`、`sglang` 或 `tensorrt_llm`。 + +长期统一以下指标域: + +| 指标域 | 统一语义 | 典型观测 | 正式结论的主要来源 | +| --- | --- | --- | --- | +| 请求与 session | 延迟、吞吐、成功率和活跃数 | request latency、first-frame latency、running sessions | AIPerf 客户端事件 | +| 调度与队列 | 运行中、等待中、抢占和拒绝 | running requests、queue depth、preemptions | Target Prometheus 指标 | +| 资源 | 计算、显存、内存和功耗压力 | GPU utilization、GPU memory、CPU memory、power | AIPerf GPU telemetry 或 Target 指标 | +| 缓存 | 容量、使用率、命中率、复用和迁移 | cache usage/hit、used/free blocks、transfer bytes | Target Prometheus 指标 | +| 媒体流 | 帧输出、抖动和控制闭环 | FPS、frame jitter、control latency | AIPerf transport/session 事件 | + +KV cache 作为可选 capability 接入,而不是所有框架的必选指标。Diffusion feature cache、prefix cache 和 KV cache 分属不同 +`cache_type`,分析时不得混用。 + +每个统一指标必须区分以下状态: + +- `observed`:窗口内存在通过校验的样本。 +- `unsupported`:target capability 明确声明不支持。 +- `missing`:声明支持,但本次窗口没有样本。 +- `invalid`:抓到数据,但类型、单位、标签或数值不符合映射规则。 +- `partial`:只有部分 endpoint 或部分 profiling window 有效。 + +除真实观测值本身为零外,任何状态都不能填 `0`。跨框架表格应同时输出 value 和 status。 + +#### 4.4.7 事件关联协议 + +关联键由 AIPerf 生成并写入产物 envelope,不要求 target 把 benchmark id 或 session id 注入 Prometheus labels: + +- Run 级:`benchmark_id`、`mode`、`implementation`、`model`、`phase`、profiling start/end。 +- Source 级:脱敏后的 `endpoint_id`、replica/worker labels 和采集状态。 +- Request/session 级:`request_id` 或 `session_id`、`event_id`、客户端 monotonic offset 和对应 wall-clock anchor。 +- Mapping 级:`mapping_version`、source metric identity 和 target metric identity。 + +客户端时延使用同一进程的 monotonic clock 计算;跨进程关联使用 AIPerf 记录的 wall-clock nanoseconds。Prometheus 快照通常只能证明 +某时间窗口内的系统状态,除非 target 本身提供稳定的 request/session 关联字段,否则不得把时间重叠描述为单请求因果关系。 + +#### 4.4.8 Target phase、chunk 与 runtime facts + +参考进程内 LingBot benchmark 的计量面,Stream 正式链路补齐以下原始字段,但统计和界面仍只在 AIPerf 实现: + +| 参考指标 | 在线 Stream 来源 | AIPerf 统一字段与解释 | +| --- | --- | --- | +| `pipeline_init.seconds` | `GET /v1/service/metadata` 的启动 phase snapshot | `pipeline_init_seconds`;服务加载到 ready 的 target monotonic 时长 | +| `runtime_creation.seconds` | `runtime_ready` metadata message | `runtime_creation_seconds`;prompt/image/control/cache 初始化时长 | +| phase peak allocated/reserved | phase measurement 的 per-device memory | `*_peak_allocated_bytes` / `*_peak_reserved_bytes`;保留设备明细,汇总层再统计 | +| 每 chunk `compute_seconds` | `chunk_sent.measurement` | `chunk_compute_seconds`;只含生成计算,不含 RTP pacing | +| 每 chunk `encode_seconds` | `chunk_sent.measurement` | `chunk_encode_seconds`;JPEG/序列化时间,与 compute 分开 | +| warmup 后 mean/p50/p90/min/max/std | AIPerf 对 raw chunk measurements 计算 | `warmup_chunks` 对每个 measured session 独立跳过,原始 chunk 不删除 | +| steady-state FPS | frames / target compute seconds | `chunk_compute_fps`;不得替代客户端 `stream_fps` | +| runtime shape/cache | `runtime_ready.runtime` | height、width、latent frames、frame tokens、attention window、KV cache capacity | +| Python/Torch/CUDA/GPU/commit | `/v1/service/metadata.environment` | `target_metadata.json` 与 HTML Target metadata 表 | + +通用数据模型位于 `aiperf.streaming.measurements`:`StreamPhaseMeasurement`、`StreamChunkMeasurement` 和 +`StreamDeviceMemoryMeasurement`。设备 memory fact 要求 `device` 加 allocated/reserved 至少一个字段,避免 target 只掌握一种 allocator 语义时伪造另一种。TeleFuser 只在 `benchmark_metrics=true` 时同步 CUDA、重置对应 phase 的 allocator peak 并 +上报 facts;AIPerf 负责有限值校验、warmup 排除、加权 FPS、分位数、JSON/JSONL 和 HTML。SGLang 或其他 target 只要由其 +adapter 生成同一模型,就能复用全部统计与界面,无需复制 TeleFuser 逻辑。 + +SGLang-Diffusion 已由 AIPerf adapter 直接消费原生 `chunk_stats`,映射如下;SGLang 无需依赖 AIPerf Python 包: + +| SGLang 原始字段 | AIPerf 字段 | 指标解释 | +| --- | --- | --- | +| `request_prepare_ms` | `request_prepare_seconds` | scheduler request 构造时间 | +| `scheduler_forward_ms` | `compute_seconds` | pipeline/scheduler 生成计算,不含输出转换 | +| `raw_payload_build_ms` | `encode_seconds` | WebP/JPEG/原始帧 payload 构建与编码 | +| `pace_wait_ms` | `output_pacing_seconds` | target 主动 pacing 等待 | +| `header_write_ms` | `output_header_write_seconds` | WebSocket header/framing 写出 | +| `raw_write_ms` | `output_payload_write_seconds` | 媒体 payload 写出 | +| `ws_write_ms` | `output_write_seconds` | WebSocket 总写出时间 | +| `chunk_total_ms` | `total_seconds` | target 内完整 chunk 生命周期 | +| `num_frames` / `num_batches` | `frames` / `output_batches` | chunk 帧数与 transport batch 数 | +| `raw_bytes` / `ws_payload_bytes` | `raw_output_bytes` / `wire_output_bytes` | 原始媒体字节与线传 payload 字节 | +| `content_type` | `output_content_type` | 输出媒体类型 | +| `memory_device` / `peak_memory_mb` | `memory[].device` / `memory[].peak_reserved_bytes` | worker 请求边界重置后的 rank-0 reserved peak;MiB 转 bytes | + +SGLang 的 `peak_memory_mb` 来自 `max_memory_reserved()`,因此 AIPerf 只填充 reserved 字段,`chunk_peak_allocated_bytes` 保持 +`null`。这避免将 reserved 值复制成 allocated 值。AIPerf 侧的完整设计同步维护在 +[Multimodal Generation and Observability Contracts](../../benchmarks/aiperf/docs/benchmark-modes/multimodal-generation.md)。 + +SGLang 在 frame batch 之后才发送同 chunk 的统计,因此 adapter 必须同时收到最后一批 frame 和对应 `chunk_stats` 后才能按 +`max_chunks` 结束;否则最后一个 chunk 的 target 指标会丢失。 + +当前真实覆盖边界: + +| Scene / Implementation | 客户端 session/control | chunk compute/output | pipeline/runtime phase | allocator peak | runtime/cache metadata | +| --- | --- | --- | --- | --- | --- | +| `stream_world` / TeleFuser WebRTC | 完整 | 完整 | 完整 | 完整(启用 `benchmark_metrics`) | 完整 | +| `stream_world` / SGLang-Diffusion WebSocket | 完整 | 完整,来自原生 `chunk_stats` | 当前 endpoint 未上报 | 插桩版本上报 reserved peak;allocated 未上报 | `/v1/models` 提供模型/pipeline 身份;请求参数保留在 `stream_config.json` | +| `stream_world` / transport mocks | 合成协议数据 | 仅合成链路验证数据 | 不支持 | 不支持 | 仅 mock 身份 | +| `batch_video` / TeleFuser、Diffusers | 保持原 batch 指标 | 不使用 stream chunk schema | 不使用本次新增字段 | 保持原 `peak_memory` 语义 | 保持原产物 | + +未上报的 target facts 在聚合中保持 `null`/缺失。AIPerf 不会用客户端时延替代 target compute,也不会把进程生命周期累计显存峰值 +伪装成 phase/chunk allocator peak。 + +客户端与 target 指标必须并列而非混用:`stream_fps` 表示客户端收到的媒体速率,`chunk_compute_fps` 表示 target 计算吞吐, +`session_runtime_s` 表示完整客户端生命周期。跨主机时不允许用 target wall clock 与客户端 wall clock 相减;target duration +必须由 target 自身 monotonic clock 形成闭区间。 + +#### 4.4.9 产物、版本与失败语义 + +| 产物 | 内容 | 状态 | +| --- | --- | --- | +| `server_metrics_export.jsonl` | 每次 scrape 的原始快照 | AIPerf 已支持,可选启用 | +| `server_metrics_export.parquet` | 可查询时序及 counter/histogram delta | AIPerf 已支持 | +| `server_metrics_export.json` / `.csv` | profiling window 聚合 | AIPerf 已支持 | +| `sessions.jsonl` / `events/*.jsonl` | Stream session 与事件时间线 | `aiperf profile --stream-config` 自动生成 | +| `target_metadata.json` | target 初始化 phase、软件/硬件环境及静态身份 | contract 声明 `metadata_path` 时自动生成 | +| `observability_manifest.json` | commit、schema、mapping、contract digest、窗口和 endpoint 完整性 | 标准 stream profile 自动生成 | +| `normalized_metrics.json` | 统一语义派生视图及状态 | 配置 mapping 时自动生成,不能替代原始文件 | + +Schema 采用显式版本:新增可选字段只提升 minor version;删除字段、改变单位、类型或统计含义必须提升 major version。映射规则独立版本化, +因此同一份原始数据可以用新映射重算,而无需重跑 benchmark。 + +观测采用 fail-open、结果 fail-visible:单个 scraper 失败不能终止 workload,但最终 run 必须标记 `complete`、`partial`、 +`disabled` 或 `invalid`,并保留错误摘要。正式报告只有在所需指标状态满足 contract 时才能进入横向对比。 + +#### 4.4.10 `teleai` 目标目录与迁移顺序 + +```text +ActivePeter/aiperf (teleai) +├── src/aiperf/server_metrics/ # 已有:抓取、原始模型、聚合和导出 +├── src/aiperf/streaming/ # 本地已实现:配置、runner、adapters、profile 与产物 +├── src/aiperf/observability/ # 本地已实现:关联、capability、manifest 和语义映射 +│ └── mappings/ # 已含 TeleFuser 声明式规则;其他框架后续扩展 +├── src/aiperf/history/ # GreptimeDB 索引、artifact 导入、历史 API 和静态前端 +├── web/history-ui/ # Vue 3、TypeScript 和 ECharts 源码 +├── tests/unit/server_metrics/ # 已有 server metrics 覆盖 +├── tests/unit/streaming/ # 本地已覆盖 contract、runner、adapters、profile 与 lifecycle callback +├── tests/unit/observability/ # 本地已覆盖映射、状态、manifest 和 server metrics 转换 +└── tests/unit/history/ # GreptimeDB、幂等导入、API 和无 fallback 语义 ``` -这会把流式 benchmark 的前置条件一次性准备好,减少手工出错。 - -## 3. 后续扩展方向 - -如果后续要 benchmark `telefuser stream-serve`,建议在同一份总文档下继续扩展为: - -1. `SGLang-Omni` / `vLLM-Omni` 的系统框架接入 -2. WebRTC / SSE / WebSocket transport -3. world-model session endpoint -4. interactive trace workload -5. frame-level metrics -6. control-to-frame latency metrics - -这样才能覆盖 TeleFuser 最核心的实时世界模型场景,并保持和批处理视频 benchmark 在同一入口下管理。 - -## 4. AIPerf 需要做哪些改造 - -AIPerf 现在已经足够支撑 `telefuser serve` 这类批处理视频压测,但如果要把它扩展到 `stream-serve` 和更完整的世界模型流式场景,还需要补下面这些能力。 - -### 4.1 保留的部分 +本地迁移已完成 adapter、标准 CLI、生命周期、自动产物和薄启动入口。剩余发布动作是将 AIPerf 工作树提交并推送到 +`teleai`,随后用新 commit 运行正式结果;在此之前不能把远端 `d72160e` 描述为已包含这些能力。 + +#### 4.4.11 GreptimeDB 历史服务与前端边界 + +跨任务历史曲线的唯一实现也位于 `ActivePeter/aiperf@teleai`。`aiperf history ingest` 将标准 profile export 与 stream artifact +重放到 GreptimeDB;`aiperf history serve` 提供只读 FastAPI API,并静态托管 Vue 3、TypeScript、ECharts 前端。TeleFuser 与 +SGLang target 仓不包含数据库 client、历史 API 或前端分支。 + +```mermaid +flowchart LR + TF[TeleFuser artifacts] --> Import[AIPerf history importer] + SG[SGLang artifacts] --> Import + Profile[Standard profile exports] --> Import + Import --> Runs[(GreptimeDB runs)] + Import --> Points[(GreptimeDB metric points)] + Runs --> API[Read-only FastAPI] + Points --> API + API --> UI[Shared Vue and ECharts UI] +``` -不需要重写的部分: +GreptimeDB 是强依赖:服务启动时连接或建表失败会直接失败,不使用 SQLite、内存索引或直接文件查询 fallback。JSON/JSONL +仍是可重放事实源,但 UI 和 API 的所有查询只访问 GreptimeDB。导入层保留 `run/session/control/phase/chunk/timeslice/gpu/server/normalized` +scope,warmup 与 profiling 通过 `phase` 区分,`stream_fps` 与 `chunk_compute_fps` 不合并。 -- benchmark runner 和 config 解析 -- 并发控制、warmup、请求数、速率模型 -- 结果汇总、summary JSON、原始记录导出 -- HTTP trace 采集 -- 远程执行、结果落盘、artifact 管理 +职责和失败语义如下: -这些都可以继续复用。 +| 层 | 设计要求 | +| --- | --- | +| 产物层 | TeleFuser、SGLang 和标准 profile 保留各自原始产物;历史服务不删除或改写事实源 | +| 导入层 | AIPerf 发现产物、生成包含 importer schema 的 digest,并幂等写入统一 run/point 模型 | +| 存储层 | 只有 GreptimeDB;数据库不可用时启动/导入失败,运行期查询返回 503 | +| API 层 | 仅提供有界、只读的 run、catalog、series、detail 和 facet 查询,不暴露任意 SQL 或远程导入 | +| 前端层 | 根据 API 的 metric catalog 和 facets 动态渲染,不按 `telefuser`/`sglang` 编写条件分支 | +| 恢复路径 | 恢复 GreptimeDB 后重放 JSON/JSONL;相同 digest 跳过,未完成 revision 重新导入,不切换备用数据库 | -### 4.2 需要新增的抽象 +这里的强依赖规则不改变 benchmark 执行期的观测策略:单个 Prometheus scrape 可以 fail-open 以免影响 workload,但缺失状态必须写入 +产物;run 完成后的历史查询仍然 fail-closed,只认 GreptimeDB。两种失败语义不能混为一谈。 -AIPerf **已经支持** chat 场景里的 multi-turn conversation benchmark,包括: +AIPerf 内的权威部署说明见 +[Benchmark History Dashboard with GreptimeDB](https://github.com/ActivePeter/aiperf/blob/teleai/docs/tutorials/history-dashboard.md),schema、幂等导入、查询、恢复和扩展规则见 +[Benchmark History Service Design](https://github.com/ActivePeter/aiperf/blob/teleai/docs/dev/history-service-design.md)。 -- `multi_turn` 数据集 -- `--conversation-turn-mean` / `--session-turns-mean` -- `--conversation-turn-delay-mean` / `--session-turn-delay-mean` -- `sticky-user-sessions` +## 5. 场景一:Batch Video 详细设计 -所以这里不是“补一个多轮对话能力”这么简单,而是要把现有多轮对话能力进一步抽象成适合 `stream-serve` 的会话模型: +### 5.1 固定 Workload 策略 -- `request` 之外增加更明确的 `session` 生命周期 -- 支持 `session start / session step / session end` -- 支持同一 session 内多轮控制消息 -- 支持流式响应里的分帧事件 -- 支持把一个 session 的生命周期完整打包成一条 trace +Batch 对标只替换推理框架,其余 workload 变量固定如下。 -这样它才能覆盖 `stream-serve`,而不只是覆盖 chat multi-turn。 +| 维度 | 固定值 | 设计决策说明 | +| --- | --- | --- | +| 模型 | `Wan2.1-I2V-14B-480P` | 两个框架均表达同一模型语义 | +| 任务 | `i2v` | 不混入 T2V 或 TI2V 输入差异 | +| 分辨率 | `832x480` | TeleFuser 禁用面积启发式,双方显式锁定输出形状 | +| 帧数 | `81` | 固定生成长度 | +| 推理步数 | `40` | 固定 diffusion 计算量 | +| `guidance_scale` | `5.0` | 固定生成参数 | +| `fps` | `16` | 固定输出时间轴 | +| Seed | `42` | 固定请求可复现性 | -### 4.3 需要新增的 transport adapter +双方 compare config 共用 `video_prompts.jsonl`,因此 prompt、参考图和请求顺序一致。 -现在 AIPerf 主要是 HTTP 请求模型。后续要对齐 `stream-serve`,建议加 adapter 层: +### 5.2 Target 对照关系 -- HTTP polling adapter -- WebSocket adapter -- SSE adapter -- WebRTC adapter +两个 target 均暴露 OpenAI-compatible `/v1/videos` 异步协议。 -每种 adapter 只负责 transport,不负责 benchmark 逻辑。 +| Target | 服务资产 | 对比角色 | +| --- | --- | --- | +| TeleFuser | [wan21_14b_image_to_video_480p_service.py](../../examples/wan_video/wan21_14b_image_to_video_480p_service.py) | 被测框架 | +| Diffusers | [diffusers_wan_i2v/service.py](../../benchmarks/baseline/diffusers_wan_i2v/service.py) | 同模型 baseline | -### 4.4 需要新增的 workload model +### 5.3 服务侧适配决策 -除了现在这种 batch video workload,还要再加: +| 适配点 | 涉及文件 | 设计决策 | +| --- | --- | --- | +| OpenAI video size 保真 | [adapter.py](../../telefuser/service/api/openai/adapter.py) | 保留 `openai_video_size`;将可解析的 `WxH` 写入 `width` 和 `height` | +| 媒体 alias 收敛 | [pipeline_runner.py](../../telefuser/service/core/pipeline_runner.py) | 集中处理 `first_image_path -> image/image_path` 和 `ref_video_path -> video_path` | +| 固定 workload 入口 | [wan21_14b_image_to_video_480p_service.py](../../examples/wan_video/wan21_14b_image_to_video_480p_service.py) | 由 `PIPELINE_MANIFEST` 声明 `i2v`;`target_video_length` 不改变固定帧数 | -- 长会话交互 workload -- 多轮控制 workload -- 连续帧输出 workload -- 动态 prompt / control message workload -- session 断开与恢复 workload +## 6. 场景二:Stream World 详细设计 -如果只保留单次请求模型,就测不到 world model 的真实交互成本。 +### 6.1 控制闭环与计量设计 -### 4.5 需要新增的指标 +Harness 按 `stream_lingbot_controls.json` 注入 timed events,并将不同 transport 的控制反馈映射到统一指标。 -批处理视频继续看: +当前控制 trace 固定注入方向控制事件: -- request latency -- throughput -- success rate -- peak memory -- tail latency +- `ArrowUp` press/release +- `ArrowLeft` press/release +- `ArrowRight` press/release -流式世界模型还要看: +控制映射: -- first frame latency -- steady-state FPS -- frame jitter -- control-to-frame latency -- session duration stability -- reconnect latency +| 控制语义 | TeleFuser DataChannel | SGLang-Diffusion WebSocket | +| --- | --- | --- | +| 前进 | `ArrowUp` | `camera_actions: w` | +| 后退 | `ArrowDown` | `camera_actions: s` | +| 左转 | `ArrowLeft` | `camera_actions: a` | +| 右转 | `ArrowRight` | `camera_actions: d` | -### 4.6 推荐的改造顺序 +跨 target 对比指标: -建议按这个顺序做: +| 指标 | TeleFuser 计量方式 | SGLang-Diffusion 计量方式 | 设计说明 | +| --- | --- | --- | --- | +| `control_ack_latency_ms` | 发送控制到收到 `control_state` 或 `applying_direction_control` | 发送控制到收到同 `event_id` 的 `chunk_stats` | SGLang 无独立 ack,使用服务端采样事件近似 | +| `control_to_next_frame_latency_ms` | 发送控制到收到下一视频帧 | 发送控制到收到同 `event_id` 的 `frame_batch` | 衡量控制到可见反馈的时延 | +| `first_frame_latency_ms` | session 建立到首帧 media track | WebSocket init 到首个 frame batch | 衡量 session 可用时间 | +| `stream_fps` | media track 接收速率 | frame batch 解码后的帧接收速率 | 仅在相同 workload 和 session 语义下比较 | -1. 保持现有 batch video benchmark 不动 -2. 把 `session` 抽象加到 AIPerf -3. 加 WebSocket / SSE adapter -4. 再补 WebRTC adapter -5. 最后接 `stream-serve` workload 和 frame-level 指标 +Summary 另保留以下 session 诊断字段: -这样可以先不破坏现有 `telefuser serve` 压测,同时逐步覆盖实时世界模型场景。 +- `offer_rtt_ms` +- `connected_latency_ms` +- `first_metadata_latency_ms` +- `session_runtime_s` +- `frames_received` +- `success_rate` -## 5. 参考链接 +### 6.2 公平对比边界 -- [SGLang-Omni](https://sgl-project.github.io/sglang-omni/) -- [vLLM-Omni](https://docs.vllm.ai/projects/vllm-omni/en/latest/) -- [TeleFuser 480P 服务示例](../../examples/wan_video/wan21_14b_image_to_video_480p_service.py) -- [Diffusers baseline 服务](../../benchmarks/baseline/diffusers_wan_i2v/service.py) +| 维度 | TeleFuser Target | SGLang-Diffusion Baseline | 设计决策说明 | +| --- | --- | --- | --- | +| 正式对比配置 | [examples/lingbot/stream_lingbot_world_fast.py](../../examples/lingbot/stream_lingbot_world_fast.py) 的默认 1-GPU 配置 | `performance_mode=speed` 的 1-GPU GPU-resident 配置 | VAE、文本编码器和 DiT 都应驻留 GPU;`auto` 可能隐式开启 VAE layerwise offload,不属于本行 | +| 降级 / smoke 配置 | 不作为正式性能表输入 | CPU offload、layerwise offload、native norm fallback | 只用于证明 WebSocket、MessagePack、控制 trace 映射和 summary 采集链路可用;FPS 和时延不能与 TeleFuser 正式配置比较 | +| 纯流式 mock | WebRTC media track + DataChannel,无模型 | WebSocket + MessagePack frame batch,无模型 | 隔离 transport、frame pacing 和控制消息路径开销,不能解释真实模型推理性能 | +| Session 并发 | 当前按单 active session 解释 | 当前按单 active session 解释 | `session_count` 和 `stagger_s` 是 harness 能力,正式结论受 target active session 限制约束 | + +若 1-GPU GPU-resident 配置无法完成 session,结果只表示该配置未跑通,不能用降级或 mock 数据替代正式性能结果。 + +2026-07-13 的 1xH100 实测确认了配置边界:`speed`、无 CPU/layerwise offload 时,sink 9 / window 18 在约 79.16 GiB +进程显存处 OOM;独立标注的 sink 6 / window 9 tuned 配置完成 1/1 profile,稳态 target compute FPS 为 `5.1906`,客户端 +stream FPS 为 `5.6788`,steady reserved allocator peak 最大为 `76,919,341,056` bytes。远端 FlashInfer CUDA compiler/header +不兼容,运行时使用 PyTorch RoPE fallback;该限定必须随结果保留。9/18 OOM 与 6/9 成功不是同 cache 配置,不能进入同配置 +性能差值计算。 + +### 6.3 Stream Adapter 资产边界 + +Stream Adapter 位于 harness 与 target service 之间,消费统一的 `StreamSessionPlan` 和 control trace。各实现将建连、控制和媒体事件转换为对应协议消息,再输出统一的 `SessionResult`、事件时间线和 summary。 + +Adapter 只隔离 transport 与编码差异,不负责模型加载、推理调度、workload 定义或指标语义。 + +| 资产 | 设计角色 | 关键职责 | +| --- | --- | --- | +| [AIPerf TeleFuser WebRTC adapter](../../benchmarks/aiperf/src/aiperf/streaming/adapters/telefuser_webrtc.py) | WebRTC adapter | 读取 `StreamSessionPlan` 和 control trace,输出 `SessionResult` 与事件 | +| [AIPerf SGLang WebSocket adapter](../../benchmarks/aiperf/src/aiperf/streaming/adapters/sglang_websocket.py) | WebSocket adapter | 发送 MessagePack `init`,映射 `camera_actions`,消费 `frame_batch` 和 `chunk_stats` | +| [TeleFuser thin launcher](../../benchmarks/telefuser_aiperf/scripts/run_stream_bench.py) | 兼容入口 | 仅转交 config、URL 与产物目录给 `aiperf profile` | +| [WebRTC mock service](../../benchmarks/telefuser_aiperf/scripts/run_mock_webrtc_service.py) | TeleFuser transport mock | 暴露 media track 和 DataChannel,不加载模型 | +| [WebSocket mock service](../../benchmarks/baseline/sglang_lingbot_stream/scripts/run_mock_stream_service.py) | SGLang transport mock | 暴露 MessagePack `frame_batch` 和 `chunk_stats`,不加载模型 | + +### 6.4 WebRTC ICE 候选者控制 + +多网卡环境中,aiortc 可能枚举过多 host candidate,导致建连时延和可达性受 workload 之外的因素影响。设计约束如下: + +- 服务端保留 [telefuser/webrtc_ice.py](../../telefuser/webrtc_ice.py),客户端由 AIPerf + [webrtc_ice.py](../../benchmarks/aiperf/src/aiperf/streaming/adapters/webrtc_ice.py)通过 IP allowlist 限制 candidate gathering。 +- 客户端默认使用 `ice_host_ips: ["auto"]`,按到 `server_url` 的系统路由选择源地址,避免多网卡主机枚举到无关 NIC。 +- 容器、TURN 或特殊路由场景可在 stream config 或 `--stream-ice-host-ip` 中显式提供地址;配置 `[]` 才恢复 aioice 的全部地址枚举。 +- 不使用全局 `sitecustomize.py` hook,避免影响非 benchmark 进程。 + +Stream session 同时保留端到端时钟和 active-window 时钟。offer、connected、首帧、首条 metadata 与总运行时间从端到端起点计算; +`session_duration_s`、control trace delay 和 control send offset 从 transport 可用时计算。WebSocket 在 init 发送完成后进入 active window; +双向 WebRTC 在 connected 且 DataChannel open 后进入 active window。这样 ICE 协商不会吞掉有效媒体窗口,也不会让控制事件在建连后集中补发。 + +## 7. 资产清单与产物矩阵 + +### 7.1 配置资产矩阵 + +Batch 配置: + +| 配置文件 | Target | 设计意图 | +| --- | --- | --- | +| [video_generation_quick.yaml](../../benchmarks/telefuser_aiperf/configs/video_generation_quick.yaml) | TeleFuser | 最小请求闭环 | +| [video_generation_e2e.yaml](../../benchmarks/telefuser_aiperf/configs/video_generation_e2e.yaml) | TeleFuser | 含 warmup、trace 和 server metrics 的端到端压测 | +| [video_generation_rate.yaml](../../benchmarks/telefuser_aiperf/configs/video_generation_rate.yaml) | TeleFuser | 使用 Poisson 到达率观察排队与吞吐 | +| [video_generation_wan21_i2v_480p_compare.yaml](../../benchmarks/telefuser_aiperf/configs/video_generation_wan21_i2v_480p_compare.yaml) | TeleFuser | 使用第 5.1 节固定 workload 的正式对比 | +| [Diffusers compare](../../benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_compare.yaml) | Diffusers | 对齐 TeleFuser compare config 的正式对比 | +| [Diffusers E2E](../../benchmarks/baseline/diffusers_wan_i2v/configs/video_generation_e2e.yaml) | Diffusers | Baseline 端到端压测 | + +Stream 配置: + +| 配置文件 | Target | 设计意图 | +| --- | --- | --- | +| [TeleFuser quick](../../benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_quick.json) | TeleFuser | WebRTC、DataChannel 和 control trace 连通性 | +| [TeleFuser compare](../../benchmarks/telefuser_aiperf/configs/stream_lingbot_world_fast_compare.json) | TeleFuser | 固定 session workload 的正式对比 | +| [TeleFuser mock quick](../../benchmarks/telefuser_aiperf/configs/stream_transport_mock_quick.json) | TeleFuser mock | WebRTC transport smoke | +| [TeleFuser mock compare](../../benchmarks/telefuser_aiperf/configs/stream_transport_mock_compare.json) | TeleFuser mock | 固定 pacing 的 transport 隔离对比 | +| [SGLang quick](../../benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_quick.json) | SGLang-Diffusion | WebSocket、MessagePack 和 control mapping 连通性 | +| [SGLang compare](../../benchmarks/baseline/sglang_lingbot_stream/configs/stream_lingbot_world_fast_compare.json) | SGLang-Diffusion | 固定 session workload 的正式 baseline | +| [SGLang mock quick](../../benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_quick.json) | SGLang mock | WebSocket transport smoke | +| [SGLang mock compare](../../benchmarks/baseline/sglang_lingbot_stream/configs/stream_transport_mock_compare.json) | SGLang mock | 固定 frame batch 的 transport 隔离对比 | + +### 7.2 数据资产矩阵 + +| 数据资产 | 消费方 | 设计角色 | +| --- | --- | --- | +| [video_prompts.jsonl](../../benchmarks/telefuser_aiperf/data/video_prompts.jsonl) | TeleFuser、Diffusers | 固定 I2V prompt、参考图和请求顺序 | +| [stream_lingbot_controls.json](../../benchmarks/telefuser_aiperf/data/stream_lingbot_controls.json) | TeleFuser、SGLang-Diffusion、mock | 固定 timed controls,由 adapter 映射到对应协议 | + +### 7.3 结果产物矩阵 + +| Benchmark mode | 产物 | 设计用途 | +| --- | --- | --- | +| `batch_video` | `summary.json` | 聚合 request latency、throughput、success rate 等 batch 指标 | +| `batch_video` | `records.jsonl` | 保存单请求生命周期和状态记录 | +| `batch_video` | HTTP trace artifacts | 追踪 submit、poll、download 的 wire-level timing | +| `stream_world` | `summary.json` | 聚合 session 级 FPS、first-frame/control latency,以及 target phase/chunk 稳态统计 | +| `stream_world` | `sessions.jsonl` | 保存每个 session 的结果、错误、raw phase/chunk measurement 和 runtime metadata | +| `stream_world` | `events/*.jsonl` | 保存完整 session 事件流,用于复查 control、frame、metadata 的时间线 | +| `stream_world` | `stream_report.html` | AIPerf 生成的自包含界面,统一展示 session、phase/chunk、显存、runtime metadata、客户端指标和 normalized server metrics;不含 TeleFuser 专用渲染逻辑 | +| `stream_world` | `target_metadata.json` | 保存 pipeline init、Python/Torch/CUDA/GPU/commit 等 target snapshot | +| 启用 server metrics | `server_metrics_export.json` | 保存 profiling window 的聚合统计 | +| 启用 server metrics | `server_metrics_export.csv` | 保存便于表格处理的聚合统计 | +| 启用 server metrics | `server_metrics_export.parquet` | 保存带 counter/histogram delta 的时序数据 | +| 启用 server metrics | `server_metrics_export.jsonl` | 可选保存每次 scrape 的原始时序快照 | + +## 8. 验证覆盖 + +验证覆盖协议边界、workload 保真和结果聚合三类风险。 + +| 测试文件 | 验证点 | +| --- | --- | +| [test_adapter.py](../../tests/unit/openai/test_adapter.py) | OpenAI video `size` 保留精确 `WxH` | +| [test_pipeline_runner.py](../../tests/unit/service/test_pipeline_runner.py) | Pipeline runner 收敛媒体 alias | +| [test_video_generation_endpoint.py](../../benchmarks/aiperf/tests/unit/endpoints/test_video_generation_endpoint.py) | `extra` 不覆盖 endpoint 管理的媒体字段 | +| [test_streaming_contracts.py](../../benchmarks/aiperf/tests/unit/streaming/test_streaming_contracts.py) | Contract 校验、control trace、session result 和 summary 聚合 | +| [test_streaming_adapters.py](../../benchmarks/aiperf/tests/unit/streaming/test_streaming_adapters.py) | SGLang `chunk_stats` 的 timing、bytes 与 reserved peak 映射 | diff --git a/docs/zh/benchmark_current_comparison.md b/docs/zh/benchmark_current_comparison.md new file mode 100644 index 0000000..5dd13c4 --- /dev/null +++ b/docs/zh/benchmark_current_comparison.md @@ -0,0 +1,184 @@ +# 当前 Benchmark 对比快照 + +本文档汇总截至 `2026-07-13` 已经跑通并记录到文档中的 benchmark 结果。更完整的运行命令、环境和单次记录见 [TeleFuser 与 AIPerf Benchmark](benchmark_aiperf.md),benchmark 设计和公平对比边界见 [TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md)。 + +## 总览 + +| 模式 | Workload | TeleFuser 当前结果 | Baseline 当前结果 | 当前结论 | +| --- | --- | --- | --- | --- | +| Batch 视频生成 | Wan2.1-I2V-14B-480P,`832x480`,`81` 帧,`40` steps | 已完成,平均请求时延 `163.08s`,吞吐 `0.00614 requests/s` | Diffusers target 已落地,当前快照未记录实测结果 | 只能展示 TeleFuser 单边结果,暂不下性能对比结论 | +| 世界模型流式会话 | LingBot-World-Fast,`832x480`,目标 `16 FPS`,同一份控制 trace | 已完成,首帧 `14.60s`,稳态 `16.05 FPS`,控制到下一帧平均 `37.60ms` | SGLang-Diffusion 6/9 tuned 已完成,首帧 `3.08s`,stream `5.68 FPS`,控制到下一帧平均 `3329.12ms` | SGLang 正式 9/18 cache 在 1xH100 OOM;6/9 可运行结果因 cache 几何不同只作受限参考 | +| 纯流式 mock 会话 | 不加载模型,固定 FPS 合成帧,同一份控制 trace | 已完成,首帧 `10.58s`,稳态 `16.00 FPS`,控制到下一帧平均 `25.63ms` | 已完成,首帧 `64.73ms`,稳态 `16.05 FPS`,控制到下一帧平均 `21.93ms` | 稳态和控制闭环接近;差异主要在 WebRTC SDP/ICE 建连和首帧 | +| 纯流式 mock 极限 FPS sweep | 不加载模型,逐步提高目标 FPS,压测 transport/harness 上限 | 最高观测 `394.97 FPS`;`30/60/120/240 FPS` 基本贴住目标,`480/960 FPS` 开始饱和 | 最高观测 `394.82 FPS`;`1920 FPS` 目标下降到 `350.64 FPS` | 两边纯传输上限都在约 `395 FPS`;这不是模型推理 FPS | + +注意:SGLang-Diffusion 6/9 tuned baseline 使用真正的 1-GPU GPU-resident `performance_mode=speed`,没有 CPU 或 layerwise offload;测试机仍需要 RoPE fallback,而且 causal KV cache 从 9/18 缩到 6/9。它证明了单卡可运行性并给出真实指标,但由于 cache 几何不同,不能直接计算 TeleFuser 与 SGLang 的严格公平性能差值。 + +## Batch 视频生成 + +当前已记录的是 TeleFuser 侧的 Wan2.1 I2V 480P benchmark。 + +| 项目 | 数值 | +| --- | --- | +| 运行环境 | 远端 H100 测试环境,具体机器信息已脱敏 | +| GPU | 4 卡可见配置 | +| 模型 | Wan2.1-I2V-14B-480P | +| 服务入口 | TeleFuser Wan2.1 I2V 480P 固定 workload 服务 | +| Benchmark 配置 | TeleFuser Wan2.1 I2V 480P compare 配置 | +| Artifact | 已归档,具体路径不在公开文档中暴露 | +| Warmup 请求数 | `1` | +| Profiling 请求数 | `2` | +| Profiling errors | `0` | +| Benchmark duration | `325.49s` | +| Request latency avg / p50 | `163084.80ms` / `163084.80ms` | +| Request latency p90 / p99 | `163373.28ms` / `163438.19ms` | +| Request latency min / max | `162724.20ms` / `163445.40ms` | +| Request throughput | `0.00614 requests/s` | + +Diffusers batch baseline 的服务、contract 和 compare config 已经在仓库中,但当前结果快照没有记录 Diffusers 侧实测 summary。因此 batch 视频生成现在只能说明 TeleFuser 固定 workload 已跑通,不能给出 TeleFuser vs Diffusers 的速度结论。 + +## 世界模型流式会话 + +这组结果是单 session stream benchmark。两边使用同一类 LingBot world-model 实时控制语义、同 prompt/first frame/输出尺寸/FPS 目标/控制 trace;TeleFuser 使用 WebRTC + DataChannel,SGLang-Diffusion baseline 使用 WebSocket + MessagePack。 + +### 指标对比 + +| 指标 | TeleFuser stream | SGLang-Diffusion stream | 对比 | +| --- | ---: | ---: | --- | +| Profile sessions | `1 / 1` | `1 / 1` | 相同 | +| Failed sessions | `0` | `0` | 相同 | +| Configured session duration | `90.0s` | `90.0s` | 相同 | +| Actual session runtime | `40.15s` | `19.277s` | 两边都提前结束 | +| Frames received | `406` | `93` | TeleFuser 多 `4.37x` | +| Connection / offer latency | WebRTC connected `14432.81ms` | WebSocket connected `12.40ms` | SGLang 建连更轻 | +| First frame latency | `14598.90ms` | `3076.18ms` | SGLang tuned 首帧更快 | +| Stream FPS | `16.05` | `5.68` | 配置不同,不计算公平倍率 | +| Target compute FPS | 当前旧产物未上报 | `5.1906` | 只陈述 SGLang target 计算吞吐 | +| Control ack latency avg | `8.88ms` | `3333.92ms` | 语义不同,SGLang 为 chunk 采样近似 | +| Control-to-next-frame avg | `37.60ms` | `3329.12ms` | SGLang tuned 仍为秒级 | +| Chunk reserved peak max | 当前旧产物未上报 | `76,919,341,056 bytes` | SGLang reset-scoped `max_memory_reserved()` | + +### 结果归档 + +| Target | 归档状态 | +| --- | --- | +| TeleFuser | 已归档,具体路径不在公开文档中暴露 | +| SGLang-Diffusion | 已归档,具体路径不在公开文档中暴露 | + +### 当前解读 + +TeleFuser 在这组数据里不是首帧更快,而是稳态流式出帧和控制闭环更快。TeleFuser 首帧包含 WebRTC offer/ICE 和 stream runtime 初始化,首帧为 `14.60s`;SGLang WebSocket 建连更轻,6/9 tuned 首帧为 `3.08s`。 + +稳态阶段的瓶颈差异更明显:TeleFuser 能按目标播放节奏输出约 `16 FPS`,SGLang-Diffusion 6/9 tuned 每个 chunk 的模型 forward 成为主耗时,target compute 为 `5.1906 FPS`、客户端接收为 `5.68 FPS`。SGLang 日志中的 WebSocket 写入约毫秒级,主要瓶颈不在传输链路,而在 scheduler/model forward。由于两端 cache 几何未完全对齐,这里不把差值写成公平加速比。 + +控制链路也不同。TeleFuser 的控制通过 DataChannel 异步进入 session,ack 和下一帧反馈都在几十毫秒级;SGLang-Diffusion baseline 没有单独 control ack 消息,benchmark 用 chunk/frame batch 事件推导控制被采样和反映到下一帧的时间,因此当前观测到秒级控制闭环。 + +## 纯流式 mock 会话 + +这组 target 不加载 LingBot、Wan 或 SGLang diffusion runtime,只用合成帧和固定 payload 压测 streaming 层。下面是 `2026-07-10` 本地时区实测结果。 + +运行口径: + +- `session_count=4` +- `warmup_sessions=1` +- `session_duration_s=20` +- `fps=16` +- 同一份控制 trace +- TeleFuser mock 走 WebRTC media track + DataChannel +- SGLang mock 走 WebSocket + MessagePack `frame_batch` / `chunk_stats` + +| 指标 | TeleFuser WebRTC mock | SGLang WebSocket mock | 说明 | +| --- | ---: | ---: | --- | +| Profile sessions | `4 / 4` | `4 / 4` | 两边都成功 | +| Offer / connect latency avg | `5009.97ms` / `10518.66ms` | `1.99ms` / `2.00ms` | WebRTC 当前 harness 有 SDP/ICE 等待 | +| First frame latency avg | `10583.69ms` | `64.73ms` | 差异主要来自建连链路,不是模型 | +| Stream FPS avg | `16.00` | `16.05` | 稳态都达到目标 FPS | +| Frames received avg | `319` | `320` | 20s 窗口内基本一致 | +| Control ack latency avg | `0.70ms` | `0.35ms` | 都是毫秒级 | +| Control-to-next-frame avg | `25.63ms` | `21.93ms` | 都是帧间隔量级 | + +这组结果说明:纯 streaming 层在稳态出帧和控制闭环上不是当前世界模型真实 benchmark 的主要瓶颈。真实 SGLang 6/9 tuned 世界模型约 `5.68 FPS`,而 mock 可以稳定到 `16 FPS`,因此真实慢点主要还在模型 forward / scheduler,而不是 WebSocket 写入本身。TeleFuser mock 的首帧和 connected latency 高,主要暴露的是当前 WebRTC offer/ICE 建连路径成本;这会影响首帧,但不影响 steady-state FPS。 + +### 极限 FPS sweep + +这组 sweep 继续使用纯流式 mock,不加载任何真实生成模型。测试口径是让服务端 mock 尽可能快地按目标 FPS 推送合成帧或固定 payload,再由 benchmark harness 统计实际接收 FPS。 + +运行口径: + +- 单 session sweep,每个 target 单独运行 +- `session_duration_s=6` +- TeleFuser mock:`320x180` 合成视频帧,经 WebRTC media track 发送 +- SGLang mock:每个 `frame_batch` 携带固定大小二进制 payload,经 WebSocket + MessagePack 发送 +- 两边都不加载 LingBot、Wan 或 SGLang diffusion runtime + +| Target FPS | TeleFuser WebRTC mock 实测 FPS | SGLang WebSocket mock 实测 FPS | +| ---: | ---: | ---: | +| `30` | `30.02` | `28.82` | +| `60` | `60.18` | `54.04` | +| `120` | `120.05` | `104.58` | +| `240` | `241.25` | `179.87` | +| `480` | `355.92` | `260.40` | +| `960` | `394.97` | `394.82` | +| `1920` | 未运行 | `350.64` | + +结论: + +- TeleFuser WebRTC mock 在 `240 FPS` 之前基本贴住目标;`480/960 FPS` 后本次测试环境下开始饱和,最高观测约 `395 FPS`。 +- SGLang WebSocket mock 在 `960 FPS` 目标下达到本次最高观测约 `395 FPS`,`1920 FPS` 目标反而下降到约 `351 FPS`。 +- TeleFuser mock 的 first-frame latency 在 sweep 中仍然约 `10.6s-11.0s`,主要来自 WebRTC SDP/ICE 建连和当前 harness 等待路径;这不代表 steady-state FPS 低。 +- SGLang mock 的 first-frame latency 为几十到数百毫秒,但它统计的是 WebSocket `frame_batch` 消息,不是 RTP video frame。两边 transport 语义不同,因此这组结果用于判断流式层上限,不用于替代真实模型 benchmark。 + +## 延迟与控制实时性分析口径 + +流式 benchmark 的延迟不能只看一个总耗时,需要拆成“启动链路”和“稳态控制链路”两部分。首帧快不一定代表控制实时,控制 ack 快也不一定代表画面已经响应。 + +### 启动链路 + +| 指标 | 含义 | 主要判断 | +| --- | --- | --- | +| `offer_rtt_ms` | 客户端发起 offer/init 到服务端返回 answer/ready 的耗时 | HTTP/SDP 或 WebSocket init 开销 | +| `connected_latency_ms` | transport 建连完成耗时 | WebRTC ICE、DataChannel 或 WebSocket 建连成本 | +| `first_metadata_latency_ms` | 首个服务端 metadata、chunk stats 或状态事件到达 | 服务端是否开始产出 | +| `first_frame_latency_ms` | 客户端收到第一帧视频或第一批 frame payload | 用户可见首帧时间 | + +当前数据里,SGLang WebSocket 建连更轻,所以 6/9 tuned 真实世界模型首帧为 `3.08s`,低于 TeleFuser 的 `14.60s`。但纯流式 mock 显示 TeleFuser 的首帧主要受 WebRTC SDP/ICE 建连和当前 harness 等待路径影响:mock first-frame 约 `10.58s`,而不是模型 forward 耗时。 + +### 稳态控制链路 + +| 指标 | 含义 | 主要判断 | +| --- | --- | --- | +| `stream_fps` | session 稳态实际接收帧率 | 模型和服务是否能跟上目标 FPS | +| `control_ack_latency_ms` | 控制消息发出到服务端确认收到或采样的耗时 | 网络、transport、server event loop 是否及时 | +| `control_to_next_frame_latency_ms` | 控制消息发出到下一帧到达客户端的耗时 | 控制能多快进入视频时间线 | +| p90 / p99 / max | 尾延迟 | 是否存在偶发长时间失控或卡顿 | + +在 `16 FPS` 目标下,一帧间隔约 `62.5ms`。如果 `control_to_next_frame_latency_ms` 能稳定落在一个帧间隔量级内,用户通常会感到控制是实时的;如果达到秒级,即使平均 FPS 尚可,交互也会明显滞后。 + +当前真实世界模型结果: + +- TeleFuser:`control_ack_latency_ms` 平均 `8.88ms`,`control_to_next_frame_latency_ms` 平均 `37.60ms`,低于一个 `16 FPS` 帧间隔,说明控制可以在下一帧量级进入流式时间线。 +- SGLang-Diffusion:没有独立 control ack 消息,benchmark 用 `chunk_stats.event_id` 推导控制被采样时间;当前 `control_to_next_frame_latency_ms` 平均 `3329.12ms`,说明控制事件被 scheduler/model chunk 采样得很晚,用户会感到秒级滞后。 +- 纯流式 mock:TeleFuser `25.63ms`、SGLang `21.93ms` 的 control-to-next-frame 都是帧间隔量级,说明当前真实 SGLang 世界模型的秒级控制滞后主要不在 WebSocket 写入本身,而在模型 forward / scheduler / control sampling 路径。 + +更严格的“控制是否真的影响画面”需要服务端把 `event_id` 写入 frame/chunk metadata,客户端统计该 `event_id` 首次出现在第几帧。当前 benchmark 已经能衡量控制消息到达下一帧的时间,但如果要证明按键后画面内容已经转向,还需要把视觉状态变化或事件应用帧号记录进 artifact。 + +## SGLang 当前配置限制 + +当前可运行的 SGLang-Diffusion 6/9 tuned baseline 使用以下边界: + +- `SGLANG_LINGBOT_DISABLE_FLASHINFER_ROPE=1`,绕过 flashinfer RoPE JIT 问题,使用 fallback。 +- `performance_mode=speed`,DiT、text encoder、VAE 全部 GPU-resident。 +- `realtime_causal_sink_size=6`。 +- `realtime_causal_kv_cache_num_frames=9`。 +- `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`。 +- `VSA_sparsity=0.0`。 + +正式 compare 的 sink 9 / window 18 在同一 `speed` 服务、单张 H100 上 warmup 与 profile 都 OOM,进程显存约 `79.16 GiB`。6/9 tuned 跑通版本由 AIPerf 原生记录 steady reserved peak 最大 `76,919,341,056 bytes`(`73,356 MiB`)。因此 9/18 的结论是容量失败,6/9 的结论是独立 tuned 可运行结果;两者不能混为同配置性能样本。 + +## 下一步对比口径 + +后续补齐结果时建议按下面顺序更新本文档: + +1. 补 Diffusers batch baseline 的同 workload 实测 summary,给出 TeleFuser vs Diffusers 的请求时延和吞吐对比。 +2. 修复 SGLang FlashInfer RoPE / CUDA JIT 兼容问题后,在不使用 fallback 的环境中复核 1-GPU GPU-resident stream baseline。 +3. 对 stream 世界模型分别记录首帧、稳态 FPS、控制 ack、控制到下一帧,不把首帧和稳态吞吐混成一个结论。 +4. 若测试多 session 并发,需要单独标注 session_count、stagger_s 和服务是否允许多 active session。 diff --git a/docs/zh/index.md b/docs/zh/index.md index 3d01156..2e657fe 100644 --- a/docs/zh/index.md +++ b/docs/zh/index.md @@ -56,8 +56,9 @@ telefuser stream-serve examples/stream_server/stream_lingbot_world_fast.py -p 80 - **[服务指南](service.md)** — 批量服务、任务 API 和 SDK - **[流式服务](stream_server.md)** — WebRTC 流式传输和双向控制 -- **[TeleFuser 与 AIPerf Benchmark](benchmark_aiperf.md)** — 批处理视频压测与运行入口 -- **[TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md)** — 对标分层、stream-serve 规划和固定 workload 设计 +- **[TeleFuser 与 AIPerf Benchmark](benchmark_aiperf.md)** — Batch、stream、baseline 与 GreptimeDB 历史服务入口 +- **[当前 Benchmark 对比快照](benchmark_current_comparison.md)** — 当前实测结果、baseline 状态和对比结论 +- **[TeleFuser 与 AIPerf Benchmark 设计](benchmark_aiperf_design.md)** — 对标分层、观测协议、GreptimeDB 历史层和固定 workload 设计 - **[配置](configuration.md)** — 运行时和模型配置 - **[并行推理](parallel.md)** — 分布式处理策略 - **[新增模型](adding_new_model.md)** — 集成新模型 diff --git a/scripts/setup_aiperf_repo.sh b/scripts/setup_aiperf_repo.sh new file mode 100755 index 0000000..ab58cfe --- /dev/null +++ b/scripts/setup_aiperf_repo.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +AIPERF_REPO_URL="${AIPERF_REPO_URL:-https://github.com/ActivePeter/aiperf.git}" +AIPERF_REPO_DIR="${AIPERF_REPO_DIR:-${ROOT_DIR}/benchmarks/aiperf}" +AIPERF_BRANCH="${AIPERF_BRANCH:-teleai}" +AIPERF_REF="${AIPERF_REF:-}" +PYTHON_BIN="${PYTHON_BIN:-python3}" +INSTALL_EDITABLE=1 +UPDATE_REPO=0 + +usage() { + cat <<'EOF' +Usage: scripts/setup_aiperf_repo.sh [options] + +Clone or update the external AIPerf dependency repository and optionally install +it in editable mode. + +Options: + --repo-url URL AIPerf git URL. Default: https://github.com/ActivePeter/aiperf.git + --dir PATH Checkout directory. Default: benchmarks/aiperf + --branch NAME Branch used for a new clone. Default: teleai + --ref REF Branch, tag, or commit to checkout after clone/fetch. + --update Fetch and fast-forward an existing checkout. Refuses dirty trees. + --no-install Skip "python -m pip install -e ". + -h, --help Show this help. + +Environment overrides: + AIPERF_REPO_URL, AIPERF_REPO_DIR, AIPERF_BRANCH, AIPERF_REF, PYTHON_BIN +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo-url) + AIPERF_REPO_URL="$2" + shift 2 + ;; + --dir) + AIPERF_REPO_DIR="$2" + shift 2 + ;; + --branch) + AIPERF_BRANCH="$2" + shift 2 + ;; + --ref) + AIPERF_REF="$2" + shift 2 + ;; + --update) + UPDATE_REPO=1 + shift + ;; + --no-install) + INSTALL_EDITABLE=0 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +ensure_clean_checkout() { + local repo_dir="$1" + if [[ -n "$(git -C "${repo_dir}" status --porcelain)" ]]; then + echo "AIPerf checkout has local changes: ${repo_dir}" >&2 + echo "Refusing to update or checkout a ref. Commit/stash them, or run without --update/--ref." >&2 + exit 1 + fi +} + +clone_repo() { + local repo_url="$1" + local repo_dir="$2" + local branch="$3" + + mkdir -p "$(dirname "${repo_dir}")" + if [[ -e "${repo_dir}" ]]; then + if [[ -d "${repo_dir}" && -z "$(ls -A "${repo_dir}")" ]]; then + rmdir "${repo_dir}" + else + echo "Path exists and is not an AIPerf git checkout: ${repo_dir}" >&2 + exit 1 + fi + fi + + echo "Cloning AIPerf from ${repo_url} (${branch}) -> ${repo_dir}" + git clone --branch "${branch}" "${repo_url}" "${repo_dir}" +} + +checkout_ref_if_needed() { + local repo_dir="$1" + local ref="$2" + + if [[ -z "${ref}" ]]; then + return + fi + + ensure_clean_checkout "${repo_dir}" + echo "Fetching and checking out AIPerf ref: ${ref}" + git -C "${repo_dir}" fetch origin "${ref}" || git -C "${repo_dir}" fetch origin + git -C "${repo_dir}" checkout "${ref}" +} + +update_repo_if_requested() { + local repo_dir="$1" + + if [[ "${UPDATE_REPO}" -ne 1 ]]; then + return + fi + + ensure_clean_checkout "${repo_dir}" + echo "Fetching AIPerf updates" + git -C "${repo_dir}" fetch origin + + local branch + branch="$(git -C "${repo_dir}" symbolic-ref --short HEAD 2>/dev/null || true)" + if [[ -n "${branch}" ]]; then + echo "Fast-forwarding AIPerf branch: ${branch}" + git -C "${repo_dir}" pull --ff-only origin "${branch}" + else + echo "AIPerf checkout is detached; fetched origin but did not pull." + fi +} + +if [[ -d "${AIPERF_REPO_DIR}/.git" ]]; then + remote_url="$(git -C "${AIPERF_REPO_DIR}" config --get remote.origin.url || true)" + echo "AIPerf checkout already exists: ${AIPERF_REPO_DIR}" + if [[ -n "${remote_url}" ]]; then + echo "origin: ${remote_url}" + fi +else + clone_repo "${AIPERF_REPO_URL}" "${AIPERF_REPO_DIR}" "${AIPERF_BRANCH}" +fi + +update_repo_if_requested "${AIPERF_REPO_DIR}" +checkout_ref_if_needed "${AIPERF_REPO_DIR}" "${AIPERF_REF}" + +if [[ "${INSTALL_EDITABLE}" -eq 1 ]]; then + echo "Installing AIPerf in editable mode" + "${PYTHON_BIN}" -m pip install -e "${AIPERF_REPO_DIR}" +fi + +echo "AIPerf dependency is ready at: ${AIPERF_REPO_DIR}" diff --git a/telefuser/metrics/__init__.py b/telefuser/metrics/__init__.py index 77f4232..5f8940f 100644 --- a/telefuser/metrics/__init__.py +++ b/telefuser/metrics/__init__.py @@ -50,6 +50,14 @@ get_metrics_registry, reset_global_registry, ) +from .runtime import ( + RuntimeMeasurement, + canonical_cuda_devices, + collect_runtime_environment, + finish_runtime_measurement, + start_runtime_measurement, + visible_cuda_devices, +) from .service_metrics import ( ServiceMetrics, get_service_metrics, @@ -90,6 +98,13 @@ "ServiceMetrics", "get_service_metrics", "reset_service_metrics", + # Runtime benchmark facts + "RuntimeMeasurement", + "canonical_cuda_devices", + "visible_cuda_devices", + "start_runtime_measurement", + "finish_runtime_measurement", + "collect_runtime_environment", # Configuration "MetricsConfig", "default_metrics_config", diff --git a/telefuser/metrics/collector.py b/telefuser/metrics/collector.py index a14d1b1..29a148b 100644 --- a/telefuser/metrics/collector.py +++ b/telefuser/metrics/collector.py @@ -282,15 +282,14 @@ def to_prometheus(self) -> list[str]: label_str = self._labels_to_prometheus() - # Cumulative bucket counts - cumulative = 0 + # Bucket counts are already cumulative because observe() increments every + # upper bound containing the value. for bucket in self._buckets: - cumulative += bucket.count - le = "inf" if bucket.upper_bound == float("inf") else str(bucket.upper_bound) + le = "+Inf" if bucket.upper_bound == float("inf") else str(bucket.upper_bound) bucket_label = f'{{le="{le}"}}' if label_str: bucket_label = "{" + f'le="{le}", ' + label_str[1:] - lines.append(f"{self.name}_bucket{bucket_label} {cumulative}") + lines.append(f"{self.name}_bucket{bucket_label} {bucket.count}") # Sum and count lines.append(f"{self.name}_sum{label_str} {self._sum}") diff --git a/telefuser/metrics/runtime.py b/telefuser/metrics/runtime.py new file mode 100644 index 0000000..8c4d9a9 --- /dev/null +++ b/telefuser/metrics/runtime.py @@ -0,0 +1,139 @@ +"""Lightweight target-side measurements for external benchmark collectors.""" + +from __future__ import annotations + +import platform +import subprocess +import time +from collections.abc import Callable, Sequence +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import torch + + +@dataclass(frozen=True) +class RuntimeMeasurement: + """State needed to finish one synchronized runtime measurement.""" + + started_at_s: float + devices: tuple[str, ...] + capture_peak_memory: bool + clock: Callable[[], float] = field(repr=False, compare=False) + + +def canonical_cuda_devices( + values: Sequence[str | torch.device], +) -> tuple[str, ...]: + """Return unique canonical CUDA device names, ignoring non-CUDA values.""" + + devices: list[str] = [] + for value in values: + device = torch.device(value) + if device.type != "cuda": + continue + index = device.index if device.index is not None else 0 + canonical = f"cuda:{index}" + if canonical not in devices: + devices.append(canonical) + return tuple(devices) + + +def visible_cuda_devices() -> tuple[str, ...]: + """Return every CUDA device visible to the current process.""" + + if not torch.cuda.is_available(): + return () + return tuple(f"cuda:{index}" for index in range(torch.cuda.device_count())) + + +def _synchronize(devices: Sequence[str]) -> None: + if not torch.cuda.is_available(): + return + for device in devices: + torch.cuda.synchronize(device) + + +def start_runtime_measurement( + devices: Sequence[str | torch.device], + *, + capture_peak_memory: bool = False, + clock: Callable[[], float] = time.perf_counter, +) -> RuntimeMeasurement: + """Synchronize devices, optionally reset peaks, and start a phase timer.""" + + canonical = canonical_cuda_devices(devices) + _synchronize(canonical) + if capture_peak_memory and torch.cuda.is_available(): + for device in canonical: + torch.cuda.reset_peak_memory_stats(device) + return RuntimeMeasurement( + started_at_s=clock(), + devices=canonical, + capture_peak_memory=capture_peak_memory, + clock=clock, + ) + + +def finish_runtime_measurement(measurement: RuntimeMeasurement) -> dict[str, Any]: + """Finish a measurement and return AIPerf-compatible phase facts.""" + + _synchronize(measurement.devices) + seconds = max(measurement.clock() - measurement.started_at_s, 0.0) + memory: list[dict[str, int | str]] = [] + if measurement.capture_peak_memory and torch.cuda.is_available(): + memory = [ + { + "device": device, + "peak_allocated_bytes": int(torch.cuda.max_memory_allocated(device)), + "peak_reserved_bytes": int(torch.cuda.max_memory_reserved(device)), + } + for device in measurement.devices + ] + return {"seconds": seconds, "memory": memory} + + +def _git_commit(repo_root: Path | None) -> str | None: + if repo_root is None: + return None + try: + return subprocess.run( + ["git", "-C", str(repo_root), "rev-parse", "HEAD"], + check=True, + capture_output=True, + text=True, + timeout=5, + ).stdout.strip() + except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired): + return None + + +def collect_runtime_environment( + devices: Sequence[str | torch.device], + *, + repo_root: Path | None = None, +) -> dict[str, Any]: + """Collect stable software and accelerator identity for benchmark artifacts.""" + + canonical = canonical_cuda_devices(devices) + gpu_info: list[dict[str, Any]] = [] + if torch.cuda.is_available(): + for device in canonical: + properties = torch.cuda.get_device_properties(device) + gpu_info.append( + { + "device": device, + "name": properties.name, + "compute_capability": (f"{properties.major}.{properties.minor}"), + "total_memory_bytes": int(properties.total_memory), + } + ) + return { + "python_version": platform.python_version(), + "platform": platform.platform(), + "torch_version": torch.__version__, + "cuda_version": torch.version.cuda, + "telefuser_git_commit": _git_commit(repo_root), + "gpus": gpu_info, + } diff --git a/telefuser/pipelines/lingbot_world_fast/service.py b/telefuser/pipelines/lingbot_world_fast/service.py index 79c4523..005efa7 100644 --- a/telefuser/pipelines/lingbot_world_fast/service.py +++ b/telefuser/pipelines/lingbot_world_fast/service.py @@ -12,6 +12,11 @@ import torch from PIL import Image, ImageDraw +from telefuser.metrics.runtime import ( + RuntimeMeasurement, + finish_runtime_measurement, + start_runtime_measurement, +) from telefuser.utils.logging import logger from .pipeline import LingBotWorldFastPipeline @@ -106,6 +111,7 @@ def create_session(self, config: dict) -> str: control_yaw_step_degrees=float(config.get("control_yaw_step_degrees", 10.0)), control_lateral_step=float(config.get("control_lateral_step", 0.12)), show_control_hud=bool(config.get("show_control_hud", True)), + benchmark_metrics=bool(config.get("benchmark_metrics", False)), ) state = LingBotWorldFastSessionState( config=session_config, @@ -351,17 +357,58 @@ def emit_status(stage: str, **data: object) -> None: payload.update(data) self._put_output(state, payload) + devices = ( + self.pipeline.device, + self.pipeline.vae_device, + self.pipeline.text_device, + ) + + def start_measurement() -> RuntimeMeasurement | None: + if not state.config.benchmark_metrics: + return None + try: + return start_runtime_measurement( + devices, + capture_peak_memory=True, + ) + except Exception as exc: + logger.warning(f"Could not start LingBot benchmark measurement: {exc}") + return None + + def finish_measurement( + measurement: RuntimeMeasurement | None, + ) -> dict[str, object] | None: + if measurement is None: + return None + try: + return finish_runtime_measurement(measurement) + except Exception as exc: + logger.warning(f"Could not finish LingBot benchmark measurement: {exc}") + return None + try: self._emit_preview_frame(state) emit_status("initializing_runtime") + runtime_measurement = start_measurement() state.runtime = self.pipeline.create_runtime(state.config, progress_callback=emit_status) runtime = state.runtime + runtime_facts = finish_measurement(runtime_measurement) + runtime_metadata = self._runtime_metadata(runtime) + runtime_ready: dict[str, object] = { + "width": runtime.width, + "height": runtime.height, + "latent_frames": runtime.latent_f, + "total_chunks": len(runtime.noise_chunks), + "runtime": runtime_metadata, + } + if runtime_facts is not None: + runtime_ready["measurement"] = { + "name": "runtime_creation", + **runtime_facts, + } emit_status( "runtime_ready", - width=runtime.width, - height=runtime.height, - latent_frames=runtime.latent_f, - total_chunks=len(runtime.noise_chunks), + **runtime_ready, ) chunk_index = 0 while state.active and runtime.active: @@ -398,24 +445,41 @@ def emit_status(stage: str, **data: object) -> None: control_override = self.pipeline.build_control_override(runtime, directional_chunk) emit_status("generating_chunk", index=chunk_index) + chunk_measurement = start_measurement() frames = self.pipeline.generate_next_chunk( runtime, control_override=control_override, progress_callback=emit_status, ) + chunk_facts = finish_measurement(chunk_measurement) if not frames: break if state.config.show_control_hud and applied_controls: frames = self._overlay_control_hud(frames, applied_controls) + encode_started_at = time.perf_counter() + frames_b64 = self.pipeline.encode_frames_to_b64(frames) + encode_seconds = time.perf_counter() - encode_started_at payload = { "type": "chunk", "index": chunk_index, "fps": state.config.fps, "timestamp": time.time(), - "frames_b64": self.pipeline.encode_frames_to_b64(frames), + "frames_b64": frames_b64, } self._put_output(state, payload) - emit_status("chunk_sent", index=chunk_index, frames=len(frames)) + chunk_sent: dict[str, object] = { + "index": chunk_index, + "frames": len(frames), + } + if chunk_facts is not None: + chunk_sent["measurement"] = { + "index": chunk_index, + "frames": len(frames), + "compute_seconds": chunk_facts["seconds"], + "encode_seconds": encode_seconds, + "memory": chunk_facts["memory"], + } + emit_status("chunk_sent", **chunk_sent) chunk_index += 1 except Exception as exc: logger.exception(f"LingBotWorld worker failed: session={session_id}, error={exc}") @@ -433,6 +497,23 @@ def emit_status(stage: str, **data: object) -> None: self._put_output(state, {"type": "done"}) self._release_runtime(state) + def _runtime_metadata( + self, + runtime: LingBotWorldFastRuntimeState, + ) -> dict[str, int]: + cache_capacity = int(runtime.self_kv_cache[0]["k"].shape[1]) + return { + "height": int(runtime.height), + "width": int(runtime.width), + "latent_frames": int(runtime.latent_f), + "frame_tokens": int(runtime.frame_tokens), + "chunk_size": int(runtime.chunk_size), + "max_attention_size": int(runtime.max_attention_size), + "kv_local_attn_size": int(self.pipeline.dit.local_attn_size), + "kv_sink_size": int(self.pipeline.dit.sink_size), + "kv_cache_capacity_tokens": cache_capacity, + } + def push_chunk(self, session_id: str, chunk: dict) -> None: state = self._sessions.get(session_id) if state is None: diff --git a/telefuser/pipelines/lingbot_world_fast/session.py b/telefuser/pipelines/lingbot_world_fast/session.py index 9a468f4..9d7c1a9 100644 --- a/telefuser/pipelines/lingbot_world_fast/session.py +++ b/telefuser/pipelines/lingbot_world_fast/session.py @@ -32,6 +32,7 @@ class LingBotWorldFastSessionConfig: control_yaw_step_degrees: float = 10.0 control_lateral_step: float = 0.12 show_control_hud: bool = True + benchmark_metrics: bool = False @dataclass diff --git a/telefuser/service/api/openai/adapter.py b/telefuser/service/api/openai/adapter.py index 91b72aa..5170b2c 100644 --- a/telefuser/service/api/openai/adapter.py +++ b/telefuser/service/api/openai/adapter.py @@ -20,6 +20,7 @@ validate_image_size, ) from telefuser.service.api.schema import TaskRequest +from telefuser.service_types import AspectRatio from telefuser.utils.logging import logger VIDEO_REFERENCE_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".webm"} @@ -163,7 +164,9 @@ def _video_to_task(req: VideoGenerationsRequest, task_type: str | None = None) - else: extra_fields["width"] = width extra_fields["height"] = height - aspect_ratio = infer_aspect_ratio(width, height) + inferred_aspect_ratio = infer_aspect_ratio(width, height) + if inferred_aspect_ratio in AspectRatio.values(): + aspect_ratio = inferred_aspect_ratio base_fields = { "task": task, diff --git a/telefuser/service/api/openai/image_routes.py b/telefuser/service/api/openai/image_routes.py index 2199c1a..e7b6a29 100644 --- a/telefuser/service/api/openai/image_routes.py +++ b/telefuser/service/api/openai/image_routes.py @@ -94,9 +94,17 @@ def __init__(self, api_server: ApiServer) -> None: """Initialize with ApiServer instance.""" self.api = api_server self.task_manager: TaskManager | None = getattr(api_server, "task_manager", None) - self.file_service = getattr(api_server, "file_service", None) self.media_service = getattr(api_server, "media_service", None) + @property + def file_service(self) -> Any | None: + """Return the current file service from the API server. + + The ApiServer installs routes before initialize_services() wires the + file service, so route handlers must not keep the initial None value. + """ + return getattr(self.api, "file_service", None) + async def create_image_generation(self, request: ImageGenerationsRequest) -> ImageResponse: """Handle image generation request with synchronous waiting.""" if not self.task_manager: @@ -269,7 +277,7 @@ async def get_image_content(self, image_id: str) -> FileResponse: raise HTTPException(status_code=404, detail=f"Image {image_id} has no output path") path = Path(output_path) - if not path.is_absolute(): + if not path.is_absolute() and not path.exists(): output_dir = getattr(self.file_service, "output_image_dir", None) if output_dir: path = output_dir / path diff --git a/telefuser/service/api/openai/video_routes.py b/telefuser/service/api/openai/video_routes.py index b7ff65c..4f7eb33 100644 --- a/telefuser/service/api/openai/video_routes.py +++ b/telefuser/service/api/openai/video_routes.py @@ -135,7 +135,15 @@ def __init__(self, api_server: ApiServer) -> None: """Initialize with ApiServer instance.""" self.api = api_server self.task_manager: TaskManager | None = getattr(api_server, "task_manager", None) - self.file_service = getattr(api_server, "file_service", None) + + @property + def file_service(self) -> Any | None: + """Return the current file service from the API server. + + The ApiServer installs routes before initialize_services() wires the + file service, so route handlers must not keep the initial None value. + """ + return getattr(self.api, "file_service", None) async def create_video( self, @@ -226,6 +234,8 @@ async def list_videos(self, after: str | None = None, limit: int = 20, order: st seconds=task_data.get("target_video_length", 4), model=task_data.get("model", "wan-video"), output_path=task_data.get("output_path"), + peak_memory_mb=task_data.get("peak_memory_mb"), + inference_time_s=task_data.get("inference_time_s"), ) videos.append(video) @@ -270,6 +280,8 @@ async def retrieve_video(self, video_id: str) -> VideoResponse: model=getattr(message, "model", None) or "wan-video", output_path=task_status.get("output_path"), progress=progress, + peak_memory_mb=task_status.get("peak_memory_mb"), + inference_time_s=task_status.get("inference_time_s"), ) if status == TaskStatus.FAILED.value: @@ -318,7 +330,7 @@ async def get_video_content(self, video_id: str) -> FileResponse: raise HTTPException(status_code=404, detail=f"Video {video_id} has no output path") path = Path(output_path) - if not path.is_absolute(): + if not path.is_absolute() and not path.exists(): output_dir = getattr(self.file_service, "output_video_dir", None) if output_dir: path = output_dir / path diff --git a/telefuser/service/api/schema.py b/telefuser/service/api/schema.py index 58c8dee..fa23bbd 100644 --- a/telefuser/service/api/schema.py +++ b/telefuser/service/api/schema.py @@ -71,6 +71,8 @@ class TaskResponse(BaseModel): task_id: str task_status: TaskStatus output_path: str + peak_memory_mb: float | None = Field(default=None, ge=0, description="Peak accelerator memory used by the task.") + inference_time_s: float | None = Field(default=None, ge=0, description="Task inference duration in seconds.") class StopTaskResponse(BaseModel): diff --git a/telefuser/service/core/file_service.py b/telefuser/service/core/file_service.py index b91ba00..ded6319 100644 --- a/telefuser/service/core/file_service.py +++ b/telefuser/service/core/file_service.py @@ -38,6 +38,7 @@ def __init__(self, cache_dir: Path, max_file_size: int | None = None) -> None: for directory in [ self.input_image_dir, + self.input_video_dir, self.output_dir, self.output_video_dir, self.output_image_dir, diff --git a/telefuser/service/core/pipeline_service.py b/telefuser/service/core/pipeline_service.py index 4524dc3..ff0dcc3 100644 --- a/telefuser/service/core/pipeline_service.py +++ b/telefuser/service/core/pipeline_service.py @@ -4,12 +4,15 @@ import asyncio import json +import math import threading +import time from types import ModuleType from typing import Any import torch.multiprocessing as mp +from telefuser.platforms import current_platform from telefuser.service_types import TaskType from telefuser.utils.logging import logger @@ -25,6 +28,45 @@ mp.set_start_method("spawn", force=True) +_BYTES_PER_MIB = 1024 * 1024 + + +def _nonnegative_finite_float(value: Any) -> float | None: + try: + number = float(value) + except (TypeError, ValueError): + return None + return number if math.isfinite(number) and number >= 0 else None + + +def _reset_peak_memory_stats() -> None: + try: + current_platform.reset_peak_memory_stats() + except Exception: + pass + + +def _peak_memory_mb() -> float | None: + try: + peak_bytes = current_platform.max_memory_allocated() + except Exception: + return None + peak_memory_mb = _nonnegative_finite_float(peak_bytes) + return peak_memory_mb / _BYTES_PER_MIB if peak_memory_mb is not None else None + + +def _raw_runtime_metrics(raw: Any) -> tuple[float | None, float | None]: + if not isinstance(raw, dict): + return None, None + metrics = raw.get("metrics") if isinstance(raw.get("metrics"), dict) else {} + inference_time_s = _nonnegative_finite_float(raw.get("inference_time_s", metrics.get("inference_time_s"))) + peak_memory_mb = _nonnegative_finite_float(raw.get("peak_memory_mb", metrics.get("peak_memory_mb"))) + if peak_memory_mb is None: + peak_memory_gb = _nonnegative_finite_float(raw.get("peak_memory_gb", metrics.get("peak_memory_gb"))) + if peak_memory_gb is not None: + peak_memory_mb = peak_memory_gb * 1024.0 + return inference_time_s, peak_memory_mb + class PipelineService: """Pipeline service with security validation for pipeline configuration files. @@ -175,12 +217,16 @@ async def run_task_with_stop_event( timeout_s = timeout_s if timeout_s is not None else float(getattr(server_config, "task_timeout", 600)) + _reset_peak_memory_stats() + inference_started_at = time.perf_counter() result = await self._runner.run( task_data=task_data, stop_event=stop_event, timeout_s=timeout_s, output_root=output_root, ) + measured_inference_time_s = time.perf_counter() - inference_started_at + raw_inference_time_s, raw_peak_memory_mb = _raw_runtime_metrics(result.raw) return { "task_id": task_data.get("task_id", ""), @@ -188,6 +234,10 @@ async def run_task_with_stop_event( "output_path": result.output_path or "", "message": result.message, "raw": result.raw, + "inference_time_s": ( + raw_inference_time_s if raw_inference_time_s is not None else measured_inference_time_s + ), + "peak_memory_mb": raw_peak_memory_mb if raw_peak_memory_mb is not None else _peak_memory_mb(), } def server_metadata(self) -> dict: diff --git a/telefuser/service/core/stream_pipeline_service.py b/telefuser/service/core/stream_pipeline_service.py index f142d79..87bdf93 100644 --- a/telefuser/service/core/stream_pipeline_service.py +++ b/telefuser/service/core/stream_pipeline_service.py @@ -18,10 +18,19 @@ import asyncio import threading +import time from collections.abc import AsyncGenerator +from pathlib import Path from types import ModuleType from typing import Any, Protocol, runtime_checkable +from telefuser.metrics.runtime import ( + RuntimeMeasurement, + collect_runtime_environment, + finish_runtime_measurement, + start_runtime_measurement, + visible_cuda_devices, +) from telefuser.utils.logging import logger from ..security.security_validator import ( @@ -38,6 +47,7 @@ STREAM_MODE_SERVER_PUSH = "server_push" STREAM_MODE_BIDIRECTIONAL = "bidirectional" +_REPO_ROOT = Path(__file__).resolve().parents[3] # --------------------------------------------------------------------------- # Service protocols – pipeline authors implement one of these @@ -84,6 +94,8 @@ def __init__(self, security_level: SecurityLevel | None = None) -> None: self.ppl_file: str | None = None self._module: ModuleType | None = None self._module_name: str | None = None + self._startup_measurement: dict[str, Any] | None = None + self._runtime_environment: dict[str, Any] = {} self.security_level = security_level or getattr(server_config, "security_level", SecurityLevel.STRICT) self.security_validator = PipelineSecurityValidator( @@ -100,6 +112,17 @@ def start_service(self, ppl_file: str, skip_validation: bool = False) -> bool: logger.warning("Stream service is already running") return True + devices = visible_cuda_devices() + started_at = time.perf_counter() + measurement: RuntimeMeasurement | None = None + try: + measurement = start_runtime_measurement( + devices, + capture_peak_memory=True, + ) + except Exception as exc: + logger.warning(f"Could not initialize stream startup metrics: {exc}") + try: if not skip_validation: validate_pipeline_file(ppl_file, self.security_level, self.security_validator) @@ -119,6 +142,29 @@ def start_service(self, ppl_file: str, skip_validation: bool = False) -> bool: self.stream_mode = self._detect_mode(self.service) self.service.start() self.is_running = True + try: + facts = ( + finish_runtime_measurement(measurement) + if measurement is not None + else { + "seconds": max(time.perf_counter() - started_at, 0.0), + "memory": [], + } + ) + except Exception as exc: + logger.warning(f"Could not finish stream startup metrics: {exc}") + facts = { + "seconds": max(time.perf_counter() - started_at, 0.0), + "memory": [], + } + self._startup_measurement = {"name": "pipeline_init", **facts} + try: + self._runtime_environment = collect_runtime_environment( + devices, + repo_root=_REPO_ROOT, + ) + except Exception as exc: + logger.warning(f"Could not collect stream runtime environment: {exc}") logger.info(f"Stream service started: mode={self.stream_mode}, file={ppl_file}") return True @@ -238,13 +284,22 @@ def has_session(self, session_id: str) -> bool: # -- metadata ------------------------------------------------------------ def server_metadata(self) -> dict[str, Any]: - return { + metadata = { "service_type": "stream", "stream_mode": self.stream_mode, "pipeline_file": self.ppl_file, "security_level": self.security_level.name if self.security_level else "NONE", "runner": "StreamPipelineService", } + startup_measurement = getattr(self, "_startup_measurement", None) + if startup_measurement is not None: + metadata["performance"] = { + "phases": [startup_measurement], + } + runtime_environment = getattr(self, "_runtime_environment", {}) + if runtime_environment: + metadata["environment"] = runtime_environment + return metadata # -- internals ----------------------------------------------------------- diff --git a/telefuser/service/core/task_manager.py b/telefuser/service/core/task_manager.py index 8a3144d..85a8abc 100644 --- a/telefuser/service/core/task_manager.py +++ b/telefuser/service/core/task_manager.py @@ -40,6 +40,8 @@ class TaskInfo: end_time: datetime | None = None error: str | None = None output_path: str | None = None + peak_memory_mb: float | None = None + inference_time_s: float | None = None stop_event: threading.Event = field(default_factory=threading.Event) thread: threading.Thread | None = None @@ -130,7 +132,14 @@ def _transition_to(self, task_id: str, target: TaskStatus, from_statuses: tuple[ self._tasks.move_to_end(task_id) return task - def complete_task(self, task_id: str, output_path: str | None = None) -> None: + def complete_task( + self, + task_id: str, + output_path: str | None = None, + *, + peak_memory_mb: float | None = None, + inference_time_s: float | None = None, + ) -> None: """Mark task as completed with metrics.""" with self._lock: if task_id not in self._tasks: @@ -151,7 +160,9 @@ def complete_task(self, task_id: str, output_path: str | None = None) -> None: if task.start_time: duration = (task.end_time - task.start_time).total_seconds() + task.inference_time_s = inference_time_s if inference_time_s is not None else duration get_service_metrics().record_task_completed(duration) + task.peak_memory_mb = peak_memory_mb def fail_task(self, task_id: str, error: str) -> None: """Mark task as failed with metrics.""" @@ -225,6 +236,8 @@ def get_task_status(self, task_id: str) -> dict[str, Any] | None: "end_time": task.end_time, "error": task.error, "output_path": task.output_path, + "peak_memory_mb": task.peak_memory_mb, + "inference_time_s": task.inference_time_s, } status.update(self._serialize_task_message(task.message)) return status diff --git a/telefuser/service/core/task_processor.py b/telefuser/service/core/task_processor.py index 36798ef..16fe8ec 100644 --- a/telefuser/service/core/task_processor.py +++ b/telefuser/service/core/task_processor.py @@ -142,7 +142,12 @@ async def _process_task(self, task_id: str) -> None: ) if result: - self.task_manager.complete_task(task_id, result.output_path) + self.task_manager.complete_task( + task_id, + result.output_path, + peak_memory_mb=result.peak_memory_mb, + inference_time_s=result.inference_time_s, + ) logger.info(f"Task {task_id} completed successfully") else: if task_info.stop_event.is_set(): diff --git a/telefuser/service/core/task_service.py b/telefuser/service/core/task_service.py index 4e62549..1d5af21 100644 --- a/telefuser/service/core/task_service.py +++ b/telefuser/service/core/task_service.py @@ -151,6 +151,8 @@ async def update_audio_path(audio_name: str, message: TaskRequest, task_data: di task_id=message.task_id, task_status=TaskStatus.COMPLETED, output_path=str(output_path), + peak_memory_mb=result.get("peak_memory_mb"), + inference_time_s=result.get("inference_time_s"), ) else: raise RuntimeError(result.get("message") or "Inference failed") diff --git a/telefuser/service/webrtc/track.py b/telefuser/service/webrtc/track.py index ac29191..b91fb0f 100644 --- a/telefuser/service/webrtc/track.py +++ b/telefuser/service/webrtc/track.py @@ -51,7 +51,7 @@ def __init__( self._buffer = bytearray() self._buf_offset = 0 self._frame_count = 0 - self._start_time: float | None = None + self._last_frame_at: float | None = None self._finished = False def feed(self, data: bytes) -> None: @@ -65,13 +65,10 @@ def signal_done(self) -> None: self._finished = True async def recv(self) -> av.AudioFrame: - if self._start_time is None: - self._start_time = time.time() - - target_time = self._start_time + self._frame_count * self._frame_duration - wait = target_time - time.time() - if wait > 0: - await asyncio.sleep(wait) + if self._last_frame_at is not None: + wait = self._frame_duration - (time.perf_counter() - self._last_frame_at) + if wait > 0: + await asyncio.sleep(wait) buf_avail = len(self._buffer) - self._buf_offset while buf_avail < self._bytes_per_frame: @@ -105,6 +102,7 @@ async def recv(self) -> av.AudioFrame: frame.pts = self._frame_count * self._samples_per_frame frame.time_base = fractions.Fraction(1, self._sample_rate) self._frame_count += 1 + self._last_frame_at = time.perf_counter() return frame @@ -137,7 +135,7 @@ def __init__( self._frame_count = 0 self._task: asyncio.Task | None = None self._finished = False - self._start_time: float | None = None + self._last_frame_at: float | None = None self._last_frame: av.VideoFrame | None = None self._placeholder_width = 640 self._placeholder_height = 360 @@ -190,13 +188,10 @@ async def recv(self) -> av.VideoFrame: if self._task is None and self._generator is not None: self._task = asyncio.create_task(self._consume_generator()) - if self._start_time is None: - self._start_time = time.time() - - target_time = self._start_time + self._frame_count * self._frame_interval - wait = target_time - time.time() - if wait > 0: - await asyncio.sleep(wait) + if self._last_frame_at is not None: + wait = self._frame_interval - (time.perf_counter() - self._last_frame_at) + if wait > 0: + await asyncio.sleep(wait) try: frame = self._queue.get_nowait() @@ -224,6 +219,7 @@ async def recv(self) -> av.VideoFrame: frame.pts = self._frame_count * self._pts_per_frame frame.time_base = fractions.Fraction(1, _RTP_CLOCK_RATE) self._frame_count += 1 + self._last_frame_at = time.perf_counter() return frame def stop(self) -> None: diff --git a/tests/integration/test_service_api.py b/tests/integration/test_service_api.py index 4bb8b86..d043e2c 100644 --- a/tests/integration/test_service_api.py +++ b/tests/integration/test_service_api.py @@ -19,7 +19,16 @@ class TestApiEndpoints: @pytest.fixture def mock_pipeline_service(self): """Create a mock pipeline service.""" - service = Mock() + service = Mock( + spec=[ + "is_running", + "process_task", + "server_metadata", + "start_pipeline", + "stop_distributed_inference", + ] + ) + service.is_running = True service.start_pipeline = Mock(return_value=True) service.stop_distributed_inference = Mock(return_value=None) service.process_task = Mock(return_value={"status": "success"}) diff --git a/tests/unit/distributed/test_pp_comm.py b/tests/unit/distributed/test_pp_comm.py index f247d00..2c67bfa 100644 --- a/tests/unit/distributed/test_pp_comm.py +++ b/tests/unit/distributed/test_pp_comm.py @@ -56,7 +56,7 @@ def test_send_on_last_stage_logs_warning(self): """Test that send on last stage logs warning and returns None.""" comm = PipelineP2PComm(None) # Single GPU, is_last_stage=True - tensor = torch.randn(1, 10, 512, device="cuda") + tensor = torch.randn(1, 10, 512) result = comm.send(tensor) assert result is None @@ -72,7 +72,7 @@ def test_send_recv_single_gpu(self): """Test send_recv on single GPU (no-op).""" comm = PipelineP2PComm(None) # Single GPU - send_tensor = torch.randn(1, 10, 512, device="cuda") + send_tensor = torch.randn(1, 10, 512) result = comm.send_recv(send_tensor) # On single GPU, recv_buffer is None since there's no previous stage @@ -114,7 +114,7 @@ def test_queue_send_on_last_stage(self): """Test queue_send on last stage does nothing.""" comm = PipelineP2PComm(None) # is_last_stage=True - tensor = torch.randn(1, 10, 512, device="cuda") + tensor = torch.randn(1, 10, 512) comm.queue_send(tensor) assert len(comm._ops) == 0 @@ -123,7 +123,7 @@ def test_queue_recv_on_first_stage(self): """Test queue_recv on first stage does nothing.""" comm = PipelineP2PComm(None) # is_first_stage=True - buffer = torch.randn(1, 10, 512, device="cuda") + buffer = torch.randn(1, 10, 512) comm.queue_recv(buffer) assert len(comm._ops) == 0 @@ -152,7 +152,7 @@ def test_send_latent_on_last_stage(self): """Test send_latent on last stage returns early.""" comm = PipelineP2PComm(None) # is_last_stage=True - tensor = torch.randn(1, 10, 512, device="cuda") + tensor = torch.randn(1, 10, 512) # Should not raise, just return comm.send_latent(tensor) @@ -182,7 +182,7 @@ def test_send_latent_async_on_last_stage(self): """Test send_latent_async on last stage returns None.""" comm = PipelineP2PComm(None) # is_last_stage=True - tensor = torch.randn(1, 10, 512, device="cuda") + tensor = torch.randn(1, 10, 512) result = comm.send_latent_async(tensor) assert result is None diff --git a/tests/unit/kernel/test_rmsnorm.py b/tests/unit/kernel/test_rmsnorm.py index 1d1aab0..347d5e5 100644 --- a/tests/unit/kernel/test_rmsnorm.py +++ b/tests/unit/kernel/test_rmsnorm.py @@ -8,11 +8,19 @@ import torch import torch.nn as nn -from telefuser.kernel.triton import ( - fused_add_rms_norm, - norm_infer, - triton_one_pass_rms_norm, -) +try: + if not torch.cuda.is_available(): + raise RuntimeError("CUDA not available") + from telefuser.kernel.triton import ( + fused_add_rms_norm, + norm_infer, + triton_one_pass_rms_norm, + ) +except (ImportError, RuntimeError) as exc: + pytest.skip( + f"Skipping Triton RMSNorm tests: {exc}", + allow_module_level=True, + ) # ============================================================================= # Reference Implementations diff --git a/tests/unit/kernel/test_rotary.py b/tests/unit/kernel/test_rotary.py index 2f2c5a2..71d36a4 100644 --- a/tests/unit/kernel/test_rotary.py +++ b/tests/unit/kernel/test_rotary.py @@ -7,7 +7,15 @@ import pytest import torch -from telefuser.kernel.triton import apply_rotary_embedding +try: + if not torch.cuda.is_available(): + raise RuntimeError("CUDA not available") + from telefuser.kernel.triton import apply_rotary_embedding +except (ImportError, RuntimeError) as exc: + pytest.skip( + f"Skipping Triton rotary tests: {exc}", + allow_module_level=True, + ) # ============================================================================= # Reference Implementations diff --git a/tests/unit/kernel/test_scale_shift.py b/tests/unit/kernel/test_scale_shift.py index b417c84..1c5cf26 100644 --- a/tests/unit/kernel/test_scale_shift.py +++ b/tests/unit/kernel/test_scale_shift.py @@ -7,12 +7,20 @@ import pytest import torch -from telefuser.kernel.triton import ( - fused_layernorm_scale_shift_gate_select01, - fused_residual_layernorm_scale_shift_gate_select01, - fused_scale_shift, - fused_scale_shift_gate_select, -) +try: + if not torch.cuda.is_available(): + raise RuntimeError("CUDA not available") + from telefuser.kernel.triton import ( + fused_layernorm_scale_shift_gate_select01, + fused_residual_layernorm_scale_shift_gate_select01, + fused_scale_shift, + fused_scale_shift_gate_select, + ) +except (ImportError, RuntimeError) as exc: + pytest.skip( + f"Skipping Triton scale/shift tests: {exc}", + allow_module_level=True, + ) # ============================================================================= # Reference Implementations diff --git a/tests/unit/offload/test_async_offload.py b/tests/unit/offload/test_async_offload.py index b034f57..4a9bf3c 100644 --- a/tests/unit/offload/test_async_offload.py +++ b/tests/unit/offload/test_async_offload.py @@ -6,6 +6,12 @@ import pytest import torch +if not torch.cuda.is_available(): + pytest.skip( + "Skipping async offload tests: CUDA not available", + allow_module_level=True, + ) + from telefuser.offload.async_offload import AsyncOffloadManager # All tests in this file require GPU diff --git a/tests/unit/openai/test_adapter.py b/tests/unit/openai/test_adapter.py index 4a99543..6cb0ac0 100644 --- a/tests/unit/openai/test_adapter.py +++ b/tests/unit/openai/test_adapter.py @@ -66,7 +66,7 @@ def test_video_to_task_text_to_video(self): assert task_req.aspect_ratio == "16:9" def test_video_to_task_preserves_exact_openai_size(self): - """Keep exact WxH size available for fixed-workload benchmark services.""" + """Keep exact WxH size while using a TaskRequest-compatible aspect ratio.""" openai_req = VideoGenerationsRequest( prompt="a cat playing", seconds=5, @@ -78,7 +78,7 @@ def test_video_to_task_preserves_exact_openai_size(self): assert task_req.openai_video_size == "832x480" assert task_req.width == 832 assert task_req.height == 480 - assert task_req.aspect_ratio == "26:15" + assert task_req.aspect_ratio == "16:9" def test_video_to_task_image_to_video(self): """Convert I2V request (auto-detected from input).""" diff --git a/tests/unit/openai/test_video_routes.py b/tests/unit/openai/test_video_routes.py index f8697ca..470cdeb 100644 --- a/tests/unit/openai/test_video_routes.py +++ b/tests/unit/openai/test_video_routes.py @@ -4,6 +4,7 @@ Uses real FastAPI TestClient with mocked services. """ +from pathlib import Path from unittest.mock import AsyncMock, MagicMock import pytest @@ -31,6 +32,8 @@ def client(tmp_path): "task_id": "vid_123", "status": TaskStatus.COMPLETED.value, "output_path": str(tmp_path / "video.mp4"), + "peak_memory_mb": 2048.0, + "inference_time_s": 12.5, } ) task_manager.get_task = MagicMock( @@ -138,6 +141,8 @@ def test_get_video_success(self, client): assert response.status_code == 200 data = response.json() assert data["id"] == "vid_123" + assert data["peak_memory_mb"] == 2048.0 + assert data["inference_time_s"] == 12.5 class TestVideoContent: @@ -149,3 +154,69 @@ def test_download_video_success(self, client): assert response.status_code == 200 assert response.content == b"fake_video" + + def test_download_video_uses_late_bound_file_service(self, tmp_path): + """Download works when file_service is attached after routes are created.""" + output_path = tmp_path / "video.mp4" + output_path.write_bytes(b"fake_video") + + task_manager = MagicMock() + task_manager.get_task_status.return_value = { + "task_id": "vid_123", + "status": TaskStatus.COMPLETED.value, + "output_path": str(output_path), + } + task_manager.get_task.return_value = MagicMock( + task_id="vid_123", + output_path=str(output_path), + status=TaskStatus.COMPLETED, + ) + + server = MagicMock() + server.task_manager = task_manager + server.file_service = None + + app = FastAPI() + app.include_router(create_router(server)) + server.file_service = MagicMock() + server.file_service.output_video_dir = tmp_path + + with TestClient(app) as test_client: + response = test_client.get("/v1/videos/vid_123/content") + + assert response.status_code == 200 + assert response.content == b"fake_video" + + def test_download_video_does_not_duplicate_relative_output_dir(self, tmp_path, monkeypatch): + """Download works when output_path already includes the relative output directory.""" + monkeypatch.chdir(tmp_path) + output_dir = Path("work_dirs/server_cache/outputs/videos") + output_path = output_dir / "vid_123.mp4" + output_path.parent.mkdir(parents=True) + output_path.write_bytes(b"fake_video") + + task_manager = MagicMock() + task_manager.get_task_status.return_value = { + "task_id": "vid_123", + "status": TaskStatus.COMPLETED.value, + "output_path": str(output_path), + } + task_manager.get_task.return_value = MagicMock( + task_id="vid_123", + output_path=str(output_path), + status=TaskStatus.COMPLETED, + ) + + server = MagicMock() + server.task_manager = task_manager + server.file_service = MagicMock() + server.file_service.output_video_dir = output_dir + + app = FastAPI() + app.include_router(create_router(server)) + + with TestClient(app) as test_client: + response = test_client.get("/v1/videos/vid_123/content") + + assert response.status_code == 200 + assert response.content == b"fake_video" diff --git a/tests/unit/pipelines/lingbot_world_fast/test_service_metrics.py b/tests/unit/pipelines/lingbot_world_fast/test_service_metrics.py new file mode 100644 index 0000000..e7cb644 --- /dev/null +++ b/tests/unit/pipelines/lingbot_world_fast/test_service_metrics.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import asyncio +from types import SimpleNamespace + +import torch +from PIL import Image + +from telefuser.pipelines.lingbot_world_fast.service import LingBotWorldFastService +from telefuser.pipelines.lingbot_world_fast.session import ( + LingBotWorldFastSessionConfig, + LingBotWorldFastSessionState, +) + + +class _ImmediateLoop: + @staticmethod + def call_soon_threadsafe(callback, payload) -> None: + callback(payload) + + +class _FakePipeline: + def __init__(self) -> None: + self.device = "cpu" + self.vae_device = torch.device("cpu") + self.text_device = torch.device("cpu") + self.dit = SimpleNamespace(local_attn_size=18, sink_size=6) + + @staticmethod + def create_runtime(config, progress_callback=None): + return SimpleNamespace( + active=True, + height=480, + width=832, + latent_f=21, + frame_tokens=1560, + chunk_size=3, + max_attention_size=32760, + noise_chunks=[object()], + self_kv_cache=[{"k": SimpleNamespace(shape=(1, 32760))}], + ) + + @staticmethod + def generate_next_chunk(runtime, **kwargs): + runtime.active = False + return [Image.new("RGB", (16, 16)) for _ in range(3)] + + @staticmethod + def encode_frames_to_b64(frames): + return ["encoded" for _ in frames] + + @staticmethod + def build_control_override(runtime, incoming): + return None + + +def test_lingbot_service_emits_runtime_and_chunk_measurements(monkeypatch) -> None: + service = LingBotWorldFastService(_FakePipeline()) + state = LingBotWorldFastSessionState( + config=LingBotWorldFastSessionConfig( + prompt="test", + image=Image.new("RGB", (16, 16)), + benchmark_metrics=True, + show_control_hud=False, + ), + output_queue=asyncio.Queue(), + loop=_ImmediateLoop(), + ) + service._sessions["session"] = state + monkeypatch.setattr(service, "_emit_preview_frame", lambda current: None) + monkeypatch.setattr(service, "_release_runtime", lambda current: None) + + service._worker_loop("session") + + payloads = [] + while not state.output_queue.empty(): + payloads.append(state.output_queue.get_nowait()) + runtime_ready = next(item for item in payloads if item.get("stage") == "runtime_ready") + chunk_sent = next(item for item in payloads if item.get("stage") == "chunk_sent") + + assert runtime_ready["measurement"]["name"] == "runtime_creation" + assert runtime_ready["runtime"]["kv_cache_capacity_tokens"] == 32760 + assert runtime_ready["runtime"]["kv_local_attn_size"] == 18 + assert chunk_sent["measurement"]["index"] == 0 + assert chunk_sent["measurement"]["frames"] == 3 + assert chunk_sent["measurement"]["compute_seconds"] >= 0 + assert chunk_sent["measurement"]["encode_seconds"] >= 0 diff --git a/tests/unit/service/test_file_service.py b/tests/unit/service/test_file_service.py new file mode 100644 index 0000000..b4bc3c3 --- /dev/null +++ b/tests/unit/service/test_file_service.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from pathlib import Path + +from telefuser.service.core.file_service import FileService + + +def test_file_service_creates_all_media_directories(tmp_path: Path) -> None: + """FileService eagerly creates every upload and output directory.""" + + service = FileService(cache_dir=tmp_path) + + assert service.input_image_dir.is_dir() + assert service.input_video_dir.is_dir() + assert service.input_audio_dir.is_dir() + assert service.output_video_dir.is_dir() + assert service.output_image_dir.is_dir() diff --git a/tests/unit/service/test_pipeline_service_metrics.py b/tests/unit/service/test_pipeline_service_metrics.py new file mode 100644 index 0000000..cfbbf71 --- /dev/null +++ b/tests/unit/service/test_pipeline_service_metrics.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import threading +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +import pytest + +from telefuser.service.core.pipeline_runner import PipelineRunResult +from telefuser.service.core.pipeline_service import PipelineService +from telefuser.service_types import PipelineRunStatus + + +@pytest.mark.asyncio +async def test_pipeline_service_exports_measured_runtime_and_peak_memory() -> None: + service = PipelineService.__new__(PipelineService) + service.is_running = True + service.pipeline = object() + service._runner = SimpleNamespace( + run=AsyncMock( + return_value=PipelineRunResult( + status=PipelineRunStatus.SUCCESS, + output_path="result.mp4", + ) + ) + ) + + with ( + patch("telefuser.service.core.pipeline_service.current_platform.reset_peak_memory_stats") as reset_peak, + patch( + "telefuser.service.core.pipeline_service.current_platform.max_memory_allocated", + return_value=2 * 1024 * 1024 * 1024, + ), + ): + result = await service.run_task_with_stop_event( + {"task_id": "task-1"}, + threading.Event(), + ) + + reset_peak.assert_called_once_with() + assert result["inference_time_s"] >= 0 + assert result["peak_memory_mb"] == 2048.0 + + +@pytest.mark.asyncio +async def test_pipeline_service_prefers_pipeline_reported_runtime_metrics() -> None: + service = PipelineService.__new__(PipelineService) + service.is_running = True + service.pipeline = object() + service._runner = SimpleNamespace( + run=AsyncMock( + return_value=PipelineRunResult( + status=PipelineRunStatus.SUCCESS, + output_path="result.mp4", + raw={ + "metrics": { + "inference_time_s": 12.5, + "peak_memory_gb": 3.0, + } + }, + ) + ) + ) + + with patch("telefuser.service.core.pipeline_service.current_platform.reset_peak_memory_stats"): + result = await service.run_task_with_stop_event( + {"task_id": "task-1"}, + threading.Event(), + ) + + assert result["inference_time_s"] == 12.5 + assert result["peak_memory_mb"] == 3072.0 diff --git a/tests/unit/service/test_stream_pipeline_metrics.py b/tests/unit/service/test_stream_pipeline_metrics.py new file mode 100644 index 0000000..a7f2b67 --- /dev/null +++ b/tests/unit/service/test_stream_pipeline_metrics.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from types import SimpleNamespace + +from telefuser.service.core import stream_pipeline_service as stream_module +from telefuser.service.core.stream_pipeline_service import StreamPipelineService + + +class _FakeServerPushService: + def start(self) -> None: + return None + + def stop(self) -> None: + return None + + async def serve(self, request): + if False: + yield request + + +def test_stream_service_metadata_exposes_startup_performance( + monkeypatch, +) -> None: + service = _FakeServerPushService() + module = SimpleNamespace(get_service=lambda: service) + monkeypatch.setattr( + stream_module, + "load_pipeline_module", + lambda *args, **kwargs: (module, "fake_module"), + ) + monkeypatch.setattr(stream_module, "visible_cuda_devices", lambda: ()) + monkeypatch.setattr( + stream_module, + "finish_runtime_measurement", + lambda measurement: {"seconds": 4.0, "memory": []}, + ) + monkeypatch.setattr( + stream_module, + "collect_runtime_environment", + lambda *args, **kwargs: {"torch_version": "test"}, + ) + pipeline_service = StreamPipelineService() + + assert pipeline_service.start_service("fake.py", skip_validation=True) is True + metadata = pipeline_service.server_metadata() + + assert metadata["performance"]["phases"] == [{"name": "pipeline_init", "seconds": 4.0, "memory": []}] + assert metadata["environment"] == {"torch_version": "test"} diff --git a/tests/unit/service/test_task_runtime.py b/tests/unit/service/test_task_runtime.py index 9da836f..e5624d8 100644 --- a/tests/unit/service/test_task_runtime.py +++ b/tests/unit/service/test_task_runtime.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +from datetime import timedelta from telefuser.service.api.schema import TaskRequest, TaskResponse from telefuser.service.core.task_manager import TaskManager, TaskStatus @@ -25,6 +26,25 @@ def test_complete_task_does_not_override_cancelled_status() -> None: assert status["output_path"] == message.output_path +def test_complete_task_exports_runtime_metrics() -> None: + task_manager = TaskManager(max_queue_size=10) + task_id = task_manager.create_task(TaskRequest(task="t2v")) + task = task_manager.start_task(task_id) + task.start_time -= timedelta(seconds=2) + + task_manager.complete_task( + task_id, + output_path="result.mp4", + peak_memory_mb=2048.0, + inference_time_s=1.5, + ) + + status = task_manager.get_task_status(task_id) + assert status is not None + assert status["peak_memory_mb"] == 2048.0 + assert status["inference_time_s"] == 1.5 + + class _ControlledMediaService: def __init__(self) -> None: self.started = asyncio.Event() diff --git a/tests/unit/service/test_webrtc_track.py b/tests/unit/service/test_webrtc_track.py new file mode 100644 index 0000000..fde510a --- /dev/null +++ b/tests/unit/service/test_webrtc_track.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import asyncio +import time + +import av +import numpy as np +from telefuser.service.webrtc.track import FrameGeneratorTrack + + +def test_video_track_does_not_catch_up_after_receiver_stall() -> None: + async def scenario() -> float: + track = FrameGeneratorTrack(generator=None, fps=20) + for _ in range(3): + track.push_frame( + av.VideoFrame.from_ndarray( + np.zeros((8, 8, 3), dtype=np.uint8), + format="rgb24", + ) + ) + + await track.recv() + await asyncio.sleep(0.12) + await track.recv() + third_started_at = time.perf_counter() + await track.recv() + third_delay_s = time.perf_counter() - third_started_at + track.stop() + return third_delay_s + + assert asyncio.run(scenario()) >= 0.04 diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py index 3d9bae7..016975a 100644 --- a/tests/unit/test_metrics.py +++ b/tests/unit/test_metrics.py @@ -238,6 +238,9 @@ def test_histogram_prometheus_output(self) -> None: assert "request_duration_bucket" in "".join(lines) assert "request_duration_sum" in "".join(lines) assert "request_duration_count" in "".join(lines) + assert 'request_duration_bucket{le="0.1"} 1' in lines + assert 'request_duration_bucket{le="1.0"} 2' in lines + assert 'request_duration_bucket{le="+Inf"} 2' in lines class TestSummary: diff --git a/tests/unit/test_runtime_metrics.py b/tests/unit/test_runtime_metrics.py new file mode 100644 index 0000000..3eb0d74 --- /dev/null +++ b/tests/unit/test_runtime_metrics.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from pathlib import Path + +import torch + +from telefuser.metrics.runtime import ( + canonical_cuda_devices, + collect_runtime_environment, + finish_runtime_measurement, + start_runtime_measurement, +) + + +def test_runtime_measurement_uses_injected_monotonic_clock() -> None: + ticks = iter([10.0, 12.5]) + measurement = start_runtime_measurement([], clock=lambda: next(ticks)) + + result = finish_runtime_measurement(measurement) + + assert result == {"seconds": 2.5, "memory": []} + + +def test_canonical_cuda_devices_deduplicates_and_ignores_cpu() -> None: + assert canonical_cuda_devices([torch.device("cpu"), "cuda", "cuda:1", "cuda:1"]) == ("cuda:0", "cuda:1") + + +def test_runtime_environment_has_stable_software_identity() -> None: + environment = collect_runtime_environment([], repo_root=Path("/missing")) + + assert environment["python_version"] + assert environment["platform"] + assert environment["torch_version"] + assert environment["gpus"] == [] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1a0f9d0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,4249 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.14" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aioice" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "ifaddr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/04/df7286233f468e19e9bedff023b6b246182f0b2ccb04ceeb69b2994021c6/aioice-0.10.2.tar.gz", hash = "sha256:bf236c6829ee33c8e540535d31cd5a066b531cb56de2be94c46be76d68b1a806", size = 44307, upload-time = "2025-11-28T15:56:48.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/e3/0d23b1f930c17d371ce1ec36ee529f22fd19ebc2a07fe3418e3d1d884ce2/aioice-0.10.2-py3-none-any.whl", hash = "sha256:14911c15ab12d096dd14d372ebb4aecbb7420b52c9b76fdfcf54375dec17fcbf", size = 24875, upload-time = "2025-11-28T15:56:47.847Z" }, +] + +[[package]] +name = "aiortc" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aioice" }, + { name = "av" }, + { name = "cryptography" }, + { name = "google-crc32c" }, + { name = "pyee" }, + { name = "pylibsrtp" }, + { name = "pyopenssl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/9c/4e027bfe0195de0442da301e2389329496745d40ae44d2d7c4571c4290ce/aiortc-1.14.0.tar.gz", hash = "sha256:adc8a67ace10a085721e588e06a00358ed8eaf5f6b62f0a95358ff45628dd762", size = 1180864, upload-time = "2025-10-13T21:40:37.905Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/ab/31646a49209568cde3b97eeade0d28bb78b400e6645c56422c101df68932/aiortc-1.14.0-py3-none-any.whl", hash = "sha256:4b244d7e482f4e1f67e685b3468269628eca1ec91fa5b329ab517738cfca086e", size = 93183, upload-time = "2025-10-13T21:40:36.59Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, +] + +[[package]] +name = "av" +version = "16.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/51/2217a9249409d2e88e16e3f16f7c0def9fd3e7ffc4238b2ec211f9935bdb/av-16.1.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:2395748b0c34fe3a150a1721e4f3d4487b939520991b13e7b36f8926b3b12295", size = 26942590, upload-time = "2026-01-09T20:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/a7070f4febc76a327c38808e01e2ff6b94531fe0b321af54ea3915165338/av-16.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:72d7ac832710a158eeb7a93242370aa024a7646516291c562ee7f14a7ea881fd", size = 21507910, upload-time = "2026-01-09T20:18:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/ae/30/ec812418cd9b297f0238fe20eb0747d8a8b68d82c5f73c56fe519a274143/av-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6cbac833092e66b6b0ac4d81ab077970b8ca874951e9c3974d41d922aaa653ed", size = 38738309, upload-time = "2026-01-09T20:18:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b8/6c5795bf1f05f45c5261f8bce6154e0e5e86b158a6676650ddd77c28805e/av-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb990672d97c18f99c02f31c8d5750236f770ffe354b5a52c5f4d16c5e65f619", size = 40293006, upload-time = "2026-01-09T20:18:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a7/44/5e183bcb9333fc3372ee6e683be8b0c9b515a506894b2d32ff465430c074/av-16.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05ad70933ac3b8ef896a820ea64b33b6cca91a5fac5259cb9ba7fa010435be15", size = 40123516, upload-time = "2026-01-09T20:18:09.955Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/b5346d582a3c3d958b4d26a2cc63ce607233582d956121eb20d2bbe55c2e/av-16.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d831a1062a3c47520bf99de6ec682bd1d64a40dfa958e5457bb613c5270e7ce3", size = 41463289, upload-time = "2026-01-09T20:18:12.459Z" }, + { url = "https://files.pythonhosted.org/packages/fa/31/acc946c0545f72b8d0d74584cb2a0ade9b7dfe2190af3ef9aa52a2e3c0b1/av-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:358ab910fef3c5a806c55176f2b27e5663b33c4d0a692dafeb049c6ed71f8aff", size = 31754959, upload-time = "2026-01-09T20:18:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/48/d0/b71b65d1b36520dcb8291a2307d98b7fc12329a45614a303ff92ada4d723/av-16.1.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:e88ad64ee9d2b9c4c5d891f16c22ae78e725188b8926eb88187538d9dd0b232f", size = 26927747, upload-time = "2026-01-09T20:18:16.976Z" }, + { url = "https://files.pythonhosted.org/packages/2f/79/720a5a6ccdee06eafa211b945b0a450e3a0b8fc3d12922f0f3c454d870d2/av-16.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cb296073fa6935724de72593800ba86ae49ed48af03960a4aee34f8a611f442b", size = 21492232, upload-time = "2026-01-09T20:18:19.266Z" }, + { url = "https://files.pythonhosted.org/packages/8e/4f/a1ba8d922f2f6d1a3d52419463ef26dd6c4d43ee364164a71b424b5ae204/av-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:720edd4d25aa73723c1532bb0597806d7b9af5ee34fc02358782c358cfe2f879", size = 39291737, upload-time = "2026-01-09T20:18:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/1a/31/fc62b9fe8738d2693e18d99f040b219e26e8df894c10d065f27c6b4f07e3/av-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c7f2bc703d0df260a1fdf4de4253c7f5500ca9fc57772ea241b0cb241bcf972e", size = 40846822, upload-time = "2026-01-09T20:18:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/53/10/ab446583dbce730000e8e6beec6ec3c2753e628c7f78f334a35cad0317f4/av-16.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d69c393809babada7d54964d56099e4b30a3e1f8b5736ca5e27bd7be0e0f3c83", size = 40675604, upload-time = "2026-01-09T20:18:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/31/d7/1003be685277005f6d63fd9e64904ee222fe1f7a0ea70af313468bb597db/av-16.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:441892be28582356d53f282873c5a951592daaf71642c7f20165e3ddcb0b4c63", size = 42015955, upload-time = "2026-01-09T20:18:29.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/4a/fa2a38ee9306bf4579f556f94ecbc757520652eb91294d2a99c7cf7623b9/av-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:273a3e32de64819e4a1cd96341824299fe06f70c46f2288b5dc4173944f0fd62", size = 31750339, upload-time = "2026-01-09T20:18:32.249Z" }, + { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, + { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/2a/63797a4dde34283dd8054219fcb29294ba1c25d68ba8c8c8a6ae53c62c45/av-16.1.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:ce2a1b3d8bf619f6c47a9f28cfa7518ff75ddd516c234a4ee351037b05e6a587", size = 26916715, upload-time = "2026-01-11T09:57:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c4/0b49cf730d0ae8cda925402f18ae814aef351f5772d14da72dd87ff66448/av-16.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:408dbe6a2573ca58a855eb8cd854112b33ea598651902c36709f5f84c991ed8e", size = 21452167, upload-time = "2026-01-11T09:57:50.606Z" }, + { url = "https://files.pythonhosted.org/packages/51/23/408806503e8d5d840975aad5699b153aaa21eb6de41ade75248a79b7a37f/av-16.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:57f657f86652a160a8a01887aaab82282f9e629abf94c780bbdbb01595d6f0f7", size = 39215659, upload-time = "2026-01-11T09:57:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/c4/19/a8528d5bba592b3903f44c28dab9cc653c95fcf7393f382d2751a1d1523e/av-16.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:adbad2b355c2ee4552cac59762809d791bda90586d134a33c6f13727fb86cb3a", size = 40874970, upload-time = "2026-01-11T09:57:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/e8/24/2dbcdf0e929ad56b7df078e514e7bd4ca0d45cba798aff3c8caac097d2f7/av-16.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f42e1a68ec2aebd21f7eb6895be69efa6aa27eec1670536876399725bbda4b99", size = 40530345, upload-time = "2026-01-11T09:58:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/54/27/ae91b41207f34e99602d1c72ab6ffd9c51d7c67e3fbcd4e3a6c0e54f882c/av-16.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58fe47aeaef0f100c40ec8a5de9abbd37f118d3ca03829a1009cf288e9aef67c", size = 41972163, upload-time = "2026-01-11T09:58:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7a/22158fb923b2a9a00dfab0e96ef2e8a1763a94dd89e666a5858412383d46/av-16.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:565093ebc93b2f4b76782589564869dadfa83af5b852edebedd8fee746457d06", size = 31729230, upload-time = "2026-01-11T09:58:07.254Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f1/878f8687d801d6c4565d57ebec08449c46f75126ebca8e0fed6986599627/av-16.1.0-cp313-cp313t-macosx_11_0_x86_64.whl", hash = "sha256:574081a24edb98343fd9f473e21ae155bf61443d4ec9d7708987fa597d6b04b2", size = 27008769, upload-time = "2026-01-11T09:58:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/30/f1/bd4ce8c8b5cbf1d43e27048e436cbc9de628d48ede088a1d0a993768eb86/av-16.1.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:9ab00ea29c25ebf2ea1d1e928d7babb3532d562481c5d96c0829212b70756ad0", size = 21590588, upload-time = "2026-01-11T09:58:12.629Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/c81f6f9209201ff0b5d5bed6da6c6e641eef52d8fbc930d738c3f4f6f75d/av-16.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a84a91188c1071f238a9523fd42dbe567fb2e2607b22b779851b2ce0eac1b560", size = 40638029, upload-time = "2026-01-11T09:58:15.399Z" }, + { url = "https://files.pythonhosted.org/packages/15/4d/07edff82b78d0459a6e807e01cd280d3180ce832efc1543de80d77676722/av-16.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c2cd0de4dd022a7225ff224fde8e7971496d700be41c50adaaa26c07bb50bf97", size = 41970776, upload-time = "2026-01-11T09:58:19.075Z" }, + { url = "https://files.pythonhosted.org/packages/da/9d/1f48b354b82fa135d388477cd1b11b81bdd4384bd6a42a60808e2ec2d66b/av-16.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0816143530624a5a93bc5494f8c6eeaf77549b9366709c2ac8566c1e9bff6df5", size = 41764751, upload-time = "2026-01-11T09:58:22.788Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c7/a509801e98db35ec552dd79da7bdbcff7104044bfeb4c7d196c1ce121593/av-16.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e3a28053af29644696d0c007e897d19b1197585834660a54773e12a40b16974c", size = 43034355, upload-time = "2026-01-11T09:58:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/e5f530d9e8f640da5f5c5f681a424c65f9dd171c871cd255d8a861785a6e/av-16.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e3e67144a202b95ed299d165232533989390a9ea3119d37eccec697dc6dbb0c", size = 31947047, upload-time = "2026-01-11T09:58:31.867Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "backrefs" +version = "7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, + { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, +] + +[[package]] +name = "bracex" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/f5/4473ad9b48cd0420a2d762a3750fa0e078e23e060b1af72662e5987e5530/bracex-3.0.tar.gz", hash = "sha256:b73f718d6bd98d8419e45df02426c86e9967c179949f779340d6c3a8c83b9111", size = 43162, upload-time = "2026-06-30T00:43:35.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/2e/68781b78e764e5ccc4af1e3d27e060069c73af90234853fa80000e7ee79d/bracex-3.0-py3-none-any.whl", hash = "sha256:3833e61c2f092d5aa0468fa2e6c6e990a306185abf763b6d122f0158e59c58a5", size = 11738, upload-time = "2026-06-30T00:43:34.196Z" }, +] + +[[package]] +name = "brotli" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/10/a090475284fc4a71aed40a96f32e44a7fe5bda39687353dd977720b211b6/brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e", size = 863089, upload-time = "2025-11-05T18:38:01.181Z" }, + { url = "https://files.pythonhosted.org/packages/03/41/17416630e46c07ac21e378c3464815dd2e120b441e641bc516ac32cc51d2/brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984", size = 445442, upload-time = "2025-11-05T18:38:02.434Z" }, + { url = "https://files.pythonhosted.org/packages/24/31/90cc06584deb5d4fcafc0985e37741fc6b9717926a78674bbb3ce018957e/brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de", size = 1532658, upload-time = "2025-11-05T18:38:03.588Z" }, + { url = "https://files.pythonhosted.org/packages/62/17/33bf0c83bcbc96756dfd712201d87342732fad70bb3472c27e833a44a4f9/brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947", size = 1631241, upload-time = "2025-11-05T18:38:04.582Z" }, + { url = "https://files.pythonhosted.org/packages/48/10/f47854a1917b62efe29bc98ac18e5d4f71df03f629184575b862ef2e743b/brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2", size = 1424307, upload-time = "2025-11-05T18:38:05.587Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b7/f88eb461719259c17483484ea8456925ee057897f8e64487d76e24e5e38d/brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84", size = 1488208, upload-time = "2025-11-05T18:38:06.613Z" }, + { url = "https://files.pythonhosted.org/packages/26/59/41bbcb983a0c48b0b8004203e74706c6b6e99a04f3c7ca6f4f41f364db50/brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d", size = 1597574, upload-time = "2025-11-05T18:38:07.838Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e6/8c89c3bdabbe802febb4c5c6ca224a395e97913b5df0dff11b54f23c1788/brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1", size = 1492109, upload-time = "2025-11-05T18:38:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/4b19d4310b2dbd545c0c33f176b0528fa68c3cd0754e34b2f2bcf56548ae/brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997", size = 334461, upload-time = "2025-11-05T18:38:10.729Z" }, + { url = "https://files.pythonhosted.org/packages/ac/39/70981d9f47705e3c2b95c0847dfa3e7a37aa3b7c6030aedc4873081ed005/brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196", size = 369035, upload-time = "2025-11-05T18:38:11.827Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744", size = 863110, upload-time = "2025-11-05T18:38:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f", size = 445438, upload-time = "2025-11-05T18:38:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e1/7fadd47f40ce5549dc44493877db40292277db373da5053aff181656e16e/brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd", size = 1534420, upload-time = "2025-11-05T18:38:15.111Z" }, + { url = "https://files.pythonhosted.org/packages/12/8b/1ed2f64054a5a008a4ccd2f271dbba7a5fb1a3067a99f5ceadedd4c1d5a7/brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe", size = 1632619, upload-time = "2025-11-05T18:38:16.094Z" }, + { url = "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a", size = 1426014, upload-time = "2025-11-05T18:38:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/26/6d/0971a8ea435af5156acaaccec1a505f981c9c80227633851f2810abd252a/brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b", size = 1489661, upload-time = "2025-11-05T18:38:18.41Z" }, + { url = "https://files.pythonhosted.org/packages/f3/75/c1baca8b4ec6c96a03ef8230fab2a785e35297632f402ebb1e78a1e39116/brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3", size = 1599150, upload-time = "2025-11-05T18:38:19.792Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1a/23fcfee1c324fd48a63d7ebf4bac3a4115bdb1b00e600f80f727d850b1ae/brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae", size = 1493505, upload-time = "2025-11-05T18:38:20.913Z" }, + { url = "https://files.pythonhosted.org/packages/36/e5/12904bbd36afeef53d45a84881a4810ae8810ad7e328a971ebbfd760a0b3/brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03", size = 334451, upload-time = "2025-11-05T18:38:21.94Z" }, + { url = "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24", size = 369035, upload-time = "2025-11-05T18:38:22.941Z" }, + { url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/2b/38/f3abb554eee089bd15471057ba85f47e53a44a462cfce265d9bf7088eb09/brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca", size = 1626913, upload-time = "2025-11-05T18:38:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f", size = 1419762, upload-time = "2025-11-05T18:38:28.295Z" }, + { url = "https://files.pythonhosted.org/packages/21/1b/0374a89ee27d152a5069c356c96b93afd1b94eae83f1e004b57eb6ce2f10/brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28", size = 1484494, upload-time = "2025-11-05T18:38:29.29Z" }, + { url = "https://files.pythonhosted.org/packages/cf/57/69d4fe84a67aef4f524dcd075c6eee868d7850e85bf01d778a857d8dbe0a/brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7", size = 1593302, upload-time = "2025-11-05T18:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3b/39e13ce78a8e9a621c5df3aeb5fd181fcc8caba8c48a194cd629771f6828/brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036", size = 1487913, upload-time = "2025-11-05T18:38:31.618Z" }, + { url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362, upload-time = "2025-11-05T18:38:32.939Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115, upload-time = "2025-11-05T18:38:33.765Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" }, + { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" }, + { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" }, + { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" }, + { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" }, + { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" }, + { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +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.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/83/6051c2a2feab48ae5bd27c84ef047191d2d4a3172f689e38eaa48ed17db1/coverage-7.15.1.tar.gz", hash = "sha256:165e9949eaf222ef1f018635d0d7f368a23bfe0212af558534c40d8c04686d67", size = 927640, upload-time = "2026-07-12T20:58:19.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/76/eef242d6bb95fb737fefd9bbf3a318897e4e82cf543b53a61c2a6e8588eb/coverage-7.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:05d87c2a43373ad6b976d0a99ad58c48633633bcdeb896dc645a006472cc4a71", size = 221195, upload-time = "2026-07-12T20:55:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/89/73/41cd50da59d513a30fd3a34b5516b962ac17514defdb6705948446a5c0b1/coverage-7.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2afce82f2cf8f4c9002746a42755e1dc61baff33d9f7ab5569b4c9101f8f4d1d", size = 221714, upload-time = "2026-07-12T20:55:58.104Z" }, + { url = "https://files.pythonhosted.org/packages/22/9d/6df6ac6677719a087c839208186525b7b1f3653c50196c43246482ada65e/coverage-7.15.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a545ef5384d787d0fcac6c349afc2c5f99dcc39e13ed3c191b2c06305f64c04", size = 248454, upload-time = "2026-07-12T20:55:59.336Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f5/86f92c429fbf942986d01c3bb7b0b6c3ad06b09f7fa745023877413dde83/coverage-7.15.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:afaa144b8f5b3bc69fe0ce50d401c46b01ab264782553bfd05a3f98804524ecb", size = 250282, upload-time = "2026-07-12T20:56:00.589Z" }, + { url = "https://files.pythonhosted.org/packages/47/c6/99d0f1c1dd1583aaf8c1deca447e44426be54a76027a8c55e29970f22b15/coverage-7.15.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b6099490e5f88569c46b18605f556c3b30acc9a0a219cf7ef8fab8f7161ec4", size = 252149, upload-time = "2026-07-12T20:56:01.853Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c3/5d30740ec96f5fd8a659e46b6ee9224197f87aa66ce4a20e93dcf46221ac/coverage-7.15.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bb5f6c2cf1ffd0bf2bd925c7cdcae9b4f208e9696d453ed51eb1f5fa0cc5b45b", size = 254061, upload-time = "2026-07-12T20:56:03.277Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/3afb2950673ca6cd22bc9ad6a9d6058c853bef8fe27a6d1df3adc274fd88/coverage-7.15.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:81572011fc1fc271317da35da593944daef7bfd507085e35751abbe702b74f69", size = 249152, upload-time = "2026-07-12T20:56:04.618Z" }, + { url = "https://files.pythonhosted.org/packages/ca/53/25ab3c0a7cf517ed4d30cd4dd82331e005d4b57fec8c19c0ffd94fb50baa/coverage-7.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f765d13c08497687d0780cca66115c6aa4ba6703ad43b61e94fab9db689e3a3", size = 250187, upload-time = "2026-07-12T20:56:06Z" }, + { url = "https://files.pythonhosted.org/packages/ad/2a/8afb5f994e26e919e5dca5164f1c7b6b7ce6090aab1aac32b1ae1782f047/coverage-7.15.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:771caba880fee96493d18dfc465c318e08ab74e3bc2a3e4089e52514be6a6e54", size = 248193, upload-time = "2026-07-12T20:56:07.376Z" }, + { url = "https://files.pythonhosted.org/packages/d4/62/b6616e15288c9a7b628f7745e832fd8af2c03ec1f7dc11da25947f5ab16f/coverage-7.15.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:433a73200848e80f27712fc113b6ff5311f29b479a7d3bd4b1106138a77f9674", size = 252004, upload-time = "2026-07-12T20:56:08.787Z" }, + { url = "https://files.pythonhosted.org/packages/9e/21/5d80d65e4df3018dedb23a44f276f20ce26e429ffd76df03de03bcf9a767/coverage-7.15.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5d6fa45079db9fbeba0a69e3d91189f05301d6ac918162a53179d32fc9ed4910", size = 248463, upload-time = "2026-07-12T20:56:10.245Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/04a22708691561c44d77e1d5a60857b351310fdfbf253533780bb31c8b6f/coverage-7.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:00b6703c6640075cdce5124e9335dfdf9167272475301828acfdd09c0e5ee731", size = 249064, upload-time = "2026-07-12T20:56:11.614Z" }, + { url = "https://files.pythonhosted.org/packages/82/c9/a5c1e8954e657559de478acc1ef73e9393d3fc9b4a2ab4427501d765aca2/coverage-7.15.1-cp310-cp310-win32.whl", hash = "sha256:3ad9a0eac4728327fd870d52f74d2e631d176c5f178eaea2d9983ab5b9755a55", size = 223248, upload-time = "2026-07-12T20:56:13.068Z" }, + { url = "https://files.pythonhosted.org/packages/54/49/5f6566e14611a58e93a926f3a8a7b22e4e0702ebb4c467ac38f452c7f4cf/coverage-7.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:eb5fa75dc3d30e3a1b75da97973479b20ffa9b0641ff56d6e94b5f3e210daa54", size = 223874, upload-time = "2026-07-12T20:56:14.396Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/5095e07a0986930174fc153fd0bb115f7f2724f5344cc672e016d4f23a4e/coverage-7.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6506330b4a8dcf53b95bd84d8d0e817107cdb3fc1438e835029cdf0bc6612eb0", size = 221320, upload-time = "2026-07-12T20:56:16.036Z" }, + { url = "https://files.pythonhosted.org/packages/43/7a/7e2598ce98433a214020a61c0755d5961f9f26df0c630dc73e06b86d3416/coverage-7.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8a51a8ec382c39d939cba0ab07ae949077ae4e842343bd4eed22d432358cea9", size = 221823, upload-time = "2026-07-12T20:56:17.54Z" }, + { url = "https://files.pythonhosted.org/packages/8b/4f/67dac6a69139b490a239d91b6d5ef127982ffb89159769241656ab879ee5/coverage-7.15.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:18ea20e3922d7f8ca9e0ef1084408d08c4ad62d5e531cb9c1f6896a99297ebea", size = 252242, upload-time = "2026-07-12T20:56:18.868Z" }, + { url = "https://files.pythonhosted.org/packages/05/37/5e439725a96de592309725550c8df639c359c209dcb4b152ed46032d4c0d/coverage-7.15.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aab9902a64b8390e3b56e539fddae1d79a267807fe5cb0c18d7d2f544ce867e2", size = 254153, upload-time = "2026-07-12T20:56:20.118Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1c/671ba9e15f9651a99962fb588a1fe4fb267cae4bb85f9bb4ac4413c6f7ef/coverage-7.15.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cc316264317b07a9e90d7f2b4188a15e36e9b54e651081b791b0515fa612a29", size = 256261, upload-time = "2026-07-12T20:56:21.682Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3c/88305322b4d015b4536b7174b8f9b87f850f01af9d89008cdbf984b65ff2/coverage-7.15.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d4e47e7eea81a8ccf060a07627654151d929da62c7b715738387c200905cec89", size = 258222, upload-time = "2026-07-12T20:56:22.962Z" }, + { url = "https://files.pythonhosted.org/packages/8d/20/d02e42333a57f266ded61ed4fb3821da1f2dc143734aaa3dcc9c117204c5/coverage-7.15.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c6829d9a3b55ad2b73ef5fda8302e5be03683789e88b1a079dcf4a773229c21d", size = 252368, upload-time = "2026-07-12T20:56:24.475Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d0/94ebc010926a191d83e83b1f1236f8bd0d14d0eb98b5c324aa4be2589a8e/coverage-7.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:764e045811f9c8cda436641f3f088283351d331a519b5807f19041cd0a68da1c", size = 253953, upload-time = "2026-07-12T20:56:26.036Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/2c8553191da0c2da2c43ff294fb9bab57a5b0fae03560304a82a8b5addc3/coverage-7.15.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:09b3b088aa24489c4082bcc35fcc8224281ab94a653dfb6d3f0c8165b0d628ab", size = 252016, upload-time = "2026-07-12T20:56:27.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/20f47986d8360fa1a31d9858dd2ba0be009d44ecfa8d02120b1eb93c028b/coverage-7.15.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7911b02f57053adf8164ae63edb1c26574d24dfccabadc5268cf69310a69a358", size = 255783, upload-time = "2026-07-12T20:56:28.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/36/fb61983b5259f78fa5e62ee74e91fe4de4c556fcbc9a3b9c9021c2f8b57b/coverage-7.15.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:664e279ed40599b8ed16f4db18d92a7e212c73129672bec8f5d96d4da48d2404", size = 251735, upload-time = "2026-07-12T20:56:30.465Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2e/d109d8d2d6c726ba2e78125297073918a2a91464f0ea777e5398fa3cf75c/coverage-7.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34fe7cf79d5f1f87f2e8ce7dc1c32950841f50e10d0120f263856acfad66de34", size = 252645, upload-time = "2026-07-12T20:56:32.076Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/5b3219cbb46135e14673427f2bf5890c4da4e6f655243692f3448aa3f42c/coverage-7.15.1-cp311-cp311-win32.whl", hash = "sha256:5e2d2536d2f57a354aa382ed303ac0e2e5c9522a508c05b998d26181b94163a7", size = 223414, upload-time = "2026-07-12T20:56:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/5b/80/24e13ade0b6df8090e2492f9c55044bf1423f7ef28c76fc1af8c827a61cb/coverage-7.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:c337da8fca7ea93ab43f3868cfcde6cf6dad32c3906b273cfbad5d7390bc423b", size = 223888, upload-time = "2026-07-12T20:56:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9e/34659930ae380491af48e2f5f5f9a2e99cd9fb7de3d30f27be5ac5425137/coverage-7.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:db3403fdb7a94d5eb73e099befad8104d2a7d110a0f0d99df0de61c5d1fa756c", size = 223435, upload-time = "2026-07-12T20:56:36.302Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/32c1826309beaf4604c54accef108fdd611e5e5e93f2f5192f050cd5f6bd/coverage-7.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9476292594309db922cc841dd13b303b3c388f4c25d279884f7e2341c681f80", size = 221497, upload-time = "2026-07-12T20:56:37.628Z" }, + { url = "https://files.pythonhosted.org/packages/db/5c/b88ce0d68fa550c7f3b58617fbf363bce64df5bf8295a01b627e4696e022/coverage-7.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c579056b0de461b3a62318b63d0b6ce90aed7f8158d3f00da094df82f29d189", size = 221854, upload-time = "2026-07-12T20:56:39.033Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fe/8509fd2a66fc4e0a829f76a0f0b1dc3cc163368352435b5f243168658077/coverage-7.15.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:23214bdbe226f2b0e9c66a7d6a1d59d4a88045dcf86e702cf0fe0d0935e3d615", size = 253359, upload-time = "2026-07-12T20:56:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/e5/81/c7009ed7ea9765adb2b9d095054d748266fae5f07ac6c5f925f33715fcde/coverage-7.15.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df164be93b46b4825cc39339440a05edc54c4d1d865ba4a60fd43d151a2a1cd3", size = 256096, upload-time = "2026-07-12T20:56:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/21/52/dc8ee03968a5ba86e2da5aa48ddc9e3747bd65d63825fdce2d96acb9c5ff/coverage-7.15.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a524fca1a6f08927d9dc2d4c873cfb7bd7202c247f08b14bdc02424071b8b304", size = 257211, upload-time = "2026-07-12T20:56:43.513Z" }, + { url = "https://files.pythonhosted.org/packages/b8/27/95d7623908da8937deb53d48efcdbf423907a47540e63c62fa21372c652b/coverage-7.15.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d70f3542cd38de85a9e257dcb1ac4c1ab4b6d7d2c2a645809207556628755d1c", size = 259473, upload-time = "2026-07-12T20:56:44.974Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3b/730d761928de97d585465680b568ae69622fb40716babadeabffe75cb51b/coverage-7.15.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d78aa537237212c4313aabe5e964b66acc86350ed19ebc56a3e202df33b6077b", size = 253759, upload-time = "2026-07-12T20:56:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fc/6b9277acff1f9484b6c12857af5774689d1a6a95e13265f7405329d2f5da/coverage-7.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a318112bb4f79d9d04766196d5a3388caa825908a6a9b052aa87de3d9aea7c61", size = 255131, upload-time = "2026-07-12T20:56:48.073Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f2/c704f86129594ba34e25a64695d2068c71d51c2b98907184d716c94f4aec/coverage-7.15.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e55d24cada901963eed5bc89fa562aa033f0d84b9d3de4ecf363737c13aed11e", size = 253275, upload-time = "2026-07-12T20:56:49.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/29/80fee8af47de4a6dce71ccf2938491f444687a756af258a56d8469b8f1b0/coverage-7.15.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3c78f0cea7275342cf2adc2ad5fdd0aafa106ad91e66d573568f2fcf62c41df5", size = 257345, upload-time = "2026-07-12T20:56:51.038Z" }, + { url = "https://files.pythonhosted.org/packages/20/21/a1e7d7ed1b48a8adf8fd5154d9e83fcc5ad8e6ff20ae00e44865057dce8d/coverage-7.15.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:86bd37eabe39977216f630a7fc1b698e7f5e81a191c7186013245c6c3d313f9d", size = 252844, upload-time = "2026-07-12T20:56:52.535Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8c/a4bc26e6ee207d412f3678f04d74be1550e83140563ca0e4997510579712/coverage-7.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6db15c217693bdc3ca0b84de1ba9afafe1c14c26a8a29d77f4ed0de2b6132e2", size = 254716, upload-time = "2026-07-12T20:56:53.968Z" }, + { url = "https://files.pythonhosted.org/packages/11/9d/8ad0266ecfada6353cf6627a1a02294cf55a907521b6ee0bd7b770cfd659/coverage-7.15.1-cp312-cp312-win32.whl", hash = "sha256:359f3fbe09a51500c51966596ee4ee4070b356552c70b3b2420eb200d68e0f76", size = 223554, upload-time = "2026-07-12T20:56:55.583Z" }, + { url = "https://files.pythonhosted.org/packages/81/6d/24224929e06c6e05a93f738bc5f9e8e6ab658f8f1d9b823e7b85430e28b8/coverage-7.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:fa75dc099c126e941a9c0baa8ebd2cbc78bd778687534fe410baf754f6d9e374", size = 224087, upload-time = "2026-07-12T20:56:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/35/23/f81441dd01de88e53c97842e706907b307d9078918c3f4998b11e9ac7250/coverage-7.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:26f89cf6d0634375f454fa71057945ad18edb0f1607a90fecf22c57dc3dc289a", size = 223472, upload-time = "2026-07-12T20:56:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1e/6fa289d7993a2a39f1b283ddb58c4bfec80f7800be654b8ba8a9f6a07c63/coverage-7.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:71ac4ca1658ca99160fd58cc6967110e989c34b04627f24ed6ec9f70fb24571a", size = 221519, upload-time = "2026-07-12T20:57:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e1/0db4902a0588234a70ab0218073c0b20fbc5c740aa35f91d360160a2ebc9/coverage-7.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26a40cbf2b13bd94af53ee02a424cb3bb96a9edfac0d00834bd068512a62714b", size = 221895, upload-time = "2026-07-12T20:57:01.867Z" }, + { url = "https://files.pythonhosted.org/packages/b4/cb/3719783865092dac5e08df842730305ee9ab1973ae7ddb6fbdf27d401f30/coverage-7.15.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4c5a5eff4ad4f9f7088fd3fc7a66d98d06566ee294b3b053309fb0a3b45be1e", size = 252882, upload-time = "2026-07-12T20:57:03.459Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5e/caf3abbdbb22629626160ffc9c017eb995b7cb11c0be46b974834cef1792/coverage-7.15.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:962aa56c1c9b016d681265880eb6acc9966029d2c4c559319cc43a1abbb9b59a", size = 255479, upload-time = "2026-07-12T20:57:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f1/d60f375bfe095fef944f0f19427aefdbf9bdd5a9571c41a4bf6e2f5fdb81/coverage-7.15.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1678eb2dc57a8ce67601b029582ef6d41e9e6ca22692aaeccd4107e40f27386c", size = 256715, upload-time = "2026-07-12T20:57:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/d7/17/8b0cbc90d02dc5adad4d9034c1824ec3fa567771b4c39d9c1e3f9b1431b8/coverage-7.15.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1174900a43f6f8c425fee10d7dbddc308adefcdc78aaced32357f5ab750a0e90", size = 258845, upload-time = "2026-07-12T20:57:08.092Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/c5e69f5fb75c322e9a3e4ef64d02eebfc3d66efceccc8514ff80a3c13a56/coverage-7.15.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98847557a6859cadf693792ce89f440cb89692993f60dc6d3a7e35f3d340216f", size = 253098, upload-time = "2026-07-12T20:57:09.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/57/21144252fdd0c01d707d48fbcea13a80b0b7c42ced3f299f885ab8978c3a/coverage-7.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8697b2edb57143546a24389efc11e1b000cd5800fc20d84f04edb601e4a7cfb8", size = 254844, upload-time = "2026-07-12T20:57:11.141Z" }, + { url = "https://files.pythonhosted.org/packages/59/2a/499a28a322b0ce6768328e6c5bb2e2ad00ac068a7c7adb2ecd8533c8c5d9/coverage-7.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6827ac0519be3fe91bf96b4060eb00d1d24f82649b29862cd75a3cfca248b02a", size = 252807, upload-time = "2026-07-12T20:57:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/928a95da5da8b60f2b00e1482c7787b3316188e6d2d227fb8e124ada43a1/coverage-7.15.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2de8ecbbc77c7e4d22572779920ed8979c69168675e96be3a548c996568c6c31", size = 256965, upload-time = "2026-07-12T20:57:14.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/10/889adbc1b8c9f866ed51e18a98bcafc0259fb9d29b81f50a719407c64ea8/coverage-7.15.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2b25f0f0fa5260df9d7bb55d47c8bdc23fa3382c1a18f7c9cae122e6c320b1ad", size = 252628, upload-time = "2026-07-12T20:57:15.892Z" }, + { url = "https://files.pythonhosted.org/packages/1a/30/a5e1871e5d93416511f8e359d1ccebfe0cbb050a1bbf7dd20228533ec0cf/coverage-7.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a2effcbd93ae340a58db718fe4181d967f84d352c4cefeaab4ff82ce813901a", size = 254399, upload-time = "2026-07-12T20:57:17.703Z" }, + { url = "https://files.pythonhosted.org/packages/2d/26/c36fbffd549dadbdd1a75827528fb00a4c46aa3187b007b750b1e2cebbf2/coverage-7.15.1-cp313-cp313-win32.whl", hash = "sha256:895e65c96aef0cecea250f6e35e9a32f11375514e1a0cb5210e0fda128c04e8e", size = 223564, upload-time = "2026-07-12T20:57:19.253Z" }, + { url = "https://files.pythonhosted.org/packages/16/fc/becbb9d2c4206d242b9b1e1e8e24a42f7926c0200dd3c788b9fab4bb96d5/coverage-7.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6d0a28b63a0d75f9ed5118105d1154fc3aa40a8605a30d5d87e3d043ad90fe7", size = 224106, upload-time = "2026-07-12T20:57:21.108Z" }, + { url = "https://files.pythonhosted.org/packages/d3/30/1cfc641461369b6858799fca61c0a8b5edc490c519bf7c636ffa6bbf556f/coverage-7.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:b4ee9818e8bae3544379ad2c09b851c4fb886aaa8860d57a1c1316ddcb16db49", size = 223497, upload-time = "2026-07-12T20:57:22.734Z" }, + { url = "https://files.pythonhosted.org/packages/34/98/07a67cf1a26e795d617ed5c540c042b0ac87b72f810c30c07f076cf334f3/coverage-7.15.1-py3-none-any.whl", hash = "sha256:717d01e6e00bed56ad13306f19e0dd2f4f645ee8159d2c72c72301d6cfc7090c", size = 213284, upload-time = "2026-07-12T20:58:18.079Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "49.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/63/d3/4a83af35d65e3fad632c926fad684c193ea4398569ccb0bbbc7fe8f5dc9a/cryptography-49.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b", size = 3993685, upload-time = "2026-06-12T20:02:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a7/f9dac0ab7f80368c56993a7bf638ef9935f825c91902798481fac0898138/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838", size = 4676239, upload-time = "2026-06-12T20:02:28.793Z" }, + { url = "https://files.pythonhosted.org/packages/d7/70/2ba3769dd0ae167e2f33dfa9592d45db6ff9a61d62ca1a5b3d1bdd09068f/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5", size = 4715584, upload-time = "2026-06-12T20:01:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/94/64/2923570ac1c0bd3a737aa366ac3abbbbde273042308b8cde95e2364a6e6a/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615", size = 4675885, upload-time = "2026-06-12T20:01:55.49Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f8/614dc7e051418cfe53d55173c1e24c6b0085e89996fe90508c2fdf769aef/cryptography-49.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6", size = 4715449, upload-time = "2026-06-12T20:02:05.469Z" }, + { url = "https://files.pythonhosted.org/packages/aa/50/a9caea39ad19c431c1a3f8a31114df65b260cdfe67786b6c7e7c040c4c44/cryptography-49.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6", size = 3783731, upload-time = "2026-06-12T20:02:43.319Z" }, +] + +[[package]] +name = "csscompressor" +version = "0.9.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/8c3ac3d8bc94e6de8d7ae270bb5bc437b210bb9d6d9e46630c98f4abd20c/csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05", size = 237808, upload-time = "2017-11-26T21:13:08.238Z" } + +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, + { url = "https://files.pythonhosted.org/packages/a8/1f/5ef51f5fbaa5d4d3201bb3d7555af028ec1aa4416275ccbf73c9e34e3d2d/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9851b0caa8bfd3bc6fa054eaf57bea7c8e9c3a62db2d2621224677f49f3c53d0", size = 6675244, upload-time = "2026-05-29T23:11:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/53/8fc9b0cdc5b7f62746e6a01b85b6461e5ae27f871010a5fcf8fa6950766d/cuda_pathfinder-1.5.6-py3-none-any.whl", hash = "sha256:7e4c07c117b78ba1fb35dac4c444d21f3677b1b1ff56175c53a8e3025c5b43c0", size = 52972, upload-time = "2026-06-30T00:58:04.34Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine == 'AMD64' and sys_platform == 'win32')" }, +] + +[[package]] +name = "diffusers" +version = "0.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/81/6095237b86a3116c4789f28c4435d5296c00c0fc74ffde99008fd6b3a36c/diffusers-0.39.0.tar.gz", hash = "sha256:14bb1d98c85a0e463d734c99aaa73b480a7bc9bad22af30fbf730ef8f09c1d67", size = 4651240, upload-time = "2026-07-03T08:48:47.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/3f/7469c46e9d22307ea686bab687d70e6bf328722952f9d10339f5e913e608/diffusers-0.39.0-py3-none-any.whl", hash = "sha256:912aca51b5787365110806e984d5555735bf8a461073bb8459029d0bca7870ef", size = 5631176, upload-time = "2026-07-03T08:48:45.337Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +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 = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "faiss-cpu" +version = "1.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/b0/48c083d01b7b68c463c1d56507147a9d733f791e1c469a77215a872a9fb5/faiss_cpu-1.14.3-cp310-abi3-macosx_14_0_arm64.whl", hash = "sha256:a9369863290a3f0e033757e4c10577b6ef7431f1cede394dabd0a137e4e2ed45", size = 4768290, upload-time = "2026-06-13T02:19:03.427Z" }, + { url = "https://files.pythonhosted.org/packages/ab/34/6b04ef5bae3eada6b5a9457d7875cce041c040d53c890815cbd1e9821c65/faiss_cpu-1.14.3-cp310-abi3-macosx_15_0_x86_64.whl", hash = "sha256:f9d0e84d909194f63f027bbd3c1e35e905e48c9345c2db6e6f24da09a6bc5906", size = 6925734, upload-time = "2026-06-13T02:19:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/b451af4b3c6dd18749ecfb58ccb68503b77e49c9aa4a89d950e9d521e058/faiss_cpu-1.14.3-cp310-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d734cfa9ac90b6a5dfed3a27cb706d05f22824703dafc3969b4e2071877a31c", size = 9661210, upload-time = "2026-06-13T02:19:06.99Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ed/57335bc18c9e18677587bec9bf070c675b29c8e683e13f4def0440731ca0/faiss_cpu-1.14.3-cp310-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8780b526c06e57aad90a8c4655dfba2ff1b3195bd600ff4499752fc45159c9fc", size = 18506292, upload-time = "2026-06-13T02:19:09.621Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d3/c6ca8c44a63b909e78aa8a69e14501c79c89613e62261d58455ea603d710/faiss_cpu-1.14.3-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b28ba083e8c02f2c9be03783402537fd3f00d27e68799c44bf88931500ee12ec", size = 11238800, upload-time = "2026-06-13T02:19:12.821Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/b405692913a301251749cb175cb3f564ed257fdaa80a22c9a36444d0095d/faiss_cpu-1.14.3-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cdcb90850cb4b7c27d270839b37bcc0dc8d1fb6a62d1e13053e51ac95061ca25", size = 19237637, upload-time = "2026-06-13T02:19:15.531Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b2/bee73e3514afd88602e606a52f99559872e277ed0010fd04769a6eff41d3/faiss_cpu-1.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:20414d5c98fb6ab9eedfb3df79841865911c2d67b6efe829dc46adfeab6f51b1", size = 16158545, upload-time = "2026-06-13T02:19:18.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/6c/bbf670ad727502102fa90c52f9b47c73753f0adda880d30427ac1d48b37b/faiss_cpu-1.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:f278003d00c30c90cf118b98428bb96f73eba4b9dadac6993c788966d2e983bd", size = 16159350, upload-time = "2026-06-13T02:19:20.601Z" }, + { url = "https://files.pythonhosted.org/packages/59/aa/bfa53255a6aa6e79b2471bb5af895f662feb02b612e1e0f9efc3d893286e/faiss_cpu-1.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a5eb27184123c7ac1060c6b862978eabf0e30c1369ccf8bdb1497d35c06ad3d", size = 16164699, upload-time = "2026-06-13T02:19:22.969Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c1/2fb14f58ff74d7a7d6fd13084c016d9b144ce0bcdf77f6526cc2e4828278/faiss_cpu-1.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea2340f675db59af8db6da4535b541ce6948d68a008989c656c292c7b0c77127", size = 16162836, upload-time = "2026-06-13T02:19:25.529Z" }, +] + +[[package]] +name = "fastapi" +version = "0.139.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/af/a5f50ccfa659ec1802cb4ca842c23f06d906a8cc9aef6016a2caeea3d4ed/fastapi-0.139.0.tar.gz", hash = "sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145", size = 423016, upload-time = "2026-07-01T16:35:33.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/7c/8e3c6ad324ea5cb36604fc3f968554887891c316d9dfde57761611d907ad/fastapi-0.139.0-py3-none-any.whl", hash = "sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189", size = 130339, upload-time = "2026-07-01T16:35:32.19Z" }, +] + +[[package]] +name = "ffmpy" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/d2/1c4c582d71bcc65c76fa69fab85de6257d50fdf6fd4a2317c53917e9a581/ffmpy-1.0.0.tar.gz", hash = "sha256:b12932e95435c8820f1cd041024402765f821971e4bae753b327fc02a6e12f8b", size = 5101, upload-time = "2025-11-11T06:24:23.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/56/dd3669eccebb6d8ac81e624542ebd53fe6f08e1b8f2f8d50aeb7e3b83f99/ffmpy-1.0.0-py3-none-any.whl", hash = "sha256:5640e5f0fd03fb6236d0e119b16ccf6522db1c826fdf35dcb87087b60fd7504f", size = 5614, upload-time = "2025-11-11T06:24:22.818Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, +] + +[[package]] +name = "ftfy" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/30/a8a0c15f9480dc91b5b7f11ebd26105e5f80898d7ff02da197fef35d8395/gitpython-3.1.51.tar.gz", hash = "sha256:22c9c94bb6b0b9f3c7157c684fece45a414cea204586b600beae6cd4570dcd6d", size = 223519, upload-time = "2026-07-12T13:40:07.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/11/1232bb1ba52a230f20ff08e1b3df7cd9d43a71b61cc0a1c37941064fe4c7/gitpython-3.1.51-py3-none-any.whl", hash = "sha256:d5b29685708f3c80a56db040b665bfd69492c8e150b5848532af8013b686c5a4", size = 215246, upload-time = "2026-07-12T13:40:06.43Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/ac/6f7bc93886a823ab545948c2dd48143027b2355ad1944c7cf852b338dc91/google_crc32c-1.8.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0470b8c3d73b5f4e3300165498e4cf25221c7eb37f1159e221d1825b6df8a7ff", size = 31296, upload-time = "2025-12-16T00:19:07.261Z" }, + { url = "https://files.pythonhosted.org/packages/f7/97/a5accde175dee985311d949cfcb1249dcbb290f5ec83c994ea733311948f/google_crc32c-1.8.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:119fcd90c57c89f30040b47c211acee231b25a45d225e3225294386f5d258288", size = 30870, upload-time = "2025-12-16T00:29:17.669Z" }, + { url = "https://files.pythonhosted.org/packages/3d/63/bec827e70b7a0d4094e7476f863c0dbd6b5f0f1f91d9c9b32b76dcdfeb4e/google_crc32c-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f35aaffc8ccd81ba3162443fabb920e65b1f20ab1952a31b13173a67811467d", size = 33214, upload-time = "2025-12-16T00:40:19.618Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/11b70614df04c289128d782efc084b9035ef8466b3d0a8757c1b6f5cf7ac/google_crc32c-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:864abafe7d6e2c4c66395c1eb0fe12dc891879769b52a3d56499612ca93b6092", size = 33589, upload-time = "2025-12-16T00:40:20.7Z" }, + { url = "https://files.pythonhosted.org/packages/3e/00/a08a4bc24f1261cc5b0f47312d8aebfbe4b53c2e6307f1b595605eed246b/google_crc32c-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:db3fe8eaf0612fc8b20fa21a5f25bd785bc3cd5be69f8f3412b0ac2ffd49e733", size = 34437, upload-time = "2025-12-16T00:35:19.437Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, +] + +[[package]] +name = "gradio" +version = "5.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "brotli" }, + { name = "fastapi" }, + { name = "ffmpy" }, + { name = "gradio-client" }, + { name = "groovy" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydub" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "ruff" }, + { name = "safehttpx" }, + { name = "semantic-version" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/04/8daf96bd6d2470f03e2a15a9fc900c7ecf6549619173f16c5944c7ec15a7/gradio-5.50.0-py3-none-any.whl", hash = "sha256:d06770d57cdda9b703ef9cf767ac93a890a0e12d82679a310eef74203a3673f4", size = 63530991, upload-time = "2025-11-21T18:07:19.239Z" }, +] + +[[package]] +name = "gradio-client" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "packaging" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/8a/f2a47134c5b5a7f3bad27eae749589a80d81efaaad8f59af47c136712bf6/gradio_client-1.14.0-py3-none-any.whl", hash = "sha256:9a2f5151978411e0f8b55a2d38cddd0a94491851149d14db4af96f5a09774825", size = 325555, upload-time = "2025-11-21T18:04:21.834Z" }, +] + +[[package]] +name = "griffelib" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/e4/8d187ea29c2e30b3a09505c567513077d6117861bde1fbd997a167f262ec/griffelib-2.1.0.tar.gz", hash = "sha256:762a186d2c6fd6794d4ea20d428d597ffb857cb56b66421651cbba15bdd5e813", size = 216234, upload-time = "2026-06-19T12:05:42.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl", hash = "sha256:cc7b3d2d2865ad0b909fcc38086e3f554b5ea7acbaa7bbb7ecaa3f5dfb7d9f00", size = 142560, upload-time = "2026-06-19T12:05:38.742Z" }, +] + +[[package]] +name = "groovy" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/ee/dd9ba7beae1005e54131b7d45263cc74c8a066d47d354e6d58ae9445a388/hf_xet-1.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", size = 4069485, upload-time = "2026-06-08T23:02:13.193Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/9cae6cfeb4e03070874e73e5c97c66eb90369d3206b6a2b1ef5f96520888/hf_xet-1.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", size = 3838493, upload-time = "2026-06-08T23:02:15.282Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b4/d5c01e0eb6d9f2ca2dacd84d0d1b71e6cfbb2ef3208c968528e010e9b3d7/hf_xet-1.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", size = 4505658, upload-time = "2026-06-08T23:02:17.196Z" }, + { url = "https://files.pythonhosted.org/packages/76/c5/29a7598c0c6383c523dc22186d577f4e04267a626cd95ae60f67c00bfe66/hf_xet-1.5.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", size = 4292822, upload-time = "2026-06-08T23:02:18.608Z" }, + { url = "https://files.pythonhosted.org/packages/04/9a/dceaf6ca69390126b86ea825fb354b93d01163199070b7bd849225de9468/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", size = 4491255, upload-time = "2026-06-08T23:02:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/48/a7/e5a7afaacf6c1791fdbeeac42951fb81c3d2bc482992b115dedcc86d963e/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", size = 4711062, upload-time = "2026-06-08T23:02:21.863Z" }, + { url = "https://files.pythonhosted.org/packages/53/49/2802f8433c9742ce281bddc1e65c02c32268ca3098d66828b05e12e45ee2/hf_xet-1.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", size = 4017205, upload-time = "2026-06-08T23:02:23.495Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5a/50c71195b9fb883659f596e7252faf4c18c58e753a9013bdbf9bac5d2250/hf_xet-1.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", size = 3845426, upload-time = "2026-06-08T23:02:25.124Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" }, + { url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" }, + { url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" }, + { url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" }, + { url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" }, +] + +[[package]] +name = "htmlmin2" +version = "0.1.13" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/31/a76f4bfa885f93b8167cb4c85cf32b54d1f64384d0b897d45bc6d19b7b45/htmlmin2-0.1.13-py3-none-any.whl", hash = "sha256:75609f2a42e64f7ce57dbff28a39890363bde9e7e5885db633317efbdf8c79a2", size = 34486, upload-time = "2023-03-14T21:28:30.388Z" }, +] + +[[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 = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b9/be66eb0decd730d89b9c94f930e4b8d87787b05724bb84af98bfd825f72c/httptools-0.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826", size = 208805, upload-time = "2026-05-25T22:16:50.434Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f7/b4d41eaae2869d31356bc4bbf546f44fae83ff298af0a043ca0625b06773/httptools-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77", size = 113527, upload-time = "2026-05-25T22:16:51.672Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e4/77487e14fc7be47180fd0eb4267c7486d0cc59b74031839a3daf8650136b/httptools-0.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6f21e2a3b0067bbe7f67e34cfd16276af556e5e52f4c7503be0cb5f90e905e4", size = 450035, upload-time = "2026-05-25T22:16:53.313Z" }, + { url = "https://files.pythonhosted.org/packages/da/72/5a8f787e323f56fbd86c32a4be92a86776e4cfe8b4317db999f452028362/httptools-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea897f0c729581ebf72131a438a7932d9b14efef72d75ada966700cac3caaeb", size = 451101, upload-time = "2026-05-25T22:16:54.696Z" }, + { url = "https://files.pythonhosted.org/packages/ed/41/b44a25560955197674b6744cb903664300e239235a5eaa69df0890d87054/httptools-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0d726cc107fceb7d45f978483b4b70dd8caa836f5914d3434bb18628eb73813", size = 436140, upload-time = "2026-05-25T22:16:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/74/b0/054aac84c03d7e097bf4c605fb7e74eec3d65c0276adf64ee97f3a103ff5/httptools-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9878eb2785ba5eb70631ad269b37976f73d647955e26c91d490eb8a4edfda4ba", size = 437041, upload-time = "2026-05-25T22:16:57.716Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e8/86b85bbc0ac7892232f1a99ab96a9aa71936984fa06adfc0afc83ca7789e/httptools-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b205e5f5523fa039679da0dfe5a10132b2a4abeae6a86fdd1ddc035f7f836557", size = 90454, upload-time = "2026-05-25T22:16:58.871Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, + { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, +] + +[[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 = "huggingface-hub" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485, upload-time = "2022-06-15T21:40:27.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, +] + +[[package]] +name = "imageio" +version = "2.37.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, +] + +[package.optional-dependencies] +ffmpeg = [ + { name = "imageio-ffmpeg" }, + { name = "psutil" }, +] + +[[package]] +name = "imageio-ffmpeg" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/c3343c721f2a1b0c9fc71c1aebf1966a3b7f08c2eea8ed5437a2865611d6/imageio_ffmpeg-0.6.0.tar.gz", hash = "sha256:e2556bed8e005564a9f925bb7afa4002d82770d6b08825078b7697ab88ba1755", size = 25210, upload-time = "2025-01-16T21:34:32.747Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl", hash = "sha256:9d2baaf867088508d4a3458e61eeb30e945c4ad8016025545f66c4b5aaef0a61", size = 24932969, upload-time = "2025-01-16T21:34:20.464Z" }, + { url = "https://files.pythonhosted.org/packages/40/5c/f3d8a657d362cc93b81aab8feda487317da5b5d31c0e1fdfd5e986e55d17/imageio_ffmpeg-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1ae3173414b5fc5f538a726c4e48ea97edc0d2cdc11f103afee655c463fa742", size = 21113891, upload-time = "2025-01-16T21:34:00.277Z" }, + { url = "https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1d47bebd83d2c5fc770720d211855f208af8a596c82d17730aa51e815cdee6dc", size = 25632706, upload-time = "2025-01-16T21:33:53.475Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2d/43c8522a2038e9d0e7dbdf3a61195ecc31ca576fb1527a528c877e87d973/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7e46fcec401dd990405049d2e2f475e2b397779df2519b544b8aab515195282", size = 29498237, upload-time = "2025-01-16T21:34:13.726Z" }, + { url = "https://files.pythonhosted.org/packages/a0/13/59da54728351883c3c1d9fca1710ab8eee82c7beba585df8f25ca925f08f/imageio_ffmpeg-0.6.0-py3-none-win32.whl", hash = "sha256:196faa79366b4a82f95c0f4053191d2013f4714a715780f0ad2a68ff37483cc2", size = 19652251, upload-time = "2025-01-16T21:34:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a", size = 31246824, upload-time = "2025-01-16T21:34:28.6Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +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.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/d8/b959609e44012a42b1f3e5ba98ea3b33c7e41e6d4b77cd8f00fd19b1d3ad/jiter-0.16.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fc4f8def331036a7b8e981b4347ebe409981edbc8308a5ea842b8c3614fa6c", size = 310082, upload-time = "2026-06-29T13:02:31.356Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3d/4d7f5667ea0e0548534ba880b84bb3d12924fd133aa83ad6c6c80fca3d76/jiter-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a71d0d2014c3275043e1170bf3d4e771493cb0dcf07be54c567155f4d8ee64b", size = 315643, upload-time = "2026-06-29T13:02:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/9b/83/bed2dcb5c9f3e1ccfcbc67dda48265fe7d5ad0c9cadda5fe95f6e3b87f94/jiter-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:741eed508c233a76313a1c7b001f8f21b82f14327e9196ae8bd29a2cc164ae84", size = 341363, upload-time = "2026-06-29T13:02:34.853Z" }, + { url = "https://files.pythonhosted.org/packages/f4/2f/6bb3c3dda668ebc0445689c81a2b0f26a82b10843d67ed9c9b2c3edc177f/jiter-0.16.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fb7bc819187b56dc48aa5c833aaf92257da8e07efdb9306156667bd2eeb491c", size = 365483, upload-time = "2026-06-29T13:02:36.295Z" }, + { url = "https://files.pythonhosted.org/packages/92/35/8a045ccb39164e70dcdae696413b661771f148b68b12b175c3a04d901937/jiter-0.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9610fd25ebccb43fca584136f5c2fbb26802447eccd430dfdbab95a0fd5126", size = 461219, upload-time = "2026-06-29T13:02:38.116Z" }, + { url = "https://files.pythonhosted.org/packages/e7/99/22292dbbf0ed0c610cfe5ddc7f3bd67237a412f121318f865196e62a07bd/jiter-0.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a1d68ff7ca1d3b5dee20a97a3decda7d5f15003823bf6d140c81f8561d3bc5c", size = 374905, upload-time = "2026-06-29T13:02:40.357Z" }, + { url = "https://files.pythonhosted.org/packages/29/ac/2f55ccb1f0eeafa6d89d24caf52f6f0944a59290ee199e9ade62177dca42/jiter-0.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb08c276dd02dac3a284acdd02cacc630d2e3cd6572a4b85519f35cbd133c3de", size = 348320, upload-time = "2026-06-29T13:02:41.923Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/7d88b9174c40064fabc07c84a9b62e6b10f5644562ec0e0a29392edbe978/jiter-0.16.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:8fc4d94713c4697347e38faf7d6ef91547c142219bdcfc7220c4870879974244", size = 356519, upload-time = "2026-06-29T13:02:43.436Z" }, + { url = "https://files.pythonhosted.org/packages/27/57/c4a33aeef513a9d5e26e31534e0bcc752d6ea0e54c94ddb7b68bade669c2/jiter-0.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0f05e229edb29e68cdd0ccb83cea13b64263416120cf943767a6fd72e6787f", size = 394204, upload-time = "2026-06-29T13:02:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/c6c23e76ebb3766b111bc399437bbc9f870a76e2a92e10b2a5f561d57372/jiter-0.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2c842cbf374a8daf50b2c04212995bee34ca2ac2cdc29a901b4cdb072c9c4131", size = 521477, upload-time = "2026-06-29T13:02:46.724Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d3/0001c8c0c5976af2625bb1cfb1895e8ec693b6589fe4574b8e6fc2c85501/jiter-0.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ed466aee31294d7cdcd4d37dfe5c42c97bc29d9a5f00eacf24504358309cb9b", size = 552187, upload-time = "2026-06-29T13:02:48.144Z" }, + { url = "https://files.pythonhosted.org/packages/f6/76/311b718e07e85740e48619c0632b36f7e0b8d113984499e436452ed13a9a/jiter-0.16.0-cp310-cp310-win32.whl", hash = "sha256:b42e9ff5376819c053da25809a8d4b6fa6e473b4856ebe42e298ac958be3d7f9", size = 206513, upload-time = "2026-06-29T13:02:49.515Z" }, + { url = "https://files.pythonhosted.org/packages/db/7f/ac680eeb0777dc0eb7dc824800ba27880d7f6bc712e362d34ad8ee559f36/jiter-0.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:10438939205546132189c8e74a2d536a707841f3a25cd7c74ee91fe503407a26", size = 199505, upload-time = "2026-06-29T13:02:50.829Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3f/fae6cc967d120ec89e31c5418a51176d8278b3087fbb384a9176754f353c/jiter-0.16.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3", size = 309289, upload-time = "2026-06-29T13:02:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/97c6c3562c077f6247d6e6ce5c82562500b6316c0d928e97e106b7a1321a/jiter-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e", size = 315181, upload-time = "2026-06-29T13:02:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/7b/89/d8d073f8aa2667e46c6c0873f86fe4a512bba4293cc730f626a076211a62/jiter-0.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037", size = 340939, upload-time = "2026-06-29T13:02:55.412Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/db4fda3ed73fb864139305e935e5b8b38a5a24692a5a9dd356c22f1b9c8d/jiter-0.16.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e", size = 364932, upload-time = "2026-06-29T13:02:57.28Z" }, + { url = "https://files.pythonhosted.org/packages/a2/74/52b5e86241057f52ddd7c9a580f90effb51f9d06239f6fc612279b91a838/jiter-0.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b", size = 461132, upload-time = "2026-06-29T13:02:58.994Z" }, + { url = "https://files.pythonhosted.org/packages/a9/87/544a700f7447c1f31c5d7833821a4daa5683165c2d5a094fbf5b5800c3dc/jiter-0.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c", size = 374857, upload-time = "2026-06-29T13:03:00.455Z" }, + { url = "https://files.pythonhosted.org/packages/40/cd/0fcc3f7d39183674d5bfa9ec640faaeb506c60be7c8f94625dfba366e37c/jiter-0.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee", size = 347053, upload-time = "2026-06-29T13:03:02.045Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ae/c7e64e7932ad597fa395b61440b249ada6366716e25c6e08dd2afbd021e6/jiter-0.16.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03", size = 356153, upload-time = "2026-06-29T13:03:03.706Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/1c719044f14da814e1a060191ab19b96f3e99207bc5b4bfc6d6be34b3f80/jiter-0.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea", size = 393956, upload-time = "2026-06-29T13:03:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/3b/dc/7b2f303a2847207e265503853a2d964a55354cffd62a5f2936c155486798/jiter-0.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91", size = 521081, upload-time = "2026-06-29T13:03:06.886Z" }, + { url = "https://files.pythonhosted.org/packages/c2/5f/501cf6e1e09caeb420195179ffc6f62aca603f1220ec53fd80d0d70b3e56/jiter-0.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3", size = 552085, upload-time = "2026-06-29T13:03:08.339Z" }, + { url = "https://files.pythonhosted.org/packages/79/54/aa5be86520113b79455c3877f3d1f07a348098df4083ba3688e9537e52dd/jiter-0.16.0-cp311-cp311-win32.whl", hash = "sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7", size = 206755, upload-time = "2026-06-29T13:03:09.653Z" }, + { url = "https://files.pythonhosted.org/packages/64/ec/2feb893eb330bd69b413866f4d5daada33c3962f1c6f270c91ca2d87fdf9/jiter-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1", size = 199155, upload-time = "2026-06-29T13:03:10.979Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9c/ca040d94415048a3666fc237774df8151c96f8d2b661cbe3b184acc95876/jiter-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056", size = 194403, upload-time = "2026-06-29T13:03:12.341Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/52ace16ed031354f0539749a49e4bf33797d82bea5137910835fa4b09793/jiter-0.16.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a", size = 306943, upload-time = "2026-06-29T13:03:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/94/2e/34957c2c1b661c252ba9bcc60ae0bddc27e0f7202c6073326a13c5390eec/jiter-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9", size = 307779, upload-time = "2026-06-29T13:03:15.418Z" }, + { url = "https://files.pythonhosted.org/packages/88/6c/59bd309cab4460c54cf1079f3eb7fe7af6a4c895c5c957a53378693bad2b/jiter-0.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72", size = 335826, upload-time = "2026-06-29T13:03:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8c/f5ef7b65f0df47afa16596969defb281ebb86e96df346d62be6fd853d620/jiter-0.16.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e", size = 362573, upload-time = "2026-06-29T13:03:18.781Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0b/ace4354da061ee38844a0c27dc2c21eecd27aea119e8da324bea987522d0/jiter-0.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290", size = 457979, upload-time = "2026-06-29T13:03:20.293Z" }, + { url = "https://files.pythonhosted.org/packages/55/40/c0253d3772eb9dcd8e6606ee9b2d53ec8e5b814589c47f140aa585f21eaa/jiter-0.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01", size = 372302, upload-time = "2026-06-29T13:03:21.739Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d2/4839422241aa12860ce597b20068727094ba0bc480723c74924ca5bad483/jiter-0.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48", size = 343805, upload-time = "2026-06-29T13:03:23.384Z" }, + { url = "https://files.pythonhosted.org/packages/e2/59/e196888a05befdda7dbe299b722d56f2f6eec65402bc34c0a3306d595feb/jiter-0.16.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9", size = 351107, upload-time = "2026-06-29T13:03:24.815Z" }, + { url = "https://files.pythonhosted.org/packages/ec/74/4cd9e0fca65232136400354b630fbfcd2de634e22ccbb96567725981b548/jiter-0.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5", size = 388441, upload-time = "2026-06-29T13:03:26.266Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8c/554691e48bc711299c0a293dd8a6179e24b2d66a54dc295421fcf64569c0/jiter-0.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730", size = 516354, upload-time = "2026-06-29T13:03:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/a4/cb/01e9d69dc2cc6759d4f91e230b34489c4fdb2518992650633f9e20bece89/jiter-0.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f", size = 547880, upload-time = "2026-06-29T13:03:29.534Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/2953195f1c6ad00f49fa67e13df7e60acb3dd4f387101bc15abccddd905e/jiter-0.16.0-cp312-cp312-win32.whl", hash = "sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274", size = 203473, upload-time = "2026-06-29T13:03:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/05/2909a8b10699a4d560f8c502b6b2c5f3991b682b1922c1eedda242b225bd/jiter-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7", size = 196905, upload-time = "2026-06-29T13:03:32.472Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a9/6b82bb1c8d7790d602489b967b982a909e5d092875a6c2ade96444c8dfc5/jiter-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331", size = 190618, upload-time = "2026-06-29T13:03:34.672Z" }, + { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203, upload-time = "2026-06-29T13:03:36.243Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489, upload-time = "2026-06-29T13:03:37.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453, upload-time = "2026-06-29T13:03:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625, upload-time = "2026-06-29T13:03:40.597Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958, upload-time = "2026-06-29T13:03:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017, upload-time = "2026-06-29T13:03:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320, upload-time = "2026-06-29T13:03:45.226Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520, upload-time = "2026-06-29T13:03:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550, upload-time = "2026-06-29T13:03:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424, upload-time = "2026-06-29T13:03:49.881Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981, upload-time = "2026-06-29T13:03:51.363Z" }, + { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853, upload-time = "2026-06-29T13:03:53.015Z" }, + { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160, upload-time = "2026-06-29T13:03:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862, upload-time = "2026-06-29T13:03:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/06/d3/8e278946d43eeca2585b4dd0834a887cd71136329b837f3a16ed86a8b4b0/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad", size = 304518, upload-time = "2026-06-29T13:05:00.172Z" }, + { url = "https://files.pythonhosted.org/packages/72/43/28d4ef495028bf0506a413d4db3f4eb3e7288a382e0f065f306a17bbeb5e/jiter-0.16.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f", size = 310207, upload-time = "2026-06-29T13:05:02.123Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ca/c366b1012da1d640de975d9683acd44e4d150d9068845d0ca2610435253f/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5", size = 342771, upload-time = "2026-06-29T13:05:03.55Z" }, + { url = "https://files.pythonhosted.org/packages/16/52/50cc4056fc1ae02e7154704e7ecc89df0afb8300222cfe8a52d3f67e4730/jiter-0.16.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85", size = 346468, upload-time = "2026-06-29T13:05:05.452Z" }, + { url = "https://files.pythonhosted.org/packages/98/ab/664fd8c4be028b2bedd3d2ff08769c4ede23d0dbc87a77c62384a0515b5d/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127", size = 303106, upload-time = "2026-06-29T13:05:07.118Z" }, + { url = "https://files.pythonhosted.org/packages/1a/07/421f1d5b65493a76e16027b848aba6a7d28073ae75944fa4289cc914d39f/jiter-0.16.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3", size = 304658, upload-time = "2026-06-29T13:05:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/0a/db/bba1155f01a01c3c37a89425d571da751bbedf5c54247b831a04cb971798/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702", size = 339719, upload-time = "2026-06-29T13:05:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/78/f7/18a1afcd64f35314b68c1f23afcd9994d0bc13e65cc77517afff4e83986d/jiter-0.16.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2", size = 343885, upload-time = "2026-06-29T13:05:12.087Z" }, +] + +[[package]] +name = "jsmin" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/73/e01e4c5e11ad0494f4407a3f623ad4d87714909f50b17a06ed121034ff6e/jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc", size = 13925, upload-time = "2022-01-16T20:35:59.13Z" } + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-api-autonav" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "mkdocstrings-python" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/b0/20960ee733a419a349877d09712d02e8ec2bda031672e5f0d0a15fc020b3/mkdocs_api_autonav-0.4.0.tar.gz", hash = "sha256:3527b0e5cf1b682bd374a3ce699ac12d6288f5fcaf93877f34a6b14c79740637", size = 17987, upload-time = "2025-09-09T12:42:02.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/37/e1413281aec69994a0ecb8baaff523b7b7da3119ae7d495b7dc659e630b0/mkdocs_api_autonav-0.4.0-py3-none-any.whl", hash = "sha256:87474e7919664fca75648a05e79de238dd5b39a0f711910d3638626b016acfe3", size = 13130, upload-time = "2025-09-09T12:42:00.731Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, +] + +[[package]] +name = "mkdocs-awesome-nav" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "natsort" }, + { name = "pydantic" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/96/d581c9ceae7d8c08c7456d1d2befbf3e9c7a52a6b03aebbe6dc1d46ffdac/mkdocs_awesome_nav-3.3.0.tar.gz", hash = "sha256:330edf4129995a7be8a9751edd8739d312fddf591c6cbd95ec80f12f4bfe8578", size = 9112, upload-time = "2025-12-02T20:10:46.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/98/dc154a7f0bb25827d988dc31992917606d1ca9f5349077db045bb5cd9dbc/mkdocs_awesome_nav-3.3.0-py3-none-any.whl", hash = "sha256:bf80f14bff9571df79129bbdf103b4198589ee7616f8a29720ae2e7ac2455930", size = 12891, upload-time = "2025-12-02T20:10:45.054Z" }, +] + +[[package]] +name = "mkdocs-gen-files" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "properdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/43/428f312149c161cae557eecd35f3c4a82b867998b1d47fb29fdfe927be26/mkdocs_gen_files-0.6.1.tar.gz", hash = "sha256:57d7ff2229e23d077e46d14a33db6d37c8823f6ce1a503c874c1764a71679763", size = 8746, upload-time = "2026-03-16T23:26:09.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/1b/3075eb67fe66e19db059f0a25744c4e56978a309603a20e1d3353d545b5e/mkdocs_gen_files-0.6.1-py3-none-any.whl", hash = "sha256:b3182bfc6219e35b8d26658cb988368659d5d023aac30c2a819247558fc12189", size = 8282, upload-time = "2026-03-16T23:26:08.292Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocs-git-revision-date-localized-plugin" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "gitpython" }, + { name = "mkdocs" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/99/8067eb7d1652767ee8e5474010647dd5a8e464e0ca8c783b5cac135a2043/mkdocs_git_revision_date_localized_plugin-1.5.3.tar.gz", hash = "sha256:873444b54cab4d47c69bd6e85da05ef5fbe81fee27e64508114c46a0e4f81e37", size = 451961, upload-time = "2026-06-01T08:32:09.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/d0/cbe85158dc091219fd5134bf6d724d30b1f2005ee1d0dabaaa41416bee78/mkdocs_git_revision_date_localized_plugin-1.5.3-py3-none-any.whl", hash = "sha256:cd96e432de6a7e59b31c7041574b22f84179c8636835419ff458877ecfaaaf05", size = 26156, upload-time = "2026-06-01T08:32:07.765Z" }, +] + +[[package]] +name = "mkdocs-glightbox" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "selectolax" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/26/c793459622da8e31f954c6f5fb51e8f098143fdfc147b1e3c25bf686f4aa/mkdocs_glightbox-0.5.2.tar.gz", hash = "sha256:c7622799347c32310878e01ccf14f70648445561010911c80590cec0353370ac", size = 510586, upload-time = "2025-10-23T14:55:18.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/ca/03624e017e5ee2d7ce8a08d89f81c1e535eb3c30d7b2dc4a435ea3fbbeae/mkdocs_glightbox-0.5.2-py3-none-any.whl", hash = "sha256:23a431ea802b60b1030c73323db2eed6ba859df1a0822ce575afa43e0ea3f47e", size = 26458, upload-time = "2025-10-23T14:55:17.43Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/29/6d2bcf41ae40802c4beda2432396fff97b8456fb496371d1bc7aad6512ec/mkdocs_material-9.7.6.tar.gz", hash = "sha256:00bdde50574f776d328b1862fe65daeaf581ec309bd150f7bff345a098c64a69", size = 4097959, upload-time = "2026-03-19T15:41:58.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl", hash = "sha256:71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba", size = 9305470, upload-time = "2026-03-19T15:41:55.217Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocs-minify-plugin" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "csscompressor" }, + { name = "htmlmin2" }, + { name = "jsmin" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/67/fe4b77e7a8ae7628392e28b14122588beaf6078b53eb91c7ed000fd158ac/mkdocs-minify-plugin-0.8.0.tar.gz", hash = "sha256:bc11b78b8120d79e817308e2b11539d790d21445eb63df831e393f76e52e753d", size = 8366, upload-time = "2024-01-29T16:11:32.982Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/cd/2e8d0d92421916e2ea4ff97f10a544a9bd5588eb747556701c983581df13/mkdocs_minify_plugin-0.8.0-py3-none-any.whl", hash = "sha256:5fba1a3f7bd9a2142c9954a6559a57e946587b21f133165ece30ea145c66aee6", size = 6723, upload-time = "2024-01-29T16:11:31.851Z" }, +] + +[[package]] +name = "mkdocs-static-i18n" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/f9/51e2ffda9c7210bc35a24f3717b08c052cd4b728dfa87f901c00d8005259/mkdocs_static_i18n-1.3.1.tar.gz", hash = "sha256:a6125ea7db6cc1a900d76a967f262535af09831160a93c56d7f0d522a79b5faf", size = 1371325, upload-time = "2026-02-20T10:42:41.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/0b/43ff4afb6b438d47718b1959a22075ed95d8460d8c47381878b37a40de63/mkdocs_static_i18n-1.3.1-py3-none-any.whl", hash = "sha256:4036e24795a150c9c4d4b001ed24a43aec01335f76188dbe5a5d8fb4a27eba65", size = 21853, upload-time = "2026-02-20T10:42:40.551Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/71/f85bdf13355073ae15a7375f09879375a830553552e58c1c4b7e0bbc5c8b/mkdocstrings-1.0.6.tar.gz", hash = "sha256:a0b8c2bdd29a6416c80d717aa369bbf7831946bd9f23c2a66db1b1dbe7693dbd", size = 100649, upload-time = "2026-07-11T19:38:05.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/5b/4c1902e8bdd5c4db63284e9d101dece4038d4025d6d88850ffe0a1578980/mkdocstrings-1.0.6-py3-none-any.whl", hash = "sha256:2703708697487d1b6d6d7b412e176fa436edf120c1bf81dc9e126b12d00893c7", size = 35787, upload-time = "2026-07-11T19:38:04.417Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffelib" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/b6/e858701499d57eee8b3fd8e78168083956c6683ddbe727b46758b19e1119/mkdocstrings_python-2.0.5.tar.gz", hash = "sha256:3a4d92556ad39637e88af94a5374213af9a8e3040c3824ceaed04b486c017594", size = 199578, upload-time = "2026-06-19T10:41:08.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/fc/10ab7e80650a9c9e8f4f1105f8c8e73567f88ed0c06ada589ab81d38687c/mkdocstrings_python-2.0.5-py3-none-any.whl", hash = "sha256:30c837bbff016549f659fcba6539ac351303f0fd7e713c89a040611072236e9d", size = 104951, upload-time = "2026-06-19T10:41:07.378Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "msgpack" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/16/f70100614b69feb3ade7285f08c9c52d6cda0a5c03f3f5e2facd63acb211/msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c", size = 82926, upload-time = "2026-06-18T16:12:31.531Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3c/08ecd5cdfe4e2de43aec79062028ad0f7b2d9b1fea5430068c198ba570da/msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895", size = 82730, upload-time = "2026-06-18T16:12:32.894Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/a70c9cb1a04ecc134005149367dcfe35d167284e8f65035a1e4156ad17b5/msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203", size = 400729, upload-time = "2026-06-18T16:12:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7f/5ce020168cf0439041526e95aa068c722c016aee21624e331aeabeee2e8e/msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73", size = 407625, upload-time = "2026-06-18T16:12:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/fb7668ce0386819303047057aef6fc1da73b584291d9cff82b821744e2ef/msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833", size = 377891, upload-time = "2026-06-18T16:12:36.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dc/9ebe654a73c3aed2e40aa6b52e3c2a02b5f53ef0085fa235a45d5b367f87/msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8", size = 391987, upload-time = "2026-06-18T16:12:37.839Z" }, + { url = "https://files.pythonhosted.org/packages/42/eb/b67cf64218a2fa25e1c671fe1d3dbb06cbeb973e71bc4b822da079862d0b/msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7", size = 374603, upload-time = "2026-06-18T16:12:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2e/9ee200cde32fd1a0101b4006202fde554c1860adfb9bf7bff31ea4c08df8/msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce", size = 405121, upload-time = "2026-06-18T16:12:40.524Z" }, + { url = "https://files.pythonhosted.org/packages/43/b6/f10117be7ca7a51e8feed699a907b8e663a8cd66e115ae6b4fb30cc7945c/msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74", size = 64088, upload-time = "2026-06-18T16:12:41.762Z" }, + { url = "https://files.pythonhosted.org/packages/ba/93/89976c696fb0224662239d952c47b4d1661b34d79a332ef5584facaa8579/msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb", size = 70113, upload-time = "2026-06-18T16:12:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" }, + { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" }, + { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" }, + { url = "https://files.pythonhosted.org/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" }, + { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" }, + { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a", size = 419989, upload-time = "2026-06-18T16:13:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/85039b7b0eb168aaad7383a23c97e291a11f08351cb45a606ce865e4e3f1/msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6", size = 377544, upload-time = "2026-06-18T16:13:03.637Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/35963899493b32030c85fc513b723ae66144ac70c11ebc52e889e16e3d99/msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a", size = 400842, upload-time = "2026-06-18T16:13:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/8e2ac970c8f99264cd9997d1c73df5466bc19da3301d7dc5500862a9b089/msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1", size = 374108, upload-time = "2026-06-18T16:13:06.232Z" }, + { url = "https://files.pythonhosted.org/packages/17/dd/fa8bd265110dfa51c20cb529f9e6d240a16fafe7e645004c6af2d01353ba/msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64", size = 414939, upload-time = "2026-06-18T16:13:07.478Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8377a5ad8953fc0437c70cc98d9ae29f27fe5ac5109fbec0812085865735/msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac", size = 64504, upload-time = "2026-06-18T16:13:08.822Z" }, + { url = "https://files.pythonhosted.org/packages/57/7f/ce1e377df7e62461fefd9eb23bfb93a4a523f40a517b377b8f844d836828/msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24", size = 71421, upload-time = "2026-06-18T16:13:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/8f/32/ebfe84c9929f08f188d56c7a2fd913406a9ddad76a634697c1c43b8112e6/msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07", size = 64775, upload-time = "2026-06-18T16:13:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/dcddcab6f6c20ecb387ca5e980371cdb3f87ff69aeca388be97eebc4c074/msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064", size = 83151, upload-time = "2026-06-18T16:13:12.173Z" }, + { url = "https://files.pythonhosted.org/packages/64/71/fbcfa83a1d6a9c6091942d1cfd070962244664b87427a9a49a6897b1b219/msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056", size = 82351, upload-time = "2026-06-18T16:13:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/e3/10/ddf7b06db879e8792d13934ddda09ff20bd2a583fd84c9b59aae9b0e650b/msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc", size = 407518, upload-time = "2026-06-18T16:13:14.233Z" }, + { url = "https://files.pythonhosted.org/packages/79/d3/36a46a8ed992b781acbc05928bd5bee3c810cb0c3563bf81a7b0c04a1a76/msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d", size = 416405, upload-time = "2026-06-18T16:13:15.435Z" }, + { url = "https://files.pythonhosted.org/packages/f9/84/e8e9598b557c0ba6ddae901a73780a4c75ac667dddf59414b1e56a42fb34/msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155", size = 376257, upload-time = "2026-06-18T16:13:17.022Z" }, + { url = "https://files.pythonhosted.org/packages/40/16/738fe6d875ad7e2a9429c165322a4ec088f4f273cdfae63d96a89c467961/msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402", size = 397469, upload-time = "2026-06-18T16:13:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/ca/be/6d5952df75a7f24f35833af764c3a6860780364cb3a0030beb8099e1b2b4/msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c", size = 372802, upload-time = "2026-06-18T16:13:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/e1/39/e2ef7dbf0473bcb8dc7c50bf782a892d67414877b63e47fc88eb189ef5e6/msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6", size = 411273, upload-time = "2026-06-18T16:13:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c5/133f4512a56e983a93445c836c9d94d88f3bc2e0980ff4b9e577bd8416ce/msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707", size = 64471, upload-time = "2026-06-18T16:13:22.293Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/577e10b055096a7dd40732358cabaf7180a20c79ed1dcdbb618e4b9deac7/msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9", size = 71274, upload-time = "2026-06-18T16:13:23.455Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ee/0c0048e7cfbef23c6a94791b8959ab28155232e7956de8a305b5ff588f05/msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a", size = 64795, upload-time = "2026-06-18T16:13:24.687Z" }, +] + +[[package]] +name = "natsort" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +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 = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +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.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +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/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://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/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.3.33" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "openai" +version = "2.45.0" +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/78/60/d4219875289b11d2c2f7da93c36283da224a2e55865ed865ab64e0ce9217/openai-2.45.0.tar.gz", hash = "sha256:10d34ca9c5643bce775852fddbfc172505cb1d4de1ccd101696c3ecff358765d", size = 1109653, upload-time = "2026-07-09T18:02:44.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/b0/2291689e3ec4723fbf5bbf3b54afcd7b160f9ddc98ca7aedfd0132af5677/openai-2.45.0-py3-none-any.whl", hash = "sha256:5df105f5f8c9b711fcb9d06d2d3888cebc82506db216484c14a4e53cdf651777", size = 1629470, upload-time = "2026-07-09T18:02:42.21Z" }, +] + +[[package]] +name = "opencv-python-headless" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/99/76b7c80252aa83c1af16393454aafd125a0287101afe8deb0a6821af0e30/opencv_python_headless-5.0.0.93.tar.gz", hash = "sha256:b82f9831daab90b725c7c1ee1b36cb5732c367096ac76d119e64e14eb70d5f3c", size = 81817738, upload-time = "2026-07-02T07:01:06.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7c/8c8097891c509d98cd128493835c95631c80be6a8f37ed9d25716c2e16f1/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:030ca5e0837a2963ab36ef896baa9767eb8d2b83353fb28af5a521e40dd8756f", size = 48322581, upload-time = "2026-07-02T05:50:34.207Z" }, + { url = "https://files.pythonhosted.org/packages/90/8c/eab2ad388c3cbab2a350c10c2ef19ce6bd099240afc31789032c996bab52/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:1e55af3abfb462eeeabe5c775f12bdb36216d8a93a3583d69e6bd6e1d6ba7d00", size = 34782894, upload-time = "2026-07-02T05:51:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/ec/78/afca939f40ffe2b2380bfa86f812b2f7d4acc5a27b27dc41b49cad7ce7b4/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10818d91510e05c04568ae12b5cd120779c70c01bf897b001a6221fe430df80f", size = 36521085, upload-time = "2026-07-02T06:55:24.429Z" }, + { url = "https://files.pythonhosted.org/packages/2b/97/8170e9819764c47e436c130d3ff6cfb73b58f923eae9d3a03d8982b04aec/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09a872a157c1376ab922a69bbf22f9a95bcc7b658a9d8b436a60212b02b2eeb4", size = 56563598, upload-time = "2026-07-02T06:55:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/1a28a7101e31801042b3098871a74b76c61581d328ef40774ff4edb53a56/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:840bd717c21e5c11cadadc022a823315ea417f961213d06b4df010e019eb16f4", size = 39648433, upload-time = "2026-07-02T06:56:04.255Z" }, + { url = "https://files.pythonhosted.org/packages/9b/21/f6ef335f6e65724aa78b8d792b48d40a48c381715f1e62f5a5049e09d07e/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ed709fdf9aa0bd1f2ed8549e71d19449b03a675bb581eb292285f6861953be37", size = 61204038, upload-time = "2026-07-02T06:56:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8f/b8756467ea991449a293797f6b3fa80fcfdd29598a0a60d1cd5715b96e61/opencv_python_headless-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:c6bcd96b185975ea240d22cfdb15a1f6d080cc95264cfbe2621f21bb144d89b9", size = 35411237, upload-time = "2026-07-02T05:50:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/b8/88/763b967f7efd7226b82c9fae16d560cba049b1f0c036647e65c610fd636e/opencv_python_headless-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:829717b6a95554f273e49e357cee3b3a2a26b6f4842fbc1bed2b45bdd8f87e0e", size = 43825962, upload-time = "2026-07-02T05:50:09.627Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, + { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, + { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, + { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, + { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +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 = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678, upload-time = "2024-10-08T16:09:37.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713, upload-time = "2024-10-08T16:09:35.726Z" }, +] + +[[package]] +name = "properdocs" +version = "1.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/29/f27a4e1eddf72ed3db6e47818fbafe6debbf09fd7051f9c1a007239b46ef/properdocs-1.6.7.tar.gz", hash = "sha256:adc7b16e562890af0e098a7e5b02e3a81c20894a87d6a28d345c9300de73c26e", size = 276141, upload-time = "2026-03-20T20:07:48.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/4d/fc923f5c85318ee8cc903566dc4e0ebe41b2dfc1d2ecf5546db232397ed6/properdocs-1.6.7-py3-none-any.whl", hash = "sha256:6fa0cfa2e01bf338f684892c8a506cf70ea88ae7f3479c933b6fa20168101cbd", size = 225406, upload-time = "2026-03-20T20:07:46.875Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, + { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, + { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, + { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, + { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, + { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, + { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, + { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, + { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, + { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, + { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, + { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, + { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, + { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, + { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, + { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, + { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, + { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, + { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, + { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, + { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, + { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, + { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, + { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, + { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, + { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, + { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, + { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, + { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, + { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + +[[package]] +name = "pyee" +version = "13.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pylibsrtp" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/a6/6e532bec974aaecbf9fe4e12538489fb1c28456e65088a50f305aeab9f89/pylibsrtp-1.0.0.tar.gz", hash = "sha256:b39dff075b263a8ded5377f2490c60d2af452c9f06c4d061c7a2b640612b34d4", size = 10858, upload-time = "2025-10-13T16:12:31.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/af/89e61a62fa3567f1b7883feb4d19e19564066c2fcd41c37e08d317b51881/pylibsrtp-1.0.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:822c30ea9e759b333dc1f56ceac778707c51546e97eb874de98d7d378c000122", size = 1865017, upload-time = "2025-10-13T16:12:15.62Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0e/8d215484a9877adcf2459a8b28165fc89668b034565277fd55d666edd247/pylibsrtp-1.0.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:aaad74e5c8cbc1c32056c3767fea494c1e62b3aea2c908eda2a1051389fdad76", size = 2182739, upload-time = "2025-10-13T16:12:17.121Z" }, + { url = "https://files.pythonhosted.org/packages/57/3f/76a841978877ae13eac0d4af412c13bbd5d83b3df2c1f5f2175f2e0f68e5/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9209b86e662ebbd17c8a9e8549ba57eca92a3e87fb5ba8c0e27b8c43cd08a767", size = 2732922, upload-time = "2025-10-13T16:12:18.348Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/cf5d2a98a66fdfe258f6b036cda570f704a644fa861d7883a34bc359501e/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:293c9f2ac21a2bd689c477603a1aa235d85cf252160e6715f0101e42a43cbedc", size = 2434534, upload-time = "2025-10-13T16:12:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/bd/08/a3f6e86c04562f7dce6717cd2206a0f84ca85c5e38121d998e0e330194c3/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_28_i686.whl", hash = "sha256:81fb8879c2e522021a7cbd3f4bda1b37c192e1af939dfda3ff95b4723b329663", size = 2345818, upload-time = "2025-10-13T16:12:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d5/130c2b5b4b51df5631684069c6f0a6761c59d096a33d21503ac207cf0e47/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4ddb562e443cf2e557ea2dfaeef0d7e6b90e96dd38eb079b4ab2c8e34a79f50b", size = 2774490, upload-time = "2025-10-13T16:12:22.659Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/715a453bfee3bea92a243888ad359094a7727cc6d393f21281320fe7798c/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:f02e616c9dfab2b03b32d8cc7b748f9d91814c0211086f987629a60f05f6e2cc", size = 2372603, upload-time = "2025-10-13T16:12:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/e3/56/52fa74294254e1f53a4ff170ee2006e57886cf4bb3db46a02b4f09e1d99f/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c134fa09e7b80a5b7fed626230c5bc257fd771bd6978e754343e7a61d96bc7e6", size = 2451269, upload-time = "2025-10-13T16:12:25.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/2e9b34f484cbdd3bac999bf1f48b696d7389433e900639089e8fc4e0da0d/pylibsrtp-1.0.0-cp310-abi3-win32.whl", hash = "sha256:bae377c3b402b17b9bbfbfe2534c2edba17aa13bea4c64ce440caacbe0858b55", size = 1247503, upload-time = "2025-10-13T16:12:27.39Z" }, + { url = "https://files.pythonhosted.org/packages/c3/70/43db21af194580aba2d9a6d4c7bd8c1a6e887fa52cd810b88f89096ecad2/pylibsrtp-1.0.0-cp310-abi3-win_amd64.whl", hash = "sha256:8d6527c4a78a39a8d397f8862a8b7cdad4701ee866faf9de4ab8c70be61fd34d", size = 1601659, upload-time = "2025-10-13T16:12:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ec/6e02b2561d056ea5b33046e3cad21238e6a9097b97d6ccc0fbe52b50c858/pylibsrtp-1.0.0-cp310-abi3-win_arm64.whl", hash = "sha256:2696bdb2180d53ac55d0eb7b58048a2aa30cd4836dd2ca683669889137a94d2a", size = 1159246, upload-time = "2025-10-13T16:12:30.285Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "11.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/a9/5f0c535ba3b08fe09270c16808e053a968868242ecbd5676d4e3a488bf28/pymdown_extensions-11.0.1.tar.gz", hash = "sha256:dd2905ae6fc5b75582fafb139a1266ffc754705efa902aa50067fa7ff4f94ec0", size = 857113, upload-time = "2026-07-02T17:59:22.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/54/da572c98c0b77626a91b5d3b89f0231d8bff5125c225420908632f8b342d/pymdown_extensions-11.0.1-py3-none-any.whl", hash = "sha256:db3943a62bab7e03af1364f0c4083e64b91fb097675a4b6cceccfbe9a77e5eb2", size = 269455, upload-time = "2026-07-02T17:59:21.271Z" }, +] + +[[package]] +name = "pyopenssl" +version = "26.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/b7/da07bae88f5a9506b4def6f2f4903cf4c3b8831e560dba8fa18ca08f758f/pyopenssl-26.3.0.tar.gz", hash = "sha256:589de7fae1c9ea670d18422ed00fc04da787bbde8e1454aea872aa57b49ad341", size = 182024, upload-time = "2026-06-12T20:28:07.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/18/1dd71c9b43192ab83f1d531ad6002dc81108ac36c475f79fb7a295abe2f4/pyopenssl-26.3.0-py3-none-any.whl", hash = "sha256:46367f8f66b92271e6d218da9c87607e1ef5a0bc5c8dea5bb3db82f395c385a3", size = 56008, upload-time = "2026-06-12T20:28:05.999Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/81/58c70036dffeccb7fe7d79d6260c69f7a28272bbd3909c29a01ea9422744/python_discovery-1.4.4.tar.gz", hash = "sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3", size = 72212, upload-time = "2026-07-08T23:06:50.691Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/ae/84bc0d2440c95772272bb6f4b3d09ccf08b2898fce89b3d4f969a9fc74e9/python_discovery-1.4.4-py3-none-any.whl", hash = "sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe", size = 34181, upload-time = "2026-07-08T23:06:49.402Z" }, +] + +[[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" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "qwen-vl-utils" +version = "0.0.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "av" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/b1/ad4fc2260a3badd278b38d642f3b987412f1f6682f0ef2b31b0572d5caa8/qwen_vl_utils-0.0.14.tar.gz", hash = "sha256:9c7cad5ae803b3a10f8bb7194deb12aeacdd032f92f4224e880c73587a7346ad", size = 8453, upload-time = "2025-09-23T09:38:57.532Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/43/80f67e0336cb2fc725f8e06f7fe35c1d0fe946f4d2b8b2175e797e07349e/qwen_vl_utils-0.0.14-py3-none-any.whl", hash = "sha256:5e28657bfd031e56bd447c5901b58ddfc3835285ed100f4c56580e0ade054e96", size = 8120, upload-time = "2025-09-23T09:38:56.297Z" }, +] + +[[package]] +name = "ray" +version = "2.56.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "jsonschema" }, + { name = "msgpack" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "requests" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c0/d6ffbe8ae2e2e10ea07aa08ea2dd35597239f0b3faaa29b5d7bbc405ab32/ray-2.56.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f34b2345a47ad144292c1b34eeba2ed8d556078f7bd118d1adf2090d5199c843", size = 66362009, upload-time = "2026-06-29T20:50:14.442Z" }, + { url = "https://files.pythonhosted.org/packages/f1/15/037f9eac9d3d43079611ca6af58c34b2a4271b9a94d57679356981b7b8e9/ray-2.56.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:15ea7ac36bfa3961c1eb2b2a099ed7dcf892f001f462920b6ec379ccafb038b0", size = 73212371, upload-time = "2026-06-29T20:50:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a3/2d0fbe2d8808ae8837885051f149cefdca3e869eaef4c65618173fa63db5/ray-2.56.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:58be75df2d4a6a85b5e514e4d3261760fe21cc09ba974ce22cc08a8e4e07c449", size = 74067715, upload-time = "2026-06-29T20:50:26.661Z" }, + { url = "https://files.pythonhosted.org/packages/9d/05/70492aed281a235421ffd116b2eb74f127f387580bb63a7f8ce5c901169e/ray-2.56.0-cp310-cp310-win_amd64.whl", hash = "sha256:b837c5a905647c9b6a4f55061c2782437da24009e4eb4781db1c9c4badc84d0b", size = 28394809, upload-time = "2026-06-29T20:50:31.035Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f2/2cbbbef7a67adc6555b141c7a67557bd7ce21cfb85b85772627c6a582e97/ray-2.56.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:a9ad4e26941eb2f8dbd494ad07f9f2227143164c6114132b26b23ad4f20b1c6f", size = 66354212, upload-time = "2026-06-29T20:50:37.148Z" }, + { url = "https://files.pythonhosted.org/packages/d6/69/09c8320519aa4d075bd6ecb1694c28d26b5b07a5b46050703d9ac2c5a9b6/ray-2.56.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:aea655831d25084cb343002a8e67a77b6aa552ddb776a65461d49f62884f096a", size = 73299499, upload-time = "2026-06-29T20:50:42.584Z" }, + { url = "https://files.pythonhosted.org/packages/18/d3/84df370c773a0b853abdba498a6258106cb928d452ff56eec7f86c2f80b4/ray-2.56.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:c5bf1a4384c0e2aa4420c95474b734d064cac354b0f00760be02d886afe96ca4", size = 74143858, upload-time = "2026-06-29T20:50:48.228Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/0b4c96879ded375d0b35441c6b3633ccbea7c7c5448335589811fe5f275c/ray-2.56.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e57781685bb4332edf8a7cfb1135ff53d50002b6a0504006e68365bcd26aa7c", size = 28390306, upload-time = "2026-06-29T20:50:53.841Z" }, + { url = "https://files.pythonhosted.org/packages/05/d2/83777f52c10e04654c162bfa093f4e34cf6decf7a0bb4311e2432bfa8ecb/ray-2.56.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:684a427c50745989e92a332343f0812c93b8506f71c768b95b1eefc113492699", size = 66344824, upload-time = "2026-06-29T20:50:58.367Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b2/d610ffe2878dae8ab903cfdded883a054c102b0104670bca7b34b662db51/ray-2.56.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:e1fd03c6ecc5fe4c31466569e41ce0a4faf26fb930798c9d1f1eb1f405a687c8", size = 73317367, upload-time = "2026-06-29T20:51:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/8d442116f41e6bebec418d89f1556c67c77d593b55cf59e716503e7a4a17/ray-2.56.0-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:78ef34a71383c1fcf335e531e0e590867857fce9069f06ed351be6ce7a58fc50", size = 74192607, upload-time = "2026-06-29T20:51:09.528Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9e/cac7454325c79eb32f36f233285d9179858c7073bc27ac91afb74854ff12/ray-2.56.0-cp312-cp312-win_amd64.whl", hash = "sha256:a8fc809dab6fc07cf05d45ea93a776c852c990512eb1fac0e3d15819fe6df10a", size = 28370978, upload-time = "2026-06-29T20:51:14.168Z" }, + { url = "https://files.pythonhosted.org/packages/65/86/29c9444c9f11f0bf3d4da75d3dc7a27c0fb86f8604beea6513968dc2df26/ray-2.56.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:992047f50473b5bfea74c8f528f999968e0b4bc735af23ad476a0f4e04741aea", size = 66289930, upload-time = "2026-06-29T20:51:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/59/59/3c4d533a92e99b12b9d2e8690e41d7ace269d77deecb0aef753df7924b9e/ray-2.56.0-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:a0e9cfe92c88ab74abca23923c15a592f49bc7617ffdda4190daca7785a7c4f6", size = 73252460, upload-time = "2026-06-29T20:51:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/59/ba/285d409253b332af91884daa4b0a3be3ce799682aa2c1e0f8dbbafaf1da4/ray-2.56.0-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:54d2725f8b65d9615c933fec5ec62e54a67b8f2e14286026fb014606253670ef", size = 74130685, upload-time = "2026-06-29T20:51:31.59Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +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.7.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/37/451aaddbf50922f34d744ad5ca919ae1fcfac112123885d9728f52a484b3/regex-2026.7.10.tar.gz", hash = "sha256:1050fedf0a8a92e843971120c2f57c3a99bea86c0dfa1d63a9fac053fe54b135", size = 416282, upload-time = "2026-07-10T19:49:46.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/69/62bb7d63f26698949c905cb7ebe29c7b0659e2a7f2a50c35cc29640b0852/regex-2026.7.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:799a369bdab91dcf0eb424ebd7aa9650897025ce22f729248d8f2c72002c4daa", size = 494652, upload-time = "2026-07-10T19:46:28.394Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f2/eed2ce38cc38def9c366d060ec739ff5f235a33647ceb73ae6be37306d39/regex-2026.7.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f0192e5f1cfc70e3cb35347135dd02e7497b3e7d83e378aa226d8b3e53a93f19", size = 295920, upload-time = "2026-07-10T19:46:30.342Z" }, + { url = "https://files.pythonhosted.org/packages/36/57/4d724eeb1c440d71ccd6400d33b62b911bb62ca05385fe1961556e628319/regex-2026.7.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:221f2771cb780186b94bbf125a151bbeb242fa1a971da6ad59d7b0370f19de9a", size = 290696, upload-time = "2026-07-10T19:46:31.953Z" }, + { url = "https://files.pythonhosted.org/packages/c7/9d/76c6779e424c64740d2a564b7ecb389a62c77b6294127d34f52008c91ea7/regex-2026.7.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2fb1f7a2deb4ca3ddebbae6b93905d21480a3b4e11de28d79d9fb0d316fcf8", size = 784833, upload-time = "2026-07-10T19:46:33.145Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/f777841d88f0c9d46699daf16f86a8086e077e506603be212de2c4584f85/regex-2026.7.10-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f98ef73a13791a387d5c841416ad7f52040ae5caf10bcf46fa12bd2b3d63745", size = 852182, upload-time = "2026-07-10T19:46:34.368Z" }, + { url = "https://files.pythonhosted.org/packages/d1/de/5ba208a0826117851f6c12af9ae7fae5838ccfde69b999b26c5fdc1cedc3/regex-2026.7.10-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9a094ed44a22f9da497453137c3118b531fd783866ab524b0b0fc146e7395e1d", size = 899571, upload-time = "2026-07-10T19:46:35.564Z" }, + { url = "https://files.pythonhosted.org/packages/7f/46/602b7b81d26a53113d3cec6dd845fd664d7b854b0ea45245bfdd6b9dc9ef/regex-2026.7.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53bbbd6c610489700f7110db1d85f3623924c3f7c760f987eca033867360788a", size = 794164, upload-time = "2026-07-10T19:46:36.972Z" }, + { url = "https://files.pythonhosted.org/packages/53/cc/c21ebc520c3e17d93e495eb2de2b1c5ae5f6780ccdb298b2d8ce1e940820/regex-2026.7.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:87b776cf2890e356e4ab104b9df846e169da3eb5b0f110975547091f4e51854e", size = 786304, upload-time = "2026-07-10T19:46:38.238Z" }, + { url = "https://files.pythonhosted.org/packages/65/d8/69ec8c062bbba8d4d153f9eb70ab43564f591035b81fc4ea7eb059fdf71c/regex-2026.7.10-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab39d2c967aae3b48a412bff9cdbe7cd7559cd1e277599aceaeada7bc82b7200", size = 769958, upload-time = "2026-07-10T19:46:39.541Z" }, + { url = "https://files.pythonhosted.org/packages/af/82/c56db326ac5f852865bd78d49a275757cc367e8114b13bc1015ed2491db1/regex-2026.7.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b56416091bfd7a429f958f69aaf6823c517be9a49cb5bf1daa3767ce8bf8095e", size = 775056, upload-time = "2026-07-10T19:46:40.808Z" }, + { url = "https://files.pythonhosted.org/packages/af/2a/1ba62462f679eb598d7325ff20798a264ddb9aa34a3f5d2ac388d54c6e4b/regex-2026.7.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:617e8f10472e34a8477931f978ff3a88d46ae2ba0e41927e580b933361f60948", size = 848857, upload-time = "2026-07-10T19:46:42.02Z" }, + { url = "https://files.pythonhosted.org/packages/84/5b/37bf2e2fe810540ca90bbfe457a2f61088721c95d442eee8bb1257165ad7/regex-2026.7.10-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:31fa17378b29519bfd0a1b8ba4e9c10cf0baf1cf4099b39b0689429e7dc2c795", size = 757747, upload-time = "2026-07-10T19:46:43.282Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c5/131dd41f73f766af6b08492073b5f28bc4f907b5571230c9f9e895e88302/regex-2026.7.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c363de7c0339d39341b6181839ed32509820b85ef506deafcf2e7e43baadab4", size = 837183, upload-time = "2026-07-10T19:46:44.91Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ee/00b9332c3c5d7460639f2c10fdc7197a64de6eaafff69478ec3332815335/regex-2026.7.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7c886a2fcbf14493ceaf9579394b33521730c161ebb8dad7db9c3e9fcab1a8", size = 782151, upload-time = "2026-07-10T19:46:46.385Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a7/31ce26ec6465c12e7136ea9efcc716f5c55cac616f7958c33f0bf171781e/regex-2026.7.10-cp310-cp310-win32.whl", hash = "sha256:b04583e8867136ae66353fa274f45121ab3ec3166dc45aaff3655a5db90d9f0e", size = 266770, upload-time = "2026-07-10T19:46:47.608Z" }, + { url = "https://files.pythonhosted.org/packages/71/00/22a554bb83203eef88a40ec2fe7cf9a6e5df8765d57f7a96ebe1af5d60c3/regex-2026.7.10-cp310-cp310-win_amd64.whl", hash = "sha256:e21e888a6b471b2bb1cdd4247e8d86632672232f29be583e7eafaa5f4634d34c", size = 277941, upload-time = "2026-07-10T19:46:48.915Z" }, + { url = "https://files.pythonhosted.org/packages/76/46/596a7084918ddf18cea6fb0cc047af1f00cec8790962e8f2edee9b6ec749/regex-2026.7.10-cp310-cp310-win_arm64.whl", hash = "sha256:081acf191b4d614d573a56cab69f948b6864daa5e3cc69f209ee92e26e454c2f", size = 276923, upload-time = "2026-07-10T19:46:50.829Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/bfd13770be1acd1c05506b93fc6be15c759d6417595d1ba334d355efbf26/regex-2026.7.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:66d2c35587cd601c95965d5c0415058ba5cfd6ffbab7624ce198bd967102b341", size = 494639, upload-time = "2026-07-10T19:46:52.207Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/0086215709f0f705661f13ba81516287538886ef0d589c545c12b0484669/regex-2026.7.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28a0973eeffff4292f5a7ee498ab65d5e94ee8cc9cea364239251eb4a260a0f1", size = 295920, upload-time = "2026-07-10T19:46:53.63Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9e/8e07d0eea46d2cf36bf4d3794634bb0a820f016d31bc349dfef008d96b02/regex-2026.7.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8331484450b3894298bef8abecce532171ff6ac60b71f999eed10f2c01941a8a", size = 290673, upload-time = "2026-07-10T19:46:54.863Z" }, + { url = "https://files.pythonhosted.org/packages/b7/56/d83c446de21c70ff49d2f1b2ff2196ac79a4ac6373d2cfe496011a250600/regex-2026.7.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0639b2488b775a0109f55a5a2172deebdedb4b6c5ab0d48c90b43cbf5de58d17", size = 792378, upload-time = "2026-07-10T19:46:56.116Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0e/d265e0cc6da47aea97e90eb896be2d2e8f92d16add13bac04fa46a0fd972/regex-2026.7.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be4223af640d0aa04c05db81d5d96ada3ead9c09187d892fd37f4f97829480be", size = 861790, upload-time = "2026-07-10T19:46:57.611Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a5/62655f6208d1170a3e9188d6a45d4af0a5ae3b9da8b87d474818ac5ff016/regex-2026.7.10-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d3c75d57a00109255e60bc9c623b6ececaf7905eaab845c79f036670ed4750a2", size = 906530, upload-time = "2026-07-10T19:46:59.142Z" }, + { url = "https://files.pythonhosted.org/packages/86/b7/d65aa2e9ffb18677cd0afbcf5990da8519a4e50778deb1bca49f043c5174/regex-2026.7.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:724ee9379568658ec06362cf24325c5315cc5a67f61dfe585bfeff58300a355b", size = 799912, upload-time = "2026-07-10T19:47:00.534Z" }, + { url = "https://files.pythonhosted.org/packages/8f/19/3a5ce23ea2eb1fe36306aef49c79746ce297e4b434aeb981b525c661413a/regex-2026.7.10-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:732c19e5828eb287d01edb83b2eb87f283ba8e5fc3441c732709d3e8cbd14aaa", size = 773675, upload-time = "2026-07-10T19:47:01.999Z" }, + { url = "https://files.pythonhosted.org/packages/fe/76/3c0eaa426700dd2ba14f2335f2b700a4e1484202254192ae440b83b8352a/regex-2026.7.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:982d07727c809b42a3968785354f11c3728414e4e90af0754345b431b2c32561", size = 781711, upload-time = "2026-07-10T19:47:03.425Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a8/a5a3fad84f9a7f897619f0f8e0a2c64946e9709044a186a8f869fb5c332f/regex-2026.7.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4574feca202f8c470bf678aed8b5d89df04aaf8dc677f3b83d92825051301c0f", size = 854539, upload-time = "2026-07-10T19:47:04.999Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c7/47e9b8c8ee77723b9eda74f517b6b25d2f555cf276c063a9eeea35bd86d5/regex-2026.7.10-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:80151ca5bfc6c4524186b3e08b499e97319b2001fc265ed2d4fc12c0d5692cdf", size = 763378, upload-time = "2026-07-10T19:47:06.845Z" }, + { url = "https://files.pythonhosted.org/packages/36/09/e27e42d9d42edf71205c7e6f5b2902bc874ea03c557c80da03b8ed16c9bf/regex-2026.7.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bb52e10e453b5493afe1f7702a2973bc10f4dd8901c0f2ed869ffaa3f8319296", size = 844663, upload-time = "2026-07-10T19:47:08.923Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b5/2423acb98362184ad9c8eebabafa15188d6a177daab919add8f2120fc6cd/regex-2026.7.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37aba1994d73b4944053ab65a15f313bd5c28c885dd7f0d494a11749d89db6e", size = 789236, upload-time = "2026-07-10T19:47:10.303Z" }, + { url = "https://files.pythonhosted.org/packages/60/ed/b387e84c8a3d6aa115dfb56865437a3fbaf28f4a6fb3b76cc6cce38ced70/regex-2026.7.10-cp311-cp311-win32.whl", hash = "sha256:6cbedeb5112f59dbd169385459b9943310bdd241c6966c19c5f6e2295055c93a", size = 266774, upload-time = "2026-07-10T19:47:11.904Z" }, + { url = "https://files.pythonhosted.org/packages/c4/64/f30a163a65ed1f07ad12c53af00a6bd2a7251a5329fba5a08adc6f9e81a3/regex-2026.7.10-cp311-cp311-win_amd64.whl", hash = "sha256:b1963ec5ba4d52788fb0eac6aca6eb8040e8e318c7e47ebbdfc09440c802919c", size = 277959, upload-time = "2026-07-10T19:47:13.231Z" }, + { url = "https://files.pythonhosted.org/packages/2b/de/61c8174171134cebb834ca9f8fe2ff8f49d8a3dd43453b48b537d0fbb49b/regex-2026.7.10-cp311-cp311-win_arm64.whl", hash = "sha256:3750c42d47712e362158a04d0fd80131f73a55e8c715b2885442a0ff6f9fc3fc", size = 276918, upload-time = "2026-07-10T19:47:14.693Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9c/2503d4ccf3452dc323f8baa3cf3ee10406037d52735c76cfced81423f183/regex-2026.7.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7252b48b0c60100095088fbeb281fca9a4fcf678a4e04b1c520c3f8613c952c4", size = 497114, upload-time = "2026-07-10T19:47:16.22Z" }, + { url = "https://files.pythonhosted.org/packages/91/eb/04534f4263a4f658cd20a511e9d6124350044f2214eb24fee2db96acf318/regex-2026.7.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:da6ef4cb8d457aab0482b50120136ae94238aaa421863eaa7d599759742c72d6", size = 297422, upload-time = "2026-07-10T19:47:17.794Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2d/35809de392ab66ba439b58c3187ae3b8b53c883233f284b59961e5725c99/regex-2026.7.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fe7ff456c22725c9d9017f7a2a7df2b51af6df77314176760b22e2d05278e181", size = 292110, upload-time = "2026-07-10T19:47:19.188Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1e/5ce0fbe9aab071893ce2b7df020d0f561f7b411ec334124302468d587884/regex-2026.7.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3463a5f26be513a49e4d497debcf1b252a2db7b92c77d89621aa90b83d2dd38", size = 796800, upload-time = "2026-07-10T19:47:20.639Z" }, + { url = "https://files.pythonhosted.org/packages/d4/67/c1ccbada395c10e334763b583e1039b1660b142303ebb941d4269130b22f/regex-2026.7.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:948dfc62683a6947b9b486c4598d8f6e3ecc542478b6767b87d52be68aeb55c6", size = 865509, upload-time = "2026-07-10T19:47:22.135Z" }, + { url = "https://files.pythonhosted.org/packages/0e/06/f0b31afc16c1208f945b66290eb2a9936ab8becdfb23bbcedb91cc5f9d9b/regex-2026.7.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c2cbd385d82f63bb35edb60b09b08abad3619bd0a4a492ae59e55afaf98e1b9d", size = 912395, upload-time = "2026-07-10T19:47:24.128Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1c/8687de3a6c3220f4f872a9bf4bcd8dc249f2a96e7dddfa93de8bd4d16399/regex-2026.7.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6222cafe00e072bb2b8f14142cd969637411fbc4dd3b1d73a90a3b817fa046f", size = 801308, upload-time = "2026-07-10T19:47:25.696Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e3/60a40ec02a2315d826414a125640aceb6f30450574c530c8f352110ece0e/regex-2026.7.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:65ee5d1ac3cd541325f5ac92625b1c1505f4d171520dd931bda7952895c5321a", size = 777120, upload-time = "2026-07-10T19:47:27.158Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9a/ec579b4f840ac59bc7c192b56e66abd4cbf385615300d59f7c94bf6863ae/regex-2026.7.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa34473fbcc108fea403074f3f45091461b18b2047d136f16ffaa4c65ad46a68", size = 785164, upload-time = "2026-07-10T19:47:28.732Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1c/60d88afd5f98d4b0fb1f8b8969270628140dc01c7ff93a939f2aa83f31a6/regex-2026.7.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d028d189d8f38d7ff292f22187c0df37f2317f554d2ed9a2908ada330af57c0", size = 860161, upload-time = "2026-07-10T19:47:30.605Z" }, + { url = "https://files.pythonhosted.org/packages/2a/40/08ae3ba45fe79e48c9a888a3389a7ee7e2d8c580d2d996da5ece02dfdcb9/regex-2026.7.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:396ea70e4ea1f19571940add3bad9fd3eb6a19dc610d0d01f692bc1ba0c10cb4", size = 765829, upload-time = "2026-07-10T19:47:32.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/e6/e613c6755d19aca9d977cdc3418a1991ffc8f386779752dd8fdfa888ea89/regex-2026.7.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ebbf0d83ed5271991d666e54bb6c90ac2c55fb2ef3a88740c6af85dc85de2402", size = 852170, upload-time = "2026-07-10T19:47:33.567Z" }, + { url = "https://files.pythonhosted.org/packages/03/33/89072f2060e6b844b4916d5bc40ef01e973640c703025707869264ec75ab/regex-2026.7.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58a4571b2a093f6f6ee4fd281faa8ebf645abcf575f758173ea2605c7a1e1ecb", size = 789550, upload-time = "2026-07-10T19:47:35.395Z" }, + { url = "https://files.pythonhosted.org/packages/e3/3c/4bc8be9a155035e63780ccac1da101f36194946fdc3f6fce90c7179fc6df/regex-2026.7.10-cp312-cp312-win32.whl", hash = "sha256:eac1207936555aa691ce32df1432b478f2729d54e6d93a1f4db9215bcd8eb47d", size = 267151, upload-time = "2026-07-10T19:47:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/9f5aade65bb98cc6e99c336e45a49a658300720c16721f3e687f8d754fec/regex-2026.7.10-cp312-cp312-win_amd64.whl", hash = "sha256:ecae626449d00db8c08f8f1fc00047a32d6d7eb5402b3976f5c3fda2b80a7a4f", size = 277751, upload-time = "2026-07-10T19:47:38.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/6f/d069dd12872ea1d50e17319d342f89e2072cae4b62f4245009a1108c74d8/regex-2026.7.10-cp312-cp312-win_arm64.whl", hash = "sha256:87794549a3f5c1c2bdfba2380c1bf87b931e375f4133d929da44f95e396bf5fe", size = 277063, upload-time = "2026-07-10T19:47:40.023Z" }, + { url = "https://files.pythonhosted.org/packages/e0/88/0c977b9f3ba9b08645516eca236388c340f56f7a87054d41a187a04e134c/regex-2026.7.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4db009b4fc533d79af3e841d6c8538730423f82ea8508e353a3713725de7901c", size = 496868, upload-time = "2026-07-10T19:47:41.675Z" }, + { url = "https://files.pythonhosted.org/packages/f6/51/600882cd5d9a3cf083fd66a4064f5b7f243ba2a7de2437d42823e286edaf/regex-2026.7.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b96341cb29a3faa5db05aff29c77d141d827414f145330e5d8846892119351c1", size = 297306, upload-time = "2026-07-10T19:47:43.521Z" }, + { url = "https://files.pythonhosted.org/packages/52/6f/48a912054ffcb756e374207bb8f4430c5c3e0ffa9627b3c7b6661844b30a/regex-2026.7.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14d27f6bd04beb01f6a25a1153d73e58c290fd45d92ba56af1bb44199fd1010d", size = 291950, upload-time = "2026-07-10T19:47:45.267Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c8/8e1c3c86ebcee7effccbd1f7fc54fe3af22aa0e9204503e2baea4a6ff001/regex-2026.7.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b6a11bf898cca3ce7bfaa17b646901107f3975677fbd5097f36e5eb5641983", size = 796817, upload-time = "2026-07-10T19:47:48.054Z" }, + { url = "https://files.pythonhosted.org/packages/65/39/3e49d9ff0e0737eb8180a00569b47aabb59b84611f48392eba4d998d91a0/regex-2026.7.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:234f8e0d65cf1df9becadae98648f74030ee85a8f12edcb5eb0f60a22a602197", size = 865513, upload-time = "2026-07-10T19:47:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/70/57/6511ad809bb3122c65bbeeffa5b750652bb03d273d29f3acb0754109b183/regex-2026.7.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:91b916d495db3e1b473c7c8e68733beec4dce8e487442db61764fff94f59740e", size = 912391, upload-time = "2026-07-10T19:47:51.776Z" }, + { url = "https://files.pythonhosted.org/packages/cc/29/a1b0c109c9e878cb04b931bfe4c54332d692b93c322e127b5ae9f25b0d9e/regex-2026.7.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f0d4ccf70b1d13711242de0ba78967db5c35d12ac408378c70e06295c3f6644", size = 801338, upload-time = "2026-07-10T19:47:53.38Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/171c3dad4d77000e1befeff2883ca88734696dfd97b2951e5e074f32e4dd/regex-2026.7.10-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c622f4c638a725c39abcb2e680b1bd592663c83b672a4ed350a17f806d75618e", size = 777149, upload-time = "2026-07-10T19:47:54.944Z" }, + { url = "https://files.pythonhosted.org/packages/33/61/41ab0de0e4574da1071c151f67d1eb9db3d92c43e31d64d2e6863c3d89bf/regex-2026.7.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41a47c2b28d9421e2509a4583a22510dc31d83212fcf38e1508a7013140f71a8", size = 785216, upload-time = "2026-07-10T19:47:56.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/28/372859ea693736f07cf7023247c7eca8f221d9c6df8697ff9f93371cca08/regex-2026.7.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:13fba679fe035037e9d5286620f88bbfd105df4d5fcd975942edd282ab986775", size = 860229, upload-time = "2026-07-10T19:47:58.278Z" }, + { url = "https://files.pythonhosted.org/packages/50/b1/e1d32cd944b599534ae655d35e8640d0ec790c0fa12e1fb29bf434d50f55/regex-2026.7.10-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8e26a075fa9945b9e44a3d02cc83d776c3b76bb1ff4b133bbfa620d5650131da", size = 765797, upload-time = "2026-07-10T19:48:00.291Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/79a2cd9556a3329351e370929743ef4f0ccc0aaff6b3dc414ae5fa4a1302/regex-2026.7.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d0834c84ae8750ae1c4cede59b0afd4d2f775be958e11b18a3eea24ed9d0d9f1", size = 852130, upload-time = "2026-07-10T19:48:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/66/58/76fec29898cf5d359ab63face50f9d4f7135cc2eca3477139227b1d09952/regex-2026.7.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64722a5031aeace7f6c8d5ea9a9b22d9368af0d6e8fa532585da8158549ea963", size = 789644, upload-time = "2026-07-10T19:48:03.748Z" }, + { url = "https://files.pythonhosted.org/packages/f6/06/3c7cec7817bda293e13c8f88aed227bbcf8b37e5990936ff6442a8fdf11a/regex-2026.7.10-cp313-cp313-win32.whl", hash = "sha256:74ae61d8573ecd51b5eeee7be2218e4c56e99c14fa8fcf97cf7519611d4be92e", size = 267130, upload-time = "2026-07-10T19:48:05.677Z" }, + { url = "https://files.pythonhosted.org/packages/88/6c/e2a6f9a6a905f923cfc912298a5949737e9504b1ca24f29eda8d04d05ece/regex-2026.7.10-cp313-cp313-win_amd64.whl", hash = "sha256:5e792367e5f9b4ffb8cad93f1beaa91837056b94da98aa5c65a0db0c1b474927", size = 277722, upload-time = "2026-07-10T19:48:07.318Z" }, + { url = "https://files.pythonhosted.org/packages/00/a6/9d8935aaa940c388496aa1a0c82669cc4b5d06291c2712d595e3f0cf16d3/regex-2026.7.10-cp313-cp313-win_arm64.whl", hash = "sha256:82ab8330e7e2e416c2d42fcec67f02c242393b8681014750d4b70b3f158e1f08", size = 277059, upload-time = "2026-07-10T19:48:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e9/26decfd3e85c09e42ff7b0d23a6f51085ca4c268db15f084928ca33459c6/regex-2026.7.10-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2b93eafd92c4128bab2f93500e8912cc9ecb3d3765f6685b902c6820d0909b6b", size = 501508, upload-time = "2026-07-10T19:48:10.668Z" }, + { url = "https://files.pythonhosted.org/packages/38/a5/5b167cebde101945690219bf34361481c9f07e858a4f46d9996b80ec1490/regex-2026.7.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3f03b92fb6ec739df042e45b06423fc717ecf0063e07ffe2897f7b2d5735e1e8", size = 299705, upload-time = "2026-07-10T19:48:12.544Z" }, + { url = "https://files.pythonhosted.org/packages/f6/20/7909be4b9f449f8c282c14b6762d59aa722aeaeebe7ee4f9bb623eeaa5e0/regex-2026.7.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bb5aab464a0c5e03a97abad5bdf54517061ebbf72340d576e99ff661a42575cc", size = 294605, upload-time = "2026-07-10T19:48:14.495Z" }, + { url = "https://files.pythonhosted.org/packages/82/88/e52550185d6fda68f549b01239698697de47320fd599f5e880b1986b7673/regex-2026.7.10-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fadb07dbe36a541283ff454b1a268afd54b077d917043f2e1e5615372cb5f200", size = 811747, upload-time = "2026-07-10T19:48:16.197Z" }, + { url = "https://files.pythonhosted.org/packages/06/98/16c255c909714de1ee04da6ae30f3ee04170f300cdc0dcf57a314ee4816a/regex-2026.7.10-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:21150500b970b12202879dfd82e7fd809d8e853140fff84d08e57a90cf1e154e", size = 871203, upload-time = "2026-07-10T19:48:18.12Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/423ed27c9bae2092a453e853da2b6628a658d08bb5a6117db8d591183d85/regex-2026.7.10-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a68b637451d64ba30ed8ae125c973fa834cc2d37dfa7f154c2b479015d477ba8", size = 917334, upload-time = "2026-07-10T19:48:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/73/87/74dac8efb500db31cb000fda6bae2be45fc2fbf1fa9412f445fbb8acbe37/regex-2026.7.10-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e23458d8903e33e7d27196d7a311523dc4e2f4137a5f34e4dbd30c8d37ff33e", size = 816379, upload-time = "2026-07-10T19:48:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/1859403654e3e030b288f06d49233c6a4f889d62b84c4ef3f3a28653173d/regex-2026.7.10-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae27622c094558e519abf3242cf4272db961d12c5c9a9ffb7a1b44b2627d5c6", size = 785563, upload-time = "2026-07-10T19:48:23.643Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d8/35d30d6bdf1ef6a5430e8982607b3a6db4df1ddedbe001e43435585d88ba/regex-2026.7.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ee877b6d78f9dff1da94fef51ae8cf9cce0967e043fdcc864c40b85cf293c192", size = 801415, upload-time = "2026-07-10T19:48:25.499Z" }, + { url = "https://files.pythonhosted.org/packages/f7/22/630f31f5ea4826167b2b064d9cac2093a5b3222af380aa432cfe1a5dabcd/regex-2026.7.10-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:2c66a8a1969cfd506d1e203c0005fd0fc3fe6efc83c945606566b6f9611d4851", size = 866560, upload-time = "2026-07-10T19:48:27.789Z" }, + { url = "https://files.pythonhosted.org/packages/8d/14/f5914a6d9c5bc63b9bed8c9a1169fb0be35dbe05cdc460e17d953031a366/regex-2026.7.10-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2bc350e1c5fa250f30ab0c3e38e5cfdffcd82cb8af224df69955cab4e3003812", size = 772877, upload-time = "2026-07-10T19:48:29.563Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0f/7c13999eef3e4186f7c79d4950fa56f041bf4de107682fb82c80db605ff9/regex-2026.7.10-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:53f54993b462f3f91fea0f2076b46deb6619a5f45d70dbd1f543f789d8b900ef", size = 856648, upload-time = "2026-07-10T19:48:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/a4/71/a48e43909b6450fb48fa94e783bef2d9a37179258bc32ef2283955df7be7/regex-2026.7.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cfcec18f7da682c4e2d82112829ce906569cb8d69fa6c26f3a50dfbed5ceb682", size = 803520, upload-time = "2026-07-10T19:48:33.275Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b8/f037d1bf2c133cb24ceb6e7d81d08417080390eddab6ddfd701aa7091874/regex-2026.7.10-cp313-cp313t-win32.whl", hash = "sha256:a2d6d30be35ddd70ce0f8ee259a4c25f24d6d689a45a5ac440f03e6bcc5a21d1", size = 269168, upload-time = "2026-07-10T19:48:35.353Z" }, + { url = "https://files.pythonhosted.org/packages/b6/9c/eaac34f8452a838956e7e89852ad049678cdc1af5d14f72d3b3b658b1ea5/regex-2026.7.10-cp313-cp313t-win_amd64.whl", hash = "sha256:c57b6ad3f7a1bdd101b2966f29dc161adf49727b1e8d3e1e89db2eda8a75c344", size = 280004, upload-time = "2026-07-10T19:48:37.106Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a9/e22e997587bc1d588b0b2cd0572027d39dd3a006216e40bbf0361688c51c/regex-2026.7.10-cp313-cp313t-win_arm64.whl", hash = "sha256:3d8ef9df02c8083c7b4b855e3cb87c8e0ebbcfea088d98c7a886aaefdf88d837", size = 279308, upload-time = "2026-07-10T19:48:38.907Z" }, +] + +[[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 = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, +] + +[[package]] +name = "ruff" +version = "0.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/17/529e78f49fc6f8076f50d985edd9a2cf011d1dbadb1cdeacc1d12afc1d26/ruff-0.9.4.tar.gz", hash = "sha256:6907ee3529244bb0ed066683e075f09285b38dd5b4039370df6ff06041ca19e7", size = 3599458, upload-time = "2025-01-30T18:09:51.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/f8/3fafb7804d82e0699a122101b5bee5f0d6e17c3a806dcbc527bb7d3f5b7a/ruff-0.9.4-py3-none-linux_armv6l.whl", hash = "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706", size = 11668400, upload-time = "2025-01-30T18:08:46.508Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/2efa772d335da48a70ab2c6bb41a096c8517ca43c086ea672d51079e3d1f/ruff-0.9.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf", size = 11628395, upload-time = "2025-01-30T18:08:50.87Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d7/cd822437561082f1c9d7225cc0d0fbb4bad117ad7ac3c41cd5d7f0fa948c/ruff-0.9.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:54499fb08408e32b57360f6f9de7157a5fec24ad79cb3f42ef2c3f3f728dfe2b", size = 11090052, upload-time = "2025-01-30T18:08:54.498Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/3660d58e893d470abb9a13f679223368ff1684a4ef40f254a0157f51b448/ruff-0.9.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37c892540108314a6f01f105040b5106aeb829fa5fb0561d2dcaf71485021137", size = 11882221, upload-time = "2025-01-30T18:08:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/79/d1/757559995c8ba5f14dfec4459ef2dd3fcea82ac43bc4e7c7bf47484180c0/ruff-0.9.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de9edf2ce4b9ddf43fd93e20ef635a900e25f622f87ed6e3047a664d0e8f810e", size = 11424862, upload-time = "2025-01-30T18:09:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/7915a7c6877bb734caa6a2af424045baf6419f685632469643dbd8eb2958/ruff-0.9.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87c90c32357c74f11deb7fbb065126d91771b207bf9bfaaee01277ca59b574ec", size = 12626735, upload-time = "2025-01-30T18:09:05.312Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cc/dadb9b35473d7cb17c7ffe4737b4377aeec519a446ee8514123ff4a26091/ruff-0.9.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56acd6c694da3695a7461cc55775f3a409c3815ac467279dfa126061d84b314b", size = 13255976, upload-time = "2025-01-30T18:09:09.425Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c3/ad2dd59d3cabbc12df308cced780f9c14367f0321e7800ca0fe52849da4c/ruff-0.9.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0c93e7d47ed951b9394cf352d6695b31498e68fd5782d6cbc282425655f687a", size = 12752262, upload-time = "2025-01-30T18:09:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/c7/17/5f1971e54bd71604da6788efd84d66d789362b1105e17e5ccc53bba0289b/ruff-0.9.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4c8772670aecf037d1bf7a07c39106574d143b26cfe5ed1787d2f31e800214", size = 14401648, upload-time = "2025-01-30T18:09:17.086Z" }, + { url = "https://files.pythonhosted.org/packages/30/24/6200b13ea611b83260501b6955b764bb320e23b2b75884c60ee7d3f0b68e/ruff-0.9.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc5f1d7afeda8d5d37660eeca6d389b142d7f2b5a1ab659d9214ebd0e025231", size = 12414702, upload-time = "2025-01-30T18:09:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/f5d50d0c4ecdcc7670e348bd0b11878154bc4617f3fdd1e8ad5297c0d0ba/ruff-0.9.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faa935fc00ae854d8b638c16a5f1ce881bc3f67446957dd6f2af440a5fc8526b", size = 11859608, upload-time = "2025-01-30T18:09:25.663Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f4/9c8499ae8426da48363bbb78d081b817b0f64a9305f9b7f87eab2a8fb2c1/ruff-0.9.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6c634fc6f5a0ceae1ab3e13c58183978185d131a29c425e4eaa9f40afe1e6d6", size = 11485702, upload-time = "2025-01-30T18:09:28.903Z" }, + { url = "https://files.pythonhosted.org/packages/18/59/30490e483e804ccaa8147dd78c52e44ff96e1c30b5a95d69a63163cdb15b/ruff-0.9.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:433dedf6ddfdec7f1ac7575ec1eb9844fa60c4c8c2f8887a070672b8d353d34c", size = 12067782, upload-time = "2025-01-30T18:09:32.371Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8c/893fa9551760b2f8eb2a351b603e96f15af167ceaf27e27ad873570bc04c/ruff-0.9.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d612dbd0f3a919a8cc1d12037168bfa536862066808960e0cc901404b77968f0", size = 12483087, upload-time = "2025-01-30T18:09:36.124Z" }, + { url = "https://files.pythonhosted.org/packages/23/15/f6751c07c21ca10e3f4a51ea495ca975ad936d780c347d9808bcedbd7182/ruff-0.9.4-py3-none-win32.whl", hash = "sha256:db1192ddda2200671f9ef61d9597fcef89d934f5d1705e571a93a67fb13a4402", size = 9852302, upload-time = "2025-01-30T18:09:40.013Z" }, + { url = "https://files.pythonhosted.org/packages/12/41/2d2d2c6a72e62566f730e49254f602dfed23019c33b5b21ea8f8917315a1/ruff-0.9.4-py3-none-win_amd64.whl", hash = "sha256:05bebf4cdbe3ef75430d26c375773978950bbf4ee3c95ccb5448940dc092408e", size = 10850051, upload-time = "2025-01-30T18:09:43.42Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e6/3d6ec3bc3d254e7f005c543a661a41c3e788976d0e52a1ada195bd664344/ruff-0.9.4-py3-none-win_arm64.whl", hash = "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41", size = 10078251, upload-time = "2025-01-30T18:09:48.01Z" }, +] + +[[package]] +name = "safehttpx" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/d1/4282284d9cf1ee873607a46442da977fc3c985059315ab23610be31d5885/safehttpx-0.1.7.tar.gz", hash = "sha256:db201c0978c41eddb8bb480f3eee59dd67304fdd91646035e9d9a720049a9d23", size = 10385, upload-time = "2025-10-24T18:30:09.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/a3/0f0b7d78e2f1eb9e8e1afbff1d2bff8d60144aee17aca51c065b516743dd/safehttpx-0.1.7-py3-none-any.whl", hash = "sha256:c4f4a162db6993464d7ca3d7cc4af0ffc6515a606dfd220b9f82c6945d869cde", size = 8959, upload-time = "2025-10-24T18:30:08.733Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version < '3.11'" }, + { name = "lazy-loader", marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pillow", marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "tifffile", version = "2025.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922, upload-time = "2025-02-18T18:04:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698, upload-time = "2025-02-18T18:04:15.362Z" }, + { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634, upload-time = "2025-02-18T18:04:18.496Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545, upload-time = "2025-02-18T18:04:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908, upload-time = "2025-02-18T18:04:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version >= '3.11'" }, + { name = "lazy-loader", marker = "python_full_version >= '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pillow", marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "tifffile", version = "2026.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", +] +dependencies = [ + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, +] + +[[package]] +name = "selectolax" +version = "0.4.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/df/d19f9b47eb6c1aa0ddf95259c757c018c39682facb569e81c23e173bbf35/selectolax-0.4.10.tar.gz", hash = "sha256:89764b4d1e32d38e635dfb270a639fc707af4315b863fd161357a517321e5046", size = 4880217, upload-time = "2026-05-26T15:43:06.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/59/442ebebf0d18e0f873101956a8dbe441752f758289c1a780503c6008893f/selectolax-0.4.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b8cc9177f9200687a24ade718f49cff09738add8eea5230445937dc6de322e1b", size = 2210999, upload-time = "2026-05-26T15:41:23.981Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/4591bf79e2e50d91373330fa28af995453e4f93032b81a4f019c2299c7da/selectolax-0.4.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d06ddf24ebcddead2c5a2ed96607f46651db899b8f331a16481d88a91d5c9af", size = 2263622, upload-time = "2026-05-26T15:41:26.229Z" }, + { url = "https://files.pythonhosted.org/packages/61/00/53d8f7c60dac83744eb7dba53517675c9988ff93bf1ffa745a7bd06acf7f/selectolax-0.4.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8fc8fc6b8597eb724b647757417080f1ba042eb6098a702d55cc8ab1eca6367", size = 2344526, upload-time = "2026-05-26T15:41:27.618Z" }, + { url = "https://files.pythonhosted.org/packages/52/82/4254fd5cdb78075f1e32d145b2dc286ed4ba42a94e24672fabc5998281a0/selectolax-0.4.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd436b0143edb5e1c4f126eeb4647344bedaab9e2a379eddb528ffac33c7adad", size = 2394529, upload-time = "2026-05-26T15:41:29.193Z" }, + { url = "https://files.pythonhosted.org/packages/53/86/5eb10718d3100ee961e2b8624681d1d65741c6a8a38de61f0b57cfbf5b32/selectolax-0.4.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:529333e1d855a0ad3b9414b5a798bb5686f584cb868f1183bb5ec9f3cb5fec4d", size = 2348589, upload-time = "2026-05-26T15:41:30.582Z" }, + { url = "https://files.pythonhosted.org/packages/df/f4/676db73dc3273dc0a10da6c9fde7ac3daa610a7a0c46fa84c89f58906616/selectolax-0.4.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0770275c24b900410eab4b08dd2e6ca8508bbe1d1880bfaf7f17123ad8889242", size = 2404267, upload-time = "2026-05-26T15:41:32.278Z" }, + { url = "https://files.pythonhosted.org/packages/18/3c/a507ecb4ff757387cc18c0600a5d282c43671b53e705db6d8574ab09249d/selectolax-0.4.10-cp310-cp310-win32.whl", hash = "sha256:3608997f2ef5f1ba80d26da784604335229deeae66e37d67122b48ef2436bf6b", size = 1769573, upload-time = "2026-05-26T15:41:33.959Z" }, + { url = "https://files.pythonhosted.org/packages/90/94/a0c9ffed6816467213783132621c4530520ad17a264b2acb69f5c38341d6/selectolax-0.4.10-cp310-cp310-win_amd64.whl", hash = "sha256:fb27937417ca7e60a5b408e1deb6fe9af691ba16d962101e9cb1e73aeefc1802", size = 1864428, upload-time = "2026-05-26T15:41:36.207Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e5/d9ffa0b10d0414f820760ab599a0f2ab5dab2bcfa15cb61d81c7044a8323/selectolax-0.4.10-cp310-cp310-win_arm64.whl", hash = "sha256:d9c5b4a32e33359bc6919fd9f3259d2ba51c04b8aa5b25e91611d94d4a503020", size = 1816721, upload-time = "2026-05-26T15:41:38.027Z" }, + { url = "https://files.pythonhosted.org/packages/63/34/cee18c79edc4268b679ff7af60c58918f2c8279de7715998c81ff4f39eff/selectolax-0.4.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b65ce508ed7f3951a2f36f807494c253d0ebe99d2b18ede149f9a97b99be7d7a", size = 2216148, upload-time = "2026-05-26T15:41:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/8a/29/60ddd1570386ea2e13683848b7d21ffbd1d41c921cb88df20cd10fd6679d/selectolax-0.4.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1bb382bd7009676716814ee81327015ab3d472aff0148fccbd20322d37af83d", size = 2270171, upload-time = "2026-05-26T15:41:40.929Z" }, + { url = "https://files.pythonhosted.org/packages/41/e7/7bef2f76dfd0d270899277d07d7418df82623f1985d570f73ad0a0b963a9/selectolax-0.4.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48c238f4f2b4ebd3d94ec260363d2bbab7df2825b592a1f9b12d59a9a9e9ee9a", size = 2347593, upload-time = "2026-05-26T15:41:42.391Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f5/fdb59ef99828ee43e2792507bd67f7d5dfd844dbb1ac6512698e16fa8add/selectolax-0.4.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:975bf1199c80965307168a05c4d88312a62b8151ac27216ed1248fbd175695bb", size = 2399514, upload-time = "2026-05-26T15:41:44.151Z" }, + { url = "https://files.pythonhosted.org/packages/6e/03/46ed77c44e877278723c31ce8fac7942a6909d46a4d50c87cd6d685b21ce/selectolax-0.4.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c74168deeb116a1271e74e7a157e329930b7f54e328b323ce00bf7089f39dc9", size = 2352416, upload-time = "2026-05-26T15:41:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/c7/80/cb89e578a53cf51674c9083e3a9518a45bf5b8af56309cc1fa63cc007703/selectolax-0.4.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:547de570413e22ae5cbc8432d6224dd5a2d5bc66d1a50e34b705e5940d035caa", size = 2409794, upload-time = "2026-05-26T15:41:47.374Z" }, + { url = "https://files.pythonhosted.org/packages/06/af/5346ac7a2053a502687b136c836c0902d0570398e8f71d73182492a3863b/selectolax-0.4.10-cp311-cp311-win32.whl", hash = "sha256:e053064c5e00788a6bcc5427a753e41c058f29e4285bf84a3e663989b7218cdb", size = 1768531, upload-time = "2026-05-26T15:41:49.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/8c/ae5b0f04d1e39bc212f3a3e1cf308a8c5f117f03cd3c089b6a04e8d8fbc7/selectolax-0.4.10-cp311-cp311-win_amd64.whl", hash = "sha256:db236a92e49b27369a98b9e8ac6c97e1534368d83281d097f3e50c2ba597f112", size = 1865401, upload-time = "2026-05-26T15:41:50.695Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b5/7eb96a229cbdd7773c223c7ecfd0b0eead78b330218c6768179ab5daa2d1/selectolax-0.4.10-cp311-cp311-win_arm64.whl", hash = "sha256:746838a34df35ceff5f6154991b09aa6c27f044dc1fe9a4137f8c57e3e9f1203", size = 1816391, upload-time = "2026-05-26T15:41:52.135Z" }, + { url = "https://files.pythonhosted.org/packages/33/26/a7966a2e2667463717c71903851b4c8a434afd3b592e8528bc87efa1cb1e/selectolax-0.4.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c1933f53fe7410777b3373b9900010ae8d392a6d245f019d8f011d12f700e389", size = 2241715, upload-time = "2026-05-26T15:41:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/76/55/0181dfa3cdec4c5db9868ca489fc50b5521a74c1847a0628db6e08bd3359/selectolax-0.4.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40babac4aea579edfb32b74acdacdb4c38bacb1ee3d1c4189f9665c52c67586f", size = 2292823, upload-time = "2026-05-26T15:41:55.692Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/6d821c8de1bcc8be380c4f9efc6688a433796a5c18809b77219e9616a918/selectolax-0.4.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d41b7e95ab0e025053efcba71267af12a6c12bc624e3bdf5dd83c6534f3d696", size = 2375082, upload-time = "2026-05-26T15:41:57.243Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/23903b34869f6721c36a46b8231da202b7af729f6e28b54fbb06f59e5195/selectolax-0.4.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e41ac4a761c5d4589ce9c40866a80fa2c5d413ac6b3af9546b43a2b3a8da18", size = 2422073, upload-time = "2026-05-26T15:41:58.667Z" }, + { url = "https://files.pythonhosted.org/packages/23/f3/85f1ac82944254822d7bc8b20bae674d9bb4fb93fd3e101f6f807c7f075c/selectolax-0.4.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:425288d4f5b18cfb0049fd1d6f3bddfba319657b9547b0e2bd24103b4b69435e", size = 2379122, upload-time = "2026-05-26T15:42:00.089Z" }, + { url = "https://files.pythonhosted.org/packages/47/61/c4e8aa47d25245644cc0ce60ea2eb922f501ccea8f642ba01c5d551e8959/selectolax-0.4.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ff2585eaf13ddc5c6614b10a7e47679b0c18a78821495e0c448364f8871592e", size = 2441931, upload-time = "2026-05-26T15:42:01.522Z" }, + { url = "https://files.pythonhosted.org/packages/67/73/05eda9364c5f1053f166648a1e69c546e53af0e01901a2f218b32c2a7010/selectolax-0.4.10-cp312-cp312-win32.whl", hash = "sha256:2428f04d2a48ba5f4c182f0d7234a7e38ac799b3358b3063f0ae41f754ee1c4a", size = 1763584, upload-time = "2026-05-26T15:42:03.055Z" }, + { url = "https://files.pythonhosted.org/packages/10/66/abcf20676cd05eeefef58f6644855216575ae3988b719e914d359ffa3104/selectolax-0.4.10-cp312-cp312-win_amd64.whl", hash = "sha256:c4b4b7c5d09a20539d369891332a107a869ee170453254048fbc18f893deb4f9", size = 1859909, upload-time = "2026-05-26T15:42:04.843Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a5/6a84520df37873cbc08af4fd8a719a495d5971a7c370c2ed8512e8391935/selectolax-0.4.10-cp312-cp312-win_arm64.whl", hash = "sha256:98f93b92d23a8feb88efc7c8e692221456bae30dc551d86d376931491152b909", size = 1810017, upload-time = "2026-05-26T15:42:06.561Z" }, + { url = "https://files.pythonhosted.org/packages/37/88/f932da5e018dcec1fa4286414db4798afdd244fbf95a46ac79db018318e1/selectolax-0.4.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3236fc0fbea9e237ee963274ebae700e68b9d6784bf91e0b3693eda63b393fa2", size = 2241128, upload-time = "2026-05-26T15:42:08.34Z" }, + { url = "https://files.pythonhosted.org/packages/9a/10/0a2caaceda0c6cf226fe5e43f6d7b9134207cf61642bed651712c7599646/selectolax-0.4.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b43da45acece07f94e4b0d555e073f1a91314c98cb86d10860a1b291bc498976", size = 2291992, upload-time = "2026-05-26T15:42:09.723Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d5/687dfb8c09110a5986a4f3f8e424b61c0f71716c5784077b76f02e4e81d7/selectolax-0.4.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5c80477a3a93f0ee350d832c6fc764cd2df1299914a816bcd5ed4f0c9701b9b", size = 2374518, upload-time = "2026-05-26T15:42:11.201Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/ac6011d2785f643baf3be4b8910e657e71427d37e41a15a513274b794425/selectolax-0.4.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:16055f712bd93507ce61ecac156bb7acf96b2e46c4d4d30c616e810f74f4da6e", size = 2421458, upload-time = "2026-05-26T15:42:12.656Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fa/0cd29c8a629fe890a53ef2db9cf9282dffad9f4d0cf9a41553a87058f82c/selectolax-0.4.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d42566a7e649d4e5461e763a241f46542df2613876422e0530bc59999063f36d", size = 2378999, upload-time = "2026-05-26T15:42:14.011Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d0/3f9ba04dba314c2f3237aca73e5b6c9578d693297bc0c91b2224d48b2455/selectolax-0.4.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6517b40e41ae5cc7756f92d88c59f178eb4a3683c7ce39c66bd5617587f628e5", size = 2441425, upload-time = "2026-05-26T15:42:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/34/f3/83e49b1b8dd68d4844a85930a6d68cbf14636be222cda03229833b8326be/selectolax-0.4.10-cp313-cp313-win32.whl", hash = "sha256:7c596b424c55ae87003140f55e6aa6f88e060b645781fa69e947ef60691b2bde", size = 1763466, upload-time = "2026-05-26T15:42:17.674Z" }, + { url = "https://files.pythonhosted.org/packages/16/6b/e77507d5aa7d5724c94333fa229694e108c0a4ca88a1c39ea9ae9ed7afc3/selectolax-0.4.10-cp313-cp313-win_amd64.whl", hash = "sha256:1bb589f6ed0f1ec28784c2cd29de111a5b6f8129c3ecf0e71b9665e588667b97", size = 1861934, upload-time = "2026-05-26T15:42:19.194Z" }, + { url = "https://files.pythonhosted.org/packages/23/69/7e31bb9427fee2d7506d20da60257ec3f72c278c176dbf47b7e0c494d521/selectolax-0.4.10-cp313-cp313-win_arm64.whl", hash = "sha256:ec333fe02c4b7d8a03c0aa58c7c2265edd3312b1ef309f03efd020f595f6dae1", size = 1810021, upload-time = "2026-05-26T15:42:21.185Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435, upload-time = "2026-07-12T08:39:34.701Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/1b/e6c69e4c2026ed575d68dda2847a404468ca7b5fa684bb0b19f71d82d29d/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc7b0b1da20f856bfac5f84b2673fe534b167e41980b27442ca8f78c2b7eb77e", size = 2180607, upload-time = "2026-07-12T08:38:01.018Z" }, + { url = "https://files.pythonhosted.org/packages/36/5a/2a1d84c87dc075d4f8cf1a2470a95399e59834e219ffb5f4285533e750d0/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b2db2056c97224e122054fd794543cde5d24b7cae28424f6e3eb79bbe08e42b", size = 1437502, upload-time = "2026-07-12T08:38:02.899Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/3d43a75dd5a22503ca5074d0d37707cabb2e4a71b4bc6e6c61be3643cc7a/sentencepiece-0.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f1f61592e7cabd45d49ce8cc0ef42ca655c091e037153754fb3fa59725b5914", size = 1345667, upload-time = "2026-07-12T08:38:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/d5/a69a8cc896e7de3fe2061b08c2f33e28656f243bed8af6a2df9f5d8c3124/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c798f0b327bac10dc95cdac77b9a197ab2bd7dd1e60ebd7586a12d918d4be711", size = 1322864, upload-time = "2026-07-12T08:38:06.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/79/dd1836df32971d4eb14ff5cb4a8b3fe4419adbeada8e81d09dc53c5c0ef0/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44284adc6fbe9d5bdd480541431a3d93f674fa44736714d3ad4bcee8283ace7d", size = 1392757, upload-time = "2026-07-12T08:38:08.559Z" }, + { url = "https://files.pythonhosted.org/packages/26/83/c3547715c29b7e4c84a180a240267f7685dde6f9b981396f16b95405ec9d/sentencepiece-0.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:1120e0791540615e650b2e9bea835bf38a7362455d8ab62dee7968219c2d79a0", size = 1245044, upload-time = "2026-07-12T08:38:10.21Z" }, + { url = "https://files.pythonhosted.org/packages/1f/55/7da03b35582a4eb276f99051109f3e3e8f176835b6d6837422e4c3a013dd/sentencepiece-0.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:524e2a85c028a0d2f9935191fa751e5ef9d9bcc39616f70ab14b28d0369c9936", size = 1190467, upload-time = "2026-07-12T08:38:12.07Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/f23a2efaa0210b883574001b88fa64e499f798f0848a0b610fb9b384d162/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69e9dc8078e128286ed3b975e37c837ba96e215a50c3ef9f3f8b7ab9e5a832a0", size = 2184255, upload-time = "2026-07-12T08:38:14.855Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/1ee0ccb772d71e822f625d6cb5f0ea825835e877f28a9ef299a1291df19e/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6dd76f3e5c8b2eb8a3a3efee787bbf5b9a66e52a048fe09cab85eca33fec6790", size = 1438545, upload-time = "2026-07-12T08:38:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/2a/92/3a6ea4a2c6dd9e7062698a5a33534ca0e20844883338ae9c6b9c122c1a9f/sentencepiece-0.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:443ac618c7a2a1377cf5c82581fbb849591d14e656d5e5a3e4682d4e36a34e4e", size = 1346997, upload-time = "2026-07-12T08:38:18.499Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3a/7839048997c7bc0c34c57526f539f835e20c7a57dc2a99f99579b11cdbef/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e2aae42960392d6dcb9a72d8e1e65a97294c965071b43c7b3429a42f350250e", size = 1324282, upload-time = "2026-07-12T08:38:20.342Z" }, + { url = "https://files.pythonhosted.org/packages/06/5f/9117bf854aef817ad0d0ee9310eed0308a7e529e7eaf2e80ad9cd281ef82/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1416b92f2f010333786fe6306ed2631121d5ea492219b0841e967b6765e64107", size = 1394242, upload-time = "2026-07-12T08:38:22.976Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/9e2569867e3dcff7ad6d89642a9615b9801b5cd698abe7df3b490361f66e/sentencepiece-0.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:70d4ca6f4d06df7f0ccab6fe4f49c8a712c8c8b6847b4f0af9a0e1dbb0e0337e", size = 1246268, upload-time = "2026-07-12T08:38:24.857Z" }, + { url = "https://files.pythonhosted.org/packages/96/c9/5d781d4ef1124564a45c98b9ff25d531c10cdf568ec6314a2d1946f9251c/sentencepiece-0.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:252908153eeec06c3ca3a32077e64a49d572e3d89881475b4e0f02d99d9fcc7c", size = 1190702, upload-time = "2026-07-12T08:38:26.789Z" }, + { url = "https://files.pythonhosted.org/packages/b8/13/7a562289c8d5b49ebdf3f9c1e8ab67cf14a8743b1d90c8f406bfdec36b72/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b", size = 2188384, upload-time = "2026-07-12T08:38:28.486Z" }, + { url = "https://files.pythonhosted.org/packages/85/d1/912f14fd5eae168aba726ffb6a9a2dc1c71fe7676c53da6f5c442b886d4a/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820", size = 1441553, upload-time = "2026-07-12T08:38:30.552Z" }, + { url = "https://files.pythonhosted.org/packages/bd/44/caa9cab5f261a019e2808bc5046152775dc57352ba9cbae7525e9e7a1ed4/sentencepiece-0.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838", size = 1347176, upload-time = "2026-07-12T08:38:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/cd798935668cff71d309d8ff10385844ecf216b1fe454f1993ed8bf2cb91/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a", size = 1325200, upload-time = "2026-07-12T08:38:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/37e3da037318a70066ded0d51bc2a7f35491ae6338dd993d5eb1503fc3b5/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da", size = 1397736, upload-time = "2026-07-12T08:38:35.771Z" }, + { url = "https://files.pythonhosted.org/packages/8d/11/753fca2e6b109be3ab7867abf357dfe48677fe726ae5a5363d0b54ca9450/sentencepiece-0.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7c6e7bf684dc12145bfa685d3060beaea55139134ba848289bee514ed42e7383", size = 1248030, upload-time = "2026-07-12T08:38:37.604Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0a/70efbe861ca182d7d4b6e1a20f58e043400848fa9f2915229f082e221648/sentencepiece-0.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:76ff5814db72e7462dece042d7593cdf102b8ec82c2b1cc201a2add34ee3050d", size = 1187325, upload-time = "2026-07-12T08:38:39.348Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a3/b3b05095c174d6e80d37d5ddc2f57c2c56237333e7bbd6079cf3243c2a8a/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:77c3ce990b23441e5ecfa5bce181fd6f408b564aeb6d7e1d1e7de9c5612501c8", size = 2188346, upload-time = "2026-07-12T08:38:41.089Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/72ebc4acb10a06bcf7503fbc6091c8f5db68300f6aac4356c09e6c76e0e1/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd523c4992041faa5c2b3cde62253d11a96c30d73a34afe48a486e8e2254cd1c", size = 1441434, upload-time = "2026-07-12T08:38:42.56Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/f9ea1a6844b4fa5dfe2312095cd866a1f724cd0905054ab9d5991778ba50/sentencepiece-0.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:201a8e0f55501a76e08dbf2c54bc45f4642b379271e89c667d517bfbc2191f2a", size = 1347267, upload-time = "2026-07-12T08:38:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/32/4f/31c1073314ad94466bca37d29581761d70110237ee3d46b0efece59a8c1e/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eed98514bffe5ecac37f493f91869c351fbb05629328bfdbc08502c6c094dc0", size = 1324980, upload-time = "2026-07-12T08:38:46.304Z" }, + { url = "https://files.pythonhosted.org/packages/59/b4/a0356fa04d6a14337a6e0e443556785a0422c53ec58baae6b9568120eb0f/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64b656f025355cf8c51abe9fbe3848540756c6d7ca5e6791b1afa664bc24c7cb", size = 1397593, upload-time = "2026-07-12T08:38:48.302Z" }, + { url = "https://files.pythonhosted.org/packages/09/fa/d2d6369257fd2f0de616b1c7110b73fab409ef61b14f1b9e0010ed325914/sentencepiece-0.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:74f0ee601047c0c12a783088b51be4e6214a62ecd9e02278c477433cd16e0ed9", size = 1247987, upload-time = "2026-07-12T08:38:50.15Z" }, + { url = "https://files.pythonhosted.org/packages/17/ee/2bb594da6fd95e32f29057f1aa7fa996701b8980090923c2d8711fdc0a24/sentencepiece-0.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:b23fe17779834d3c27aaf2edac9486d04cca1a7deb8f5facda35150ac6263a91", size = 1187250, upload-time = "2026-07-12T08:38:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/58/9c/dfc82846460e7a712310f5613f23d8b553cabb4e2e648663c11d8382af56/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:72b7825b331b1b7e7c45be2e674b3e3c65af608fa376bad2d851b20aaf0cdc78", size = 2223080, upload-time = "2026-07-12T08:38:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/3ff12cebe6d31662d9ceeabfb282de20bd0d6098fa282b4a3b8305abc7e8/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d795c4ac689a57f9d4ba2288126ec7901d389ad5827d2f8b8533c883974fe563", size = 1458511, upload-time = "2026-07-12T08:38:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/59/5a/16d51d05360be4cee3ebfe4837c184054c4eed16cabaeb3b039524e9a000/sentencepiece-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ab3f1ae98970b5590e2209341522718900ba19bcc2c207ffaa6bd417ad960c5", size = 1361138, upload-time = "2026-07-12T08:38:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/0f/af/c30ee2a9f99d51db9844acaa8fa0b611a97c2fa7116646fa43db3300b187/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec27c152a1f1b24bc9168b55a5880f3c16e2334e697da6f55a1046a22405a3d", size = 1328625, upload-time = "2026-07-12T08:39:00.849Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1a/4c6b39d03f5ba8439509adbd5a23c9538088a3cb679e7a47b911e8442bc6/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59d6588712101ccfcae9b03692be3aaae1514c2078666d7b05f15ba3a702e41b", size = 1398595, upload-time = "2026-07-12T08:39:02.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9eedddcec1fd57bc70200fa3ebf792d18fa63527a5369581cd416c81f97f/sentencepiece-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:89625fb43765cccaa1443b9adb61f283e5fe4cb1536728205d06bada730caa53", size = 1259346, upload-time = "2026-07-12T08:39:04.559Z" }, + { url = "https://files.pythonhosted.org/packages/41/15/7e74c8533848866ff560b29f7d8719921b76c4ec7149592d6d28e0deee75/sentencepiece-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:4f0603267cd15b92b68c2c0e852a441507614b70dc7773659baa6b8c214a91fd", size = 1196596, upload-time = "2026-07-12T08:39:06.454Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, +] + +[[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 = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "telefuser" +source = { editable = "." } +dependencies = [ + { name = "click" }, + { name = "diffusers" }, + { name = "einops" }, + { name = "fastapi" }, + { name = "ftfy" }, + { name = "gradio" }, + { name = "imageio", extra = ["ffmpeg"] }, + { name = "loguru" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "ray" }, + { name = "safetensors" }, + { name = "torchvision" }, + { name = "tqdm" }, + { name = "transformers" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[package.optional-dependencies] +cache = [ + { name = "faiss-cpu" }, + { name = "protobuf" }, + { name = "qwen-vl-utils" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sentencepiece" }, +] +dev = [ + { name = "pillow" }, + { name = "pre-commit" }, + { name = "psutil" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "uv" }, +] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-api-autonav" }, + { name = "mkdocs-awesome-nav" }, + { name = "mkdocs-gen-files" }, + { name = "mkdocs-git-revision-date-localized-plugin" }, + { name = "mkdocs-glightbox" }, + { name = "mkdocs-material" }, + { name = "mkdocs-minify-plugin" }, + { name = "mkdocs-static-i18n" }, + { name = "mkdocstrings-python" }, + { name = "regex" }, + { name = "ruff" }, +] +webrtc = [ + { name = "aiortc" }, + { name = "opencv-python-headless" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiortc", marker = "extra == 'webrtc'", specifier = ">=1.9.0" }, + { name = "click" }, + { name = "diffusers", specifier = ">=0.36.0" }, + { name = "einops" }, + { name = "faiss-cpu", marker = "extra == 'cache'" }, + { name = "fastapi" }, + { name = "ftfy" }, + { name = "gradio", specifier = "==5.50" }, + { name = "imageio", extras = ["ffmpeg"], specifier = ">=2.37.2" }, + { name = "loguru" }, + { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.5.0" }, + { name = "mkdocs-api-autonav", marker = "extra == 'docs'" }, + { name = "mkdocs-awesome-nav", marker = "extra == 'docs'" }, + { name = "mkdocs-gen-files", marker = "extra == 'docs'" }, + { name = "mkdocs-git-revision-date-localized-plugin", marker = "extra == 'docs'" }, + { name = "mkdocs-glightbox", marker = "extra == 'docs'" }, + { name = "mkdocs-material", marker = "extra == 'docs'" }, + { name = "mkdocs-minify-plugin", marker = "extra == 'docs'" }, + { name = "mkdocs-static-i18n", marker = "extra == 'docs'" }, + { name = "mkdocstrings-python", marker = "extra == 'docs'" }, + { name = "openai" }, + { name = "opencv-python-headless", marker = "extra == 'webrtc'", specifier = ">=4.0" }, + { name = "pillow", marker = "extra == 'dev'", specifier = ">=9.0.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = "==4.0.1" }, + { name = "protobuf", marker = "extra == 'cache'" }, + { name = "psutil", marker = "extra == 'dev'", specifier = ">=7.2.0" }, + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pydantic-settings", specifier = ">=2.0.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.21.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "python-multipart", specifier = ">=0.0.20" }, + { name = "qwen-vl-utils", marker = "extra == 'cache'" }, + { name = "ray" }, + { name = "regex", marker = "extra == 'docs'" }, + { name = "ruff", marker = "extra == 'docs'", specifier = "==0.9.4" }, + { name = "safetensors" }, + { name = "scikit-image", marker = "extra == 'dev'", specifier = ">=0.19.0" }, + { name = "scipy", marker = "extra == 'cache'" }, + { name = "sentencepiece", marker = "extra == 'cache'" }, + { name = "torchvision" }, + { name = "tqdm", specifier = ">=4.66.0" }, + { name = "transformers", specifier = "==4.57.3" }, + { name = "uv", marker = "extra == 'dev'" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0,<1" }, +] +provides-extras = ["cache", "dev", "docs", "webrtc"] + +[[package]] +name = "tifffile" +version = "2025.5.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/d0/18fed0fc0916578a4463f775b0fbd9c5fed2392152d039df2fb533bfdd5d/tifffile-2025.5.10.tar.gz", hash = "sha256:018335d34283aa3fd8c263bae5c3c2b661ebc45548fde31504016fcae7bf1103", size = 365290, upload-time = "2025-05-10T19:22:34.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/06/bd0a6097da704a7a7c34a94cfd771c3ea3c2f405dd214e790d22c93f6be1/tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad", size = 226533, upload-time = "2025-05-10T19:22:27.279Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", +] +dependencies = [ + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/38/5e2ecef5af2f4fd4a89bb8d6240de9458bab4d51a4cbd97aeb3a0cd618e2/tifffile-2026.6.1.tar.gz", hash = "sha256:626c892c0e899d959b9438e7c0e1491dc154a7fead1f1f37a991724a50eceba9", size = 429694, upload-time = "2026-05-31T23:57:12.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/59/208f71d70ddc6184f79b8c6d87d46eb7d7b12c19186a817dec9c9c3f3693/tifffile-2026.6.1-py3-none-any.whl", hash = "sha256:0d7382d2769b855b81ce358528e2b40c16d48aa39031746efa81215205332a8d", size = 267108, upload-time = "2026-05-31T23:57:10.597Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, + { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://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 = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/e7/19894fdb51c7dbaf94f5a79bb0871da0992e8e4241e579cb006da46d2e58/torch-2.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:94f0de129916f77b8dc2c7a8eff644cfeddfe59e39c9f55e9f6e17543410281d", size = 111178962, upload-time = "2026-07-08T16:05:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5c/b1d5de470c54e339b30a92d96683a71bcebd78f5f2a7fc714cd6dc6bbd68/torch-2.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ab4b69f3ee03a62a002cfbf77b1ca5e88aceb4ea64cb4388bb28f638ddbb045", size = 427198333, upload-time = "2026-07-08T16:05:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/50/c0/68a84105e1fcb8970144b388ff3d3e5dc15a3be28c1e247841f7d7247e41/torch-2.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c78b7b4d04461855a764cf01bae9a462bb88bc93defcfa11235cbc8fdf3e12c4", size = 526555154, upload-time = "2026-07-08T16:05:06.507Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c9/0bb9d097b03cbaf96bb75b15e867347b8e41bfcdfe0539452d17d9e63993/torch-2.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:2bd30b6b730d987fa386ce3898933762c5cb8cc82eb0535211d787cc3ce2dfeb", size = 122015602, upload-time = "2026-07-08T16:05:45.25Z" }, + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870, upload-time = "2026-07-08T16:05:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998, upload-time = "2026-07-08T16:04:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292, upload-time = "2026-07-08T16:04:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313, upload-time = "2026-07-08T16:03:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" }, +] + +[[package]] +name = "torchvision" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/df/1ba039ad6cfe6e69209c36766b9b6e8c6fe92481c6d4e4ca52296f5f699d/torchvision-0.28.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:2a1ef4b6f4bf5828b48cfad97372c8982db906830884b2868ba5c3df937a7d81", size = 1856019, upload-time = "2026-07-08T16:07:59.283Z" }, + { url = "https://files.pythonhosted.org/packages/88/ea/5c70ecf86f8e95174a85061cea78683a7bb7f422f09c3f3d4f30b7600fa9/torchvision-0.28.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:546fd85345cf8652f6cd099d4f9884b0ca5c2f3fae78689a21dd2f35ea6b622f", size = 7838211, upload-time = "2026-07-08T16:07:27.023Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/2f7ff1997d793e45d85fafa8374ee25348b7dae9ac521ba8751d7e1c75d5/torchvision-0.28.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6dfb0f45e2b4ceb4e76f158c3fbb5f44387099f3c466e3423a09ab665a194aba", size = 7669419, upload-time = "2026-07-08T16:07:41.648Z" }, + { url = "https://files.pythonhosted.org/packages/42/d0/2b3c30834ff23acd3854d0ff59bc580711f4b36d725de40105a852ed3719/torchvision-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:7fad44dc9582570c7d92c4487d36ac46998f40cc39b438e8b8f5111a935ce4e8", size = 3500355, upload-time = "2026-07-08T16:07:56.865Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017, upload-time = "2026-07-08T16:07:55.533Z" }, + { url = "https://files.pythonhosted.org/packages/27/be/1b9c5de9c655ca2df4a74100fa671a7b848532ff787e077ccde14a7dea2a/torchvision-0.28.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5a38bc6da3d72621be003400b66f66a2b4c6d644fde05f680c2cb7ca8cf8dd6c", size = 7841822, upload-time = "2026-07-08T16:07:49.207Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9b/f1e68e861d4462e3e195a642c2b448e7b7d3fad5f209487162b9a2133d9b/torchvision-0.28.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e80f543b22503d9415e126db5f0ff3917036925e38560ee6b9ae38c571a4002", size = 7670718, upload-time = "2026-07-08T16:07:46.525Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/1494610ff54cbb154beb55033cc2cd50f3de04dac132fa2dd00e4f2b2556/torchvision-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:9a45ea67235d965ef52187130d20002a4de20c54ea3d927a24286961d268dc37", size = 3814319, upload-time = "2026-07-08T16:07:37.153Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/c1cab1ecbb3ff1a380a3f99283db1dee61b8afe354f6352c643b65937130/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", size = 1856020, upload-time = "2026-07-08T16:07:52.182Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4c/95233776e2def960e5abb7a07931230a545f43717a56a1e1140162033598/torchvision-0.28.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5cf78ebc401ce64ae19b8c55de866bb836797d559a4de9c25ccbe74cfa642d3a", size = 7842127, upload-time = "2026-07-08T16:07:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/93/e4/e9b2495d0d57b9f60d63c57d0a910410a81b4b073bf70917bef815291119/torchvision-0.28.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:028a3d481b37d785605620d7cdad897064c5a55bae2aa1f2658766333e291940", size = 7675040, upload-time = "2026-07-08T16:07:58.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9c/55ed9cb6dfe3ee9c837df5cd0e758372e5829aa38b8dd71343aa632cc4e2/torchvision-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:87dc16b2df427c1318ad335f1e2be2b3b15b2cf20f7934c83b0505a48425ee5d", size = 4085785, upload-time = "2026-07-08T16:07:50.928Z" }, + { url = "https://files.pythonhosted.org/packages/20/55/08a726c14c67b37c8aca04b077766909f1c7ed23f76116884fe63b9bd033/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", size = 1856021, upload-time = "2026-07-08T16:07:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/40beacd53809194f5259e590d1afaeaa8ad57da15f77c646e6560bcc4616/torchvision-0.28.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bb6dd6918460ed89cc7644adcc2402991474d6933cf1ce92b390641cb233fddf", size = 7797014, upload-time = "2026-07-08T16:07:43.04Z" }, + { url = "https://files.pythonhosted.org/packages/32/db/062cdb5a84380a60439775311fff34d89229760d2a50680393dc18699956/torchvision-0.28.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad7b3a439265cc3739a4ab5b4c998c0e38ea99c0ee7ca4dea35c5d0b099ec237", size = 7674669, upload-time = "2026-07-08T16:07:38.91Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a6/b4081e2d04e1541abf82785ac9e5178a494c19330391f551356c8c18b7b3/torchvision-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:7e9dd6f60d6e15f8dc27d4f877fdb6002fc70d70272412135f1c2ff9cfa08d3b", size = 4157380, upload-time = "2026-07-08T16:07:40.22Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, +] + +[[package]] +name = "transformers" +version = "4.57.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/70/d42a739e8dfde3d92bb2fff5819cbf331fe9657323221e79415cd5eb65ee/transformers-4.57.3.tar.gz", hash = "sha256:df4945029aaddd7c09eec5cad851f30662f8bd1746721b34cc031d70c65afebc", size = 10139680, upload-time = "2025-11-25T15:51:30.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/6b/2f416568b3c4c91c96e5a365d164f8a4a4a88030aa8ab4644181fdadce97/transformers-4.57.3-py3-none-any.whl", hash = "sha256:c77d353a4851b1880191603d36acb313411d3577f6e2897814f333841f7003f4", size = 11993463, upload-time = "2025-11-25T15:51:26.493Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, +] + +[[package]] +name = "typer" +version = "0.26.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/f7/68adc395201b20b872d68e975386832e8005ffeacedd43a1d837a32815be/typer-0.26.8.tar.gz", hash = "sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e", size = 202097, upload-time = "2026-06-26T09:22:45.705Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/87/b9fd69c92c6102a066e1b86a35243f53e70bd4c709f2a26d9f4fee4f4dc0/typer-0.26.8-py3-none-any.whl", hash = "sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c", size = 122564, upload-time = "2026-06-26T09:22:44.72Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +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 = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + +[[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 = "uv" +version = "0.11.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/a2/bfd6755b40682ef7e775ddb9d52823dea6551352f4244106da4bad37cd3c/uv-0.11.28.tar.gz", hash = "sha256:df86cfd135542a833e9f84708b3b8dbaa987a3b9db85b267062db49ab639d242", size = 5985690, upload-time = "2026-07-07T23:12:47.095Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/5d/507b829e79353fe3dcb2779cde8f64497d9c99f9b08b18b8f55ee3bf1786/uv-0.11.28-py3-none-linux_armv6l.whl", hash = "sha256:ae5bbdb6150adcd625f5fa720b04abf2014247d878d1035f19751bb0e7274543", size = 25893158, upload-time = "2026-07-07T23:11:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/10/54/50c85a663ce723e061523ab4ac8b01b650077584e80950f9c93fd073979f/uv-0.11.28-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bb11d94cb848ff58af79e0bb5e4037cd324d27dbe2dabb7746db698b724a9a20", size = 25041511, upload-time = "2026-07-07T23:11:58.885Z" }, + { url = "https://files.pythonhosted.org/packages/32/e1/49968cab72f16a7d6c45d095d319f9efbe8ce05f3f15c5f7104493694289/uv-0.11.28-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e9eb317b1cdb249887df77ac232d8a9448f26858b2399f9f2949c6a7b9bedf88", size = 23570471, upload-time = "2026-07-07T23:12:01.832Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4d/c9fe448dcd5cf65a5f054517aa42551b7f0920710a6891d98af321a06b22/uv-0.11.28-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:041e4b80bebc58d7142ac9394370cacd73185fd8d066d6675d14707d83408f6d", size = 25594677, upload-time = "2026-07-07T23:12:04.597Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/4c0c71075ba66cc594f856cbd98844058fcb53cb4dd8a6fccda8419562bb/uv-0.11.28-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:185416a5316df8c5442b47178349f1f27fc1034468670ac1fb499eae3b25bd68", size = 25427944, upload-time = "2026-07-07T23:12:07.226Z" }, + { url = "https://files.pythonhosted.org/packages/6c/77/50fef66f4e26bf771429e1d88f849476d8ed21f0fb0708acaa53dc5772a5/uv-0.11.28-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4a9fe246cb2882532277f5d5e5bd8a59462981462a2f98426f35ecfca82460e", size = 25448458, upload-time = "2026-07-07T23:12:10.894Z" }, + { url = "https://files.pythonhosted.org/packages/d0/93/720f45af65ebda460166dc64f3318acd65f7bd3a8e326fbd21810fd920ee/uv-0.11.28-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f7ce6f6015a3e857bc6a663514afa62856b669ee5c1bd120e4c58ac2ef5513d", size = 26917218, upload-time = "2026-07-07T23:12:13.802Z" }, + { url = "https://files.pythonhosted.org/packages/cd/27/a9b68a15a5fe8db7103bea514c2adb79e9b1114fc8dc96fb39dbd7a5b898/uv-0.11.28-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b3d0ea11e83b373a2166b82dd0864f5677fbadf98db64541ab2e59c42968905", size = 27771542, upload-time = "2026-07-07T23:12:16.519Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fd/208607a7f5f86188775387fe0839ef97cf8d013e8d0e909140b7fdb7d0d1/uv-0.11.28-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c60294e3be4fa203a04015fc02ac8a31d936e86fde06dcb43c7f8f22661dfff", size = 26972190, upload-time = "2026-07-07T23:12:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/62273ee6c9fbebccd8248c153b44870f81ebf5267c31edf4c095d78537fb/uv-0.11.28-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fe42df9f42056037473f3876adec1615709b57d3470ed39178ff420f3afb9f", size = 27127688, upload-time = "2026-07-07T23:12:22.43Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8c/b15212904e6f0aa4a3709dc86838c6fa070fe97c7e96b3f10174a26b16e3/uv-0.11.28-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:fab3c31007a611866475824a666f5a721bf0c9335db806355a97fcfba2a6bbb7", size = 25715221, upload-time = "2026-07-07T23:12:25.144Z" }, + { url = "https://files.pythonhosted.org/packages/64/35/b83b7c599474aaf1277c2224c09679640c2320562155c4b6ece1c6f014c1/uv-0.11.28-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:2e91eb8a0b00d5f4427195fc818bcaa4d8bb4fccb79f4e973e74802419ab06ca", size = 26392793, upload-time = "2026-07-07T23:12:27.848Z" }, + { url = "https://files.pythonhosted.org/packages/81/49/8093318206dee51b5cfcabbf110892ff63cfd897a5df002e2d8b61350fe6/uv-0.11.28-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:47e3f12fe6f5c80a01639d8df36efde7bdddfc3bbc52250df623547d8d393105", size = 26522809, upload-time = "2026-07-07T23:12:30.757Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/b26d82e9297c29c201f61698ee56bba956f94953b23089532d026a97d93f/uv-0.11.28-py3-none-musllinux_1_1_i686.whl", hash = "sha256:d01c7c665511c047f350e587b8b6557c96b61b2eddafbcd8964f0cc2f5b9afbe", size = 26156793, upload-time = "2026-07-07T23:12:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c2/163e89424668d6c01499efbe85a854ad38f07834bde3f2b16df159eab1d5/uv-0.11.28-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:3fcfda468448093f4d5961ca8c068b0aeec2d02f7226d58ee8513321a929fe4f", size = 27327614, upload-time = "2026-07-07T23:12:36.252Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c9/db4cb824777d013272ccfa77db07a4d12bf1584899458c1917a4b5a4069d/uv-0.11.28-py3-none-win32.whl", hash = "sha256:692edef9cf1d2dd69bb9d9fc01f281a82610547900ce227a3cb269cdf988b5ce", size = 24665179, upload-time = "2026-07-07T23:12:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/40/bc/d67b18cddd54c503c7bad2b189a47fd7a1d07ea10b9212624f892b985498/uv-0.11.28-py3-none-win_amd64.whl", hash = "sha256:f4fcf2c8d9f1444b900e6b8dbbb828825fb76eca01acd18aeaa5c90240408cda", size = 27603677, upload-time = "2026-07-07T23:12:41.985Z" }, + { url = "https://files.pythonhosted.org/packages/57/94/dc31a771eac989973219c730552dbcf5bf7ea6652dba4ba89b1bbdc75a80/uv-0.11.28-py3-none-win_arm64.whl", hash = "sha256:e94560995737c50525d586da553521fbafe9ef06641e7d885db4b270f53ee84d", size = 25839294, upload-time = "2026-07-07T23:12:44.893Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.51.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/d9/b477fddb68840b570af8b22afe9b035cbc277b5fb7b33dea390617a8b10f/virtualenv-21.6.1.tar.gz", hash = "sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128", size = 5526620, upload-time = "2026-07-10T19:33:53.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/7c/4e7225d46d634a0d8d534dd8a6ce0c319d09b4d0cf0337eb314ca4789d8c/virtualenv-21.6.1-py3-none-any.whl", hash = "sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b", size = 5506392, upload-time = "2026-07-10T19:33:51.629Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/5a/2bf22ecb24916983bf1cc0095e7dea2741d14d6553b0d6a2ac8bc96eca93/watchfiles-1.2.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9", size = 400471, upload-time = "2026-05-18T04:31:08.908Z" }, + { url = "https://files.pythonhosted.org/packages/55/70/dea1f6a0e76607841a60fb51af150e70124864673f61704abb62b90cdcc7/watchfiles-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4", size = 394599, upload-time = "2026-05-18T04:30:19.845Z" }, + { url = "https://files.pythonhosted.org/packages/18/52/752dcc7dc817baef5e89518732925795ce52e36a683a9a3c9fb68b21504e/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631", size = 455458, upload-time = "2026-05-18T04:30:29.126Z" }, + { url = "https://files.pythonhosted.org/packages/12/48/366ebbb22fcc504c2f72b45f0b7e72f40a18795cc01752c16066d597b67a/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994", size = 460513, upload-time = "2026-05-18T04:31:40.85Z" }, + { url = "https://files.pythonhosted.org/packages/ad/44/1f9e1b15e7a729062e0d0c3d0d7225ea4ab98b2267ef87287153be2495fc/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e", size = 493616, upload-time = "2026-05-18T04:30:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/7e/55/8b1086dcc8a1d6a697a62767bd7ea368e74c61c6fd171683cfe24a3fe5d2/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19", size = 573154, upload-time = "2026-05-18T04:30:37.903Z" }, + { url = "https://files.pythonhosted.org/packages/14/7a/242f400cc77fafa7b18d53d19d9cb64fc6a6f61f28c55913bae7c674d92a/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8", size = 467046, upload-time = "2026-05-18T04:30:41.869Z" }, + { url = "https://files.pythonhosted.org/packages/02/c8/79eee650c62d2c186598489814468e389b5def0ebe755399ff645b35b1b2/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07", size = 457100, upload-time = "2026-05-18T04:31:13.064Z" }, + { url = "https://files.pythonhosted.org/packages/81/36/519f6dbb7a95e4fe7c1513ed25b1520295ef9905a27f1f2226a73892bfb7/watchfiles-1.2.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551", size = 467038, upload-time = "2026-05-18T04:30:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/2f/12/951af6b9f89097e02511122258402cb3578443021930b70cf968d6310dc0/watchfiles-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310", size = 632563, upload-time = "2026-05-18T04:30:11.539Z" }, + { url = "https://files.pythonhosted.org/packages/28/cc/0cba1f0a6117b7ec117271bdc3cb3a5a252005959755a2c09a745e0942cc/watchfiles-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df", size = 660851, upload-time = "2026-05-18T04:31:53.186Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/26347558cc8bf6877845e66b315f644d03c173906aa09e233a3f4fd23928/watchfiles-1.2.0-cp310-cp310-win32.whl", hash = "sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1", size = 277023, upload-time = "2026-05-18T04:30:18.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/68/a5e67b6b68e94f4c1511d61c46c55eba0737583620b6febf194c7b9cc23f/watchfiles-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d", size = 290107, upload-time = "2026-05-18T04:32:09.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, + { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, + { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, + { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, +] + +[[package]] +name = "wcmatch" +version = "11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/25/1da725838132221e33568973da484ff43813662ccc06ebf7f6e3abddfcd5/wcmatch-11.0.tar.gz", hash = "sha256:55d95c2447789712774b198ceec72939e88b5618f1f8f0a9b605bf7740b63b96", size = 141360, upload-time = "2026-07-10T05:50:24.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/12/f38b6fee116274d7221743caab07d765032e1370bb54cad8714f87aeb0e8/wcmatch-11.0-py3-none-any.whl", hash = "sha256:3a5977ace27e075eef67eb03d539563f1a19018b62881949a42932cf66926934", size = 42914, upload-time = "2026-07-10T05:50:22.995Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +]