esandorfi commited on
Commit
eb8df9a
·
1 Parent(s): 5ac14c5

Evaluation

Browse files
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ data_eval/
4
+ data_results/
Makefile CHANGED
@@ -1,4 +1,4 @@
1
- .PHONY: help docker-build docker-run local-install local-run local-test local-test-integration eval-photo-v1 eval-dance-v1
2
 
3
  help:
4
  @echo "---------------------------------------------------"
@@ -9,8 +9,12 @@ help:
9
  @echo " local-run Run API locally on :7860"
10
  @echo " local-test Run unit tests"
11
  @echo " local-test-integration Run integration tests"
12
- @echo " eval-photo-v1 Run eval on personal-photos-lite-v1"
13
- @echo " eval-dance-v1 Run eval on scene-dance-formation-group-v1"
 
 
 
 
14
 
15
  docker-build:
16
  docker build -t photo-classification .
@@ -30,14 +34,34 @@ local-test:
30
  local-test-integration:
31
  uv run pytest -q -m integration
32
 
33
- eval-photo-v1:
34
  uv run python scripts/classify_dataset.py \
35
  --label-set label-dataset/personal-photos-lite-v1.json \
36
- --images label-dataset \
37
- --csv results/personal-photos-lite-v1.csv
38
 
39
- eval-dance-v1:
40
  uv run python scripts/classify_dataset.py \
41
  --label-set label-dataset/scene-dance-formation-group-v1.json \
42
- --images label-dataset \
43
- --csv results/scene-dance-formation-group-v1.csv
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .PHONY: help docker-build docker-run local-install local-run local-test local-test-integration eval-photo-v1 eval-dance-v1 eval-photo-matrix eval-dance-matrix data-photos data-dance
2
 
3
  help:
4
  @echo "---------------------------------------------------"
 
9
  @echo " local-run Run API locally on :7860"
10
  @echo " local-test Run unit tests"
11
  @echo " local-test-integration Run integration tests"
12
+ @echo " eval-photo Run eval on personal-photos-lite-v1"
13
+ @echo " eval-dance Run eval on scene-dance-formation-group-v1"
14
+ @echo " eval-photo-matrix Run eval for all personal-photos label sets"
15
+ @echo " eval-dance-matrix Run eval for all dance label sets"
16
+ @echo " data-photos Download + normalize photo eval dataset"
17
+ @echo " data-dance Download + normalize dance eval dataset"
18
 
19
  docker-build:
20
  docker build -t photo-classification .
 
34
  local-test-integration:
35
  uv run pytest -q -m integration
36
 
37
+ eval-photo:
38
  uv run python scripts/classify_dataset.py \
39
  --label-set label-dataset/personal-photos-lite-v1.json \
40
+ --images data_eval/photos/normalized \
41
+ --csv data_results/personal-photos-lite-v1.csv
42
 
43
+ eval-dance:
44
  uv run python scripts/classify_dataset.py \
45
  --label-set label-dataset/scene-dance-formation-group-v1.json \
46
+ --images data_eval/dance/normalized \
47
+ --csv data_results/scene-dance-formation-group-v1.csv
48
+
49
+ eval-photo-matrix:
50
+ uv run python scripts/eval_matrix.py \
51
+ --label-sets "label-dataset/personal-photos-*.json" \
52
+ --images data_eval/photos/normalized \
53
+ --out-dir data_results \
54
+ --summary-csv data_results/personal-photos-summary.csv
55
+
56
+ eval-dance-matrix:
57
+ uv run python scripts/eval_matrix.py \
58
+ --label-sets "label-dataset/scene-dance-*.json" \
59
+ --images data_eval/dance/normalized \
60
+ --out-dir data_results \
61
+ --summary-csv data_results/dance-summary.csv
62
+
63
+ data-photos:
64
+ uv run python scripts/dataset_prep.py --out data_eval --target photos --n 50 --normalize
65
+
66
+ data-dance:
67
+ uv run python scripts/dataset_prep.py --out data_eval --target dance --n 50 --normalize
README.md CHANGED
@@ -161,13 +161,58 @@ local images and capture timings:
161
  uv run python scripts/classify_dataset.py \
162
  --label-set label-dataset/personal-photos-lite-v1.json \
163
  --images /path/to/images \
164
- --csv results/summary.csv
165
  ```
166
 
167
  Makefile shortcuts:
168
 
169
  - `make eval-photo-v1`
170
  - `make eval-dance-v1`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  ## Project layout
173
 
 
161
  uv run python scripts/classify_dataset.py \
162
  --label-set label-dataset/personal-photos-lite-v1.json \
163
  --images /path/to/images \
164
+ --csv data_results/summary.csv
165
  ```
166
 
167
  Makefile shortcuts:
168
 
169
  - `make eval-photo-v1`
170
  - `make eval-dance-v1`
171
+ - `make eval-photo-matrix`
172
+ - `make eval-dance-matrix`
173
+
174
+ Matrix eval (multiple label sets against the same images):
175
+
176
+ ```bash
177
+ uv run python scripts/eval_matrix.py \
178
+ --label-sets "label-dataset/personal-photos-*.json" \
179
+ --images data_eval/photos/normalized \
180
+ --out-dir data_results \
181
+ --summary-csv data_results/personal-photos-summary.csv
182
+ ```
183
+
184
+ ## Eval datasets (download schema)
185
+
186
+ We use a simple, reproducible layout for evaluation datasets created by `scripts/dataset_prep.py`:
187
+
188
+ ```
189
+ data_eval/
190
+ photos/
191
+ raw/ # downloaded originals
192
+ normalized/ # normalized JPEGs
193
+ dance/
194
+ raw/ # downloaded originals
195
+ normalized/ # normalized JPEGs
196
+ ```
197
+
198
+ Download and normalize (recommended):
199
+
200
+ ```bash
201
+ uv run python scripts/dataset_prep.py --out data_eval --target photos --n 50 --normalize
202
+ uv run python scripts/dataset_prep.py --out data_eval --target dance --n 50 --normalize
203
+ ```
204
+
205
+ Reset existing files and start fresh:
206
+
207
+ ```bash
208
+ uv run python scripts/dataset_prep.py --out data_eval --target photos --n 50 --normalize --reset
209
+ ```
210
+
211
+ Normalize your own folder into the same schema:
212
+
213
+ ```bash
214
+ uv run python scripts/dataset_prep.py --normalize-only --in-dir /path/to/images --out data_eval/photos
215
+ ```
216
 
217
  ## Project layout
218
 
label-dataset/{ersonal-photos-large-v1.json → personal-photos-large-v1.json} RENAMED
File without changes
pyproject.toml CHANGED
@@ -20,8 +20,11 @@ dependencies = [
20
  [project.optional-dependencies]
21
  dev = [
22
  "click==8.1.7",
 
23
  "httpx==0.27.2",
24
  "pytest==8.3.2",
 
 
25
  ]
26
 
27
  [tool.setuptools]
 
20
  [project.optional-dependencies]
21
  dev = [
22
  "click==8.1.7",
23
+ "datasets==2.21.0",
24
  "httpx==0.27.2",
25
  "pytest==8.3.2",
26
+ "requests==2.32.3",
27
+ "tqdm==4.66.5",
28
  ]
29
 
30
  [tool.setuptools]
scripts/classify_dataset.py CHANGED
@@ -5,6 +5,7 @@ import base64
5
  import csv
6
  import json
7
  from dataclasses import dataclass
 
8
  from pathlib import Path
9
  from typing import Iterable
10
 
@@ -21,7 +22,9 @@ class Config:
21
  top_k: int
22
  activate: bool
23
  limit: int
 
24
  csv_path: Path | None
 
25
 
26
 
27
  def iter_images(paths: list[Path]) -> Iterable[Path]:
@@ -63,11 +66,15 @@ def classify_one(client: httpx.Client, label_set_hash: str, image: Path, domain_
63
 
64
 
65
  def to_row(image: Path, data: dict) -> dict[str, str]:
 
 
66
  return {
67
  "image": str(image),
68
  "label_set_hash": data.get("label_set_hash", ""),
69
  "model_id": data.get("model_id", ""),
70
  "chosen_domains": "|".join(data.get("chosen_domains", [])),
 
 
71
  "elapsed_ms": str(data.get("elapsed_ms", "")),
72
  "elapsed_domain_ms": str(data.get("elapsed_domain_ms", "")),
73
  "elapsed_labels_ms": str(data.get("elapsed_labels_ms", "")),
@@ -79,18 +86,54 @@ def write_csv(path: Path, rows: list[dict[str, str]]) -> None:
79
  with path.open("w", newline="", encoding="utf-8") as f:
80
  writer = csv.DictWriter(
81
  f,
82
- fieldnames=[
83
- "image",
84
- "label_set_hash",
85
- "model_id",
86
- "chosen_domains",
87
- "elapsed_ms",
88
- "elapsed_domain_ms",
89
- "elapsed_labels_ms",
90
- ],
91
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  writer.writeheader()
93
- writer.writerows(rows)
 
 
 
 
94
 
95
 
96
  def run(cfg: Config) -> int:
@@ -113,6 +156,15 @@ def run(cfg: Config) -> int:
113
 
114
  if cfg.csv_path and rows:
115
  write_csv(cfg.csv_path, rows)
 
 
 
 
 
 
 
 
 
116
 
117
  return 0
118
 
@@ -125,7 +177,9 @@ def run(cfg: Config) -> int:
125
  @click.option("--top-k", default=5, show_default=True, type=int)
126
  @click.option("--activate", is_flag=True, default=False)
127
  @click.option("--limit", default=0, show_default=True, type=int)
 
128
  @click.option("--csv", "csv_path", type=click.Path(path_type=Path))
 
129
  def cli(
130
  api: str,
131
  label_set: Path,
@@ -134,7 +188,9 @@ def cli(
134
  top_k: int,
135
  activate: bool,
136
  limit: int,
 
137
  csv_path: Path | None,
 
138
  ) -> None:
139
  cfg = Config(
140
  api=api,
@@ -144,7 +200,9 @@ def cli(
144
  top_k=top_k,
145
  activate=activate,
146
  limit=limit,
 
147
  csv_path=csv_path,
 
148
  )
149
  raise SystemExit(run(cfg))
150
 
 
5
  import csv
6
  import json
7
  from dataclasses import dataclass
8
+ from datetime import datetime, timezone
9
  from pathlib import Path
10
  from typing import Iterable
11
 
 
22
  top_k: int
23
  activate: bool
24
  limit: int
25
+ out_dir: Path
26
  csv_path: Path | None
27
+ summary: bool
28
 
29
 
30
  def iter_images(paths: list[Path]) -> Iterable[Path]:
 
66
 
67
 
68
  def to_row(image: Path, data: dict) -> dict[str, str]:
69
+ domain_hits = data.get("domain_hits", [])
70
+ label_hits = data.get("label_hits", [])
71
  return {
72
  "image": str(image),
73
  "label_set_hash": data.get("label_set_hash", ""),
74
  "model_id": data.get("model_id", ""),
75
  "chosen_domains": "|".join(data.get("chosen_domains", [])),
76
+ "domain_hits": "|".join(f"{d.get('id')}:{d.get('score'):.4f}" for d in domain_hits),
77
+ "label_hits": "|".join(f"{l.get('id')}:{l.get('score'):.4f}" for l in label_hits),
78
  "elapsed_ms": str(data.get("elapsed_ms", "")),
79
  "elapsed_domain_ms": str(data.get("elapsed_domain_ms", "")),
80
  "elapsed_labels_ms": str(data.get("elapsed_labels_ms", "")),
 
86
  with path.open("w", newline="", encoding="utf-8") as f:
87
  writer = csv.DictWriter(
88
  f,
89
+ fieldnames=[
90
+ "image",
91
+ "label_set_hash",
92
+ "model_id",
93
+ "chosen_domains",
94
+ "domain_hits",
95
+ "label_hits",
96
+ "elapsed_ms",
97
+ "elapsed_domain_ms",
98
+ "elapsed_labels_ms",
99
+ ],
100
+ )
101
+ writer.writeheader()
102
+ writer.writerows(rows)
103
+
104
+
105
+ def _percentile(values: list[int], q: float) -> int:
106
+ if not values:
107
+ return 0
108
+ values = sorted(values)
109
+ idx = int(round((len(values) - 1) * q))
110
+ return values[idx]
111
+
112
+
113
+ def write_summary(path: Path, rows: list[dict[str, str]]) -> None:
114
+ times: list[int] = []
115
+ for row in rows:
116
+ try:
117
+ times.append(int(row["elapsed_ms"]))
118
+ except Exception:
119
+ continue
120
+ summary = {
121
+ "count": str(len(times)),
122
+ "avg_elapsed_ms": str(int(sum(times) / max(1, len(times)))),
123
+ "p50_elapsed_ms": str(_percentile(times, 0.50)),
124
+ "p90_elapsed_ms": str(_percentile(times, 0.90)),
125
+ "p95_elapsed_ms": str(_percentile(times, 0.95)),
126
+ "p99_elapsed_ms": str(_percentile(times, 0.99)),
127
+ }
128
+ path.parent.mkdir(parents=True, exist_ok=True)
129
+ with path.open("w", newline="", encoding="utf-8") as f:
130
+ writer = csv.DictWriter(f, fieldnames=list(summary.keys()))
131
  writer.writeheader()
132
+ writer.writerow(summary)
133
+
134
+
135
+ def _timestamp() -> str:
136
+ return datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
137
 
138
 
139
  def run(cfg: Config) -> int:
 
156
 
157
  if cfg.csv_path and rows:
158
  write_csv(cfg.csv_path, rows)
159
+ elif rows:
160
+ ts = _timestamp()
161
+ out_path = cfg.out_dir / f"{cfg.label_set.stem}_{ts}.csv"
162
+ write_csv(out_path, rows)
163
+
164
+ if cfg.summary:
165
+ ts = _timestamp()
166
+ summary_path = cfg.out_dir / f"{cfg.label_set.stem}_summary_{ts}.csv"
167
+ write_summary(summary_path, rows)
168
 
169
  return 0
170
 
 
177
  @click.option("--top-k", default=5, show_default=True, type=int)
178
  @click.option("--activate", is_flag=True, default=False)
179
  @click.option("--limit", default=0, show_default=True, type=int)
180
+ @click.option("--out-dir", default="data_results", show_default=True, type=click.Path(path_type=Path))
181
  @click.option("--csv", "csv_path", type=click.Path(path_type=Path))
182
+ @click.option("--summary", is_flag=True, default=False)
183
  def cli(
184
  api: str,
185
  label_set: Path,
 
188
  top_k: int,
189
  activate: bool,
190
  limit: int,
191
+ out_dir: Path,
192
  csv_path: Path | None,
193
+ summary: bool,
194
  ) -> None:
195
  cfg = Config(
196
  api=api,
 
200
  top_k=top_k,
201
  activate=activate,
202
  limit=limit,
203
+ out_dir=out_dir,
204
  csv_path=csv_path,
205
+ summary=summary,
206
  )
207
  raise SystemExit(run(cfg))
208
 
scripts/dataset_prep.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import io
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Iterable, Optional
8
+
9
+ import click
10
+ import requests
11
+ from PIL import Image, ImageOps
12
+ from tqdm import tqdm
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Config:
17
+ out_dir: Path
18
+ target: str
19
+ n: int
20
+ seed: int
21
+ normalize: bool
22
+ max_side: int
23
+ jpeg_quality: int
24
+ normalize_only: bool
25
+ in_dir: Optional[Path]
26
+ reset: bool
27
+
28
+
29
+ def ensure_dir(path: Path) -> None:
30
+ path.mkdir(parents=True, exist_ok=True)
31
+
32
+
33
+ def normalize_image_bytes(img_bytes: bytes, max_side: int, jpeg_quality: int) -> bytes:
34
+ with Image.open(io.BytesIO(img_bytes)) as im:
35
+ im = ImageOps.exif_transpose(im)
36
+ im = im.convert("RGB")
37
+
38
+ w, h = im.size
39
+ scale = max_side / float(max(w, h))
40
+ if scale < 1.0:
41
+ new_w = max(1, int(round(w * scale)))
42
+ new_h = max(1, int(round(h * scale)))
43
+ im = im.resize((new_w, new_h), Image.Resampling.LANCZOS)
44
+
45
+ out = io.BytesIO()
46
+ im.save(out, format="JPEG", quality=jpeg_quality, optimize=True, progressive=True)
47
+ return out.getvalue()
48
+
49
+
50
+ def download_url(url: str, timeout_s: float = 20.0) -> Optional[bytes]:
51
+ try:
52
+ resp = requests.get(url, timeout=timeout_s)
53
+ resp.raise_for_status()
54
+ return resp.content
55
+ except Exception:
56
+ return None
57
+
58
+
59
+ def save_bytes(path: Path, data: bytes) -> None:
60
+ ensure_dir(path.parent)
61
+ path.write_bytes(data)
62
+
63
+
64
+ def iter_images(paths: Iterable[Path]) -> Iterable[Path]:
65
+ exts = {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".tif", ".tiff"}
66
+ for path in paths:
67
+ if path.is_dir():
68
+ for p in sorted(path.rglob("*")):
69
+ if p.is_file() and p.suffix.lower() in exts:
70
+ yield p
71
+ elif path.is_file() and path.suffix.lower() in exts:
72
+ yield path
73
+
74
+
75
+ def normalize_existing(in_dir: Path, out_dir: Path, max_side: int, jpeg_quality: int) -> None:
76
+ files = list(iter_images([in_dir]))
77
+ ensure_dir(out_dir)
78
+
79
+ for p in tqdm(files, desc=f"Normalizing {in_dir.name}"):
80
+ try:
81
+ raw = p.read_bytes()
82
+ norm = normalize_image_bytes(raw, max_side=max_side, jpeg_quality=jpeg_quality)
83
+ rel = p.relative_to(in_dir)
84
+ out_path = (out_dir / rel).with_suffix(".jpg")
85
+ save_bytes(out_path, norm)
86
+ except Exception:
87
+ continue
88
+
89
+ print(f"Normalized images written to: {out_dir}")
90
+
91
+
92
+ def _next_index(dir_path: Path, prefix: str) -> int:
93
+ if not dir_path.exists():
94
+ return 0
95
+ max_idx = -1
96
+ for p in dir_path.glob(f"{prefix}_*.bin"):
97
+ stem = p.stem
98
+ try:
99
+ idx = int(stem.split("_")[-1])
100
+ except ValueError:
101
+ continue
102
+ max_idx = max(max_idx, idx)
103
+ return max_idx + 1
104
+
105
+
106
+ def download_photos_open_images(
107
+ out_dir: Path,
108
+ n: int,
109
+ seed: int,
110
+ normalize: bool,
111
+ max_side: int,
112
+ jpeg_quality: int,
113
+ reset: bool,
114
+ ) -> None:
115
+ from datasets import load_dataset
116
+
117
+ ds = load_dataset("bitmind/open-images-v7", split="train", streaming=True)
118
+
119
+ saved = 0
120
+
121
+ raw_dir = out_dir / "photos" / "raw"
122
+ norm_dir = out_dir / "photos" / "normalized"
123
+ if reset:
124
+ for p in raw_dir.glob("openimages_*.bin"):
125
+ p.unlink()
126
+ for p in norm_dir.glob("openimages_*.jpg"):
127
+ p.unlink()
128
+ start_idx = _next_index(raw_dir, "openimages")
129
+
130
+ for row in tqdm(ds, desc="Streaming Open Images V7"):
131
+ if saved >= n:
132
+ break
133
+ url = row.get("image_url") or row.get("url") or row.get("imageUrl") or row.get("ImageURL")
134
+ if not url:
135
+ continue
136
+
137
+ img = download_url(url)
138
+ if not img:
139
+ continue
140
+
141
+ idx = start_idx + saved
142
+ raw_name = f"openimages_{idx:06d}.bin"
143
+ save_bytes(raw_dir / raw_name, img)
144
+
145
+ if normalize:
146
+ norm = normalize_image_bytes(img, max_side=max_side, jpeg_quality=jpeg_quality)
147
+ norm_name = f"openimages_{idx:06d}.jpg"
148
+ save_bytes(norm_dir / norm_name, norm)
149
+ saved += 1
150
+
151
+ print(f"Saved {saved} images to: {raw_dir}")
152
+ if normalize:
153
+ print(f"Normalized images written to: {norm_dir}")
154
+
155
+
156
+ def download_dance_x_dance(
157
+ out_dir: Path,
158
+ n: int,
159
+ seed: int,
160
+ normalize: bool,
161
+ max_side: int,
162
+ jpeg_quality: int,
163
+ reset: bool,
164
+ ) -> None:
165
+ from datasets import load_dataset
166
+
167
+ ds = load_dataset("MCG-NJU/X-Dance", split="train", streaming=True)
168
+
169
+ raw_dir = out_dir / "dance" / "raw"
170
+ norm_dir = out_dir / "dance" / "normalized"
171
+ if reset:
172
+ for p in raw_dir.glob("xdance_*.bin"):
173
+ p.unlink()
174
+ for p in norm_dir.glob("xdance_*.jpg"):
175
+ p.unlink()
176
+ start_idx = _next_index(raw_dir, "xdance")
177
+
178
+ saved = 0
179
+ for row in tqdm(ds, desc="Streaming X-Dance"):
180
+ if saved >= n:
181
+ break
182
+ img_obj = row.get("image")
183
+ img_bytes: Optional[bytes] = None
184
+ if img_obj is not None and hasattr(img_obj, "convert"):
185
+ out = io.BytesIO()
186
+ img_obj.convert("RGB").save(out, format="PNG")
187
+ img_bytes = out.getvalue()
188
+ else:
189
+ url = row.get("image_url") or row.get("url")
190
+ if url:
191
+ img_bytes = download_url(url)
192
+
193
+ if not img_bytes:
194
+ continue
195
+
196
+ idx = start_idx + saved
197
+ raw_name = f"xdance_{idx:06d}.bin"
198
+ save_bytes(raw_dir / raw_name, img_bytes)
199
+
200
+ if normalize:
201
+ norm = normalize_image_bytes(img_bytes, max_side=max_side, jpeg_quality=jpeg_quality)
202
+ norm_name = f"xdance_{idx:06d}.jpg"
203
+ save_bytes(norm_dir / norm_name, norm)
204
+ saved += 1
205
+
206
+ print(f"Saved {saved} images to: {raw_dir}")
207
+ if normalize:
208
+ print(f"Normalized images written to: {norm_dir}")
209
+
210
+
211
+ def run(cfg: Config) -> None:
212
+ if cfg.normalize_only:
213
+ if not cfg.in_dir:
214
+ raise SystemExit("--in-dir is required with --normalize-only")
215
+ normalize_existing(cfg.in_dir, cfg.out_dir, cfg.max_side, cfg.jpeg_quality)
216
+ return
217
+
218
+ ensure_dir(cfg.out_dir)
219
+ if cfg.target == "photos":
220
+ download_photos_open_images(
221
+ out_dir=cfg.out_dir,
222
+ n=cfg.n,
223
+ seed=cfg.seed,
224
+ normalize=cfg.normalize,
225
+ max_side=cfg.max_side,
226
+ jpeg_quality=cfg.jpeg_quality,
227
+ reset=cfg.reset,
228
+ )
229
+ else:
230
+ download_dance_x_dance(
231
+ out_dir=cfg.out_dir,
232
+ n=cfg.n,
233
+ seed=cfg.seed,
234
+ normalize=cfg.normalize,
235
+ max_side=cfg.max_side,
236
+ jpeg_quality=cfg.jpeg_quality,
237
+ reset=cfg.reset,
238
+ )
239
+
240
+
241
+ @click.command()
242
+ @click.option("--out", "out_dir", required=True, type=click.Path(path_type=Path))
243
+ @click.option("--target", type=click.Choice(["photos", "dance"], case_sensitive=False), required=True)
244
+ @click.option("--n", default=500, show_default=True, type=int)
245
+ @click.option("--seed", default=0, show_default=True, type=int)
246
+ @click.option("--normalize", is_flag=True, default=False)
247
+ @click.option("--max-side", default=512, show_default=True, type=int)
248
+ @click.option("--jpeg-quality", default=92, show_default=True, type=int)
249
+ @click.option("--normalize-only", is_flag=True, default=False)
250
+ @click.option("--in-dir", type=click.Path(path_type=Path))
251
+ @click.option("--reset", is_flag=True, default=False, help="Delete existing raw/normalized files before download.")
252
+ def cli(
253
+ out_dir: Path,
254
+ target: str,
255
+ n: int,
256
+ seed: int,
257
+ normalize: bool,
258
+ max_side: int,
259
+ jpeg_quality: int,
260
+ normalize_only: bool,
261
+ in_dir: Optional[Path],
262
+ reset: bool,
263
+ ) -> None:
264
+ cfg = Config(
265
+ out_dir=out_dir,
266
+ target=target,
267
+ n=n,
268
+ seed=seed,
269
+ normalize=normalize,
270
+ max_side=max_side,
271
+ jpeg_quality=jpeg_quality,
272
+ normalize_only=normalize_only,
273
+ in_dir=in_dir,
274
+ reset=reset,
275
+ )
276
+ run(cfg)
277
+
278
+
279
+ if __name__ == "__main__":
280
+ cli()
scripts/eval_matrix.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import csv
5
+ import json
6
+ from dataclasses import dataclass
7
+ import base64
8
+ from pathlib import Path
9
+ from typing import Iterable
10
+
11
+ import click
12
+ import httpx
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Config:
17
+ api: str
18
+ label_sets: list[Path]
19
+ images: list[Path]
20
+ domain_top_n: int
21
+ top_k: int
22
+ out_dir: Path
23
+ summary_csv: Path | None
24
+
25
+
26
+ def iter_images(paths: Iterable[Path]) -> Iterable[Path]:
27
+ exts = {".jpg", ".jpeg", ".png", ".webp"}
28
+ for path in paths:
29
+ if path.is_dir():
30
+ for p in sorted(path.rglob("*")):
31
+ if p.is_file() and p.suffix.lower() in exts:
32
+ yield p
33
+ elif path.is_file() and path.suffix.lower() in exts:
34
+ yield path
35
+
36
+
37
+ def expand_label_sets(paths: Iterable[str]) -> list[Path]:
38
+ out: list[Path] = []
39
+ for raw in paths:
40
+ p = Path(raw)
41
+ if any(ch in raw for ch in ["*", "?", "["]):
42
+ out.extend(sorted(Path().glob(raw)))
43
+ else:
44
+ out.append(p)
45
+ return [p for p in out if p.is_file()]
46
+
47
+
48
+ def upload_label_set(client: httpx.Client, label_set: Path) -> str:
49
+ payload = json.loads(label_set.read_text())
50
+ r = client.post("/api/v1/label-sets", json=payload)
51
+ r.raise_for_status()
52
+ return r.json()["label_set_hash"]
53
+
54
+
55
+ def classify_one(
56
+ client: httpx.Client,
57
+ label_set_hash: str,
58
+ image_b64: str,
59
+ domain_top_n: int,
60
+ top_k: int,
61
+ ) -> dict:
62
+ payload = {
63
+ "image_base64": image_b64,
64
+ "domain_top_n": domain_top_n,
65
+ "top_k": top_k,
66
+ }
67
+ r = client.post(f"/api/v1/classify?label_set_hash={label_set_hash}", json=payload)
68
+ r.raise_for_status()
69
+ return r.json()
70
+
71
+
72
+ def to_row(label_set: Path, image: Path, data: dict) -> dict[str, str]:
73
+ return {
74
+ "label_set": label_set.name,
75
+ "image": str(image),
76
+ "label_set_hash": data.get("label_set_hash", ""),
77
+ "model_id": data.get("model_id", ""),
78
+ "chosen_domains": "|".join(data.get("chosen_domains", [])),
79
+ "elapsed_ms": str(data.get("elapsed_ms", "")),
80
+ "elapsed_domain_ms": str(data.get("elapsed_domain_ms", "")),
81
+ "elapsed_labels_ms": str(data.get("elapsed_labels_ms", "")),
82
+ }
83
+
84
+
85
+ def write_csv(path: Path, rows: list[dict[str, str]]) -> None:
86
+ path.parent.mkdir(parents=True, exist_ok=True)
87
+ with path.open("w", newline="", encoding="utf-8") as f:
88
+ writer = csv.DictWriter(
89
+ f,
90
+ fieldnames=[
91
+ "label_set",
92
+ "image",
93
+ "label_set_hash",
94
+ "model_id",
95
+ "chosen_domains",
96
+ "elapsed_ms",
97
+ "elapsed_domain_ms",
98
+ "elapsed_labels_ms",
99
+ ],
100
+ )
101
+ writer.writeheader()
102
+ writer.writerows(rows)
103
+
104
+
105
+ def write_summary(path: Path, rows: list[dict[str, str]]) -> None:
106
+ summary: dict[str, list[int]] = {}
107
+ for row in rows:
108
+ label = row["label_set"]
109
+ try:
110
+ elapsed = int(row["elapsed_ms"])
111
+ except Exception:
112
+ continue
113
+ summary.setdefault(label, []).append(elapsed)
114
+
115
+ out_rows: list[dict[str, str]] = []
116
+ for label, times in summary.items():
117
+ avg = int(sum(times) / max(1, len(times)))
118
+ out_rows.append({"label_set": label, "count": str(len(times)), "avg_elapsed_ms": str(avg)})
119
+
120
+ path.parent.mkdir(parents=True, exist_ok=True)
121
+ with path.open("w", newline="", encoding="utf-8") as f:
122
+ writer = csv.DictWriter(f, fieldnames=["label_set", "count", "avg_elapsed_ms"])
123
+ writer.writeheader()
124
+ writer.writerows(out_rows)
125
+
126
+
127
+ def run(cfg: Config) -> None:
128
+ images = list(iter_images(cfg.images))
129
+ if not images:
130
+ raise SystemExit("No images found.")
131
+ if not cfg.label_sets:
132
+ raise SystemExit("No label sets found.")
133
+
134
+ rows: list[dict[str, str]] = []
135
+ with httpx.Client(base_url=cfg.api, timeout=30) as client:
136
+ for label_set in cfg.label_sets:
137
+ label_set_hash = upload_label_set(client, label_set)
138
+ for image in images:
139
+ image_b64 = image.read_bytes()
140
+ data = classify_one(
141
+ client,
142
+ label_set_hash,
143
+ image_b64=base64.b64encode(image_b64).decode("utf-8"),
144
+ domain_top_n=cfg.domain_top_n,
145
+ top_k=cfg.top_k,
146
+ )
147
+ print(json.dumps({"label_set": label_set.name, "image": str(image), "result": data}))
148
+ rows.append(to_row(label_set, image, data))
149
+
150
+ out_path = cfg.out_dir / "eval_matrix.csv"
151
+ write_csv(out_path, rows)
152
+ if cfg.summary_csv:
153
+ write_summary(cfg.summary_csv, rows)
154
+
155
+
156
+ @click.command()
157
+ @click.option("--api", default="http://localhost:7860", show_default=True)
158
+ @click.option("--label-sets", "label_sets_raw", multiple=True, required=True)
159
+ @click.option("--images", multiple=True, required=True, type=click.Path(path_type=Path))
160
+ @click.option("--domain-top-n", default=2, show_default=True, type=int)
161
+ @click.option("--top-k", default=5, show_default=True, type=int)
162
+ @click.option("--out-dir", default="data_results", show_default=True, type=click.Path(path_type=Path))
163
+ @click.option("--summary-csv", type=click.Path(path_type=Path))
164
+ def cli(
165
+ api: str,
166
+ label_sets_raw: tuple[str, ...],
167
+ images: tuple[Path, ...],
168
+ domain_top_n: int,
169
+ top_k: int,
170
+ out_dir: Path,
171
+ summary_csv: Path | None,
172
+ ) -> None:
173
+ label_sets = expand_label_sets(label_sets_raw)
174
+ cfg = Config(
175
+ api=api,
176
+ label_sets=label_sets,
177
+ images=list(images),
178
+ domain_top_n=domain_top_n,
179
+ top_k=top_k,
180
+ out_dir=out_dir,
181
+ summary_csv=summary_csv,
182
+ )
183
+ run(cfg)
184
+
185
+
186
+ if __name__ == "__main__":
187
+ cli()
src/photo_classification.egg-info/PKG-INFO CHANGED
@@ -11,8 +11,12 @@ Requires-Dist: torch==2.3.1
11
  Requires-Dist: transformers==4.44.2
12
  Requires-Dist: uvicorn[standard]==0.30.6
13
  Provides-Extra: dev
 
 
14
  Requires-Dist: httpx==0.27.2; extra == "dev"
15
  Requires-Dist: pytest==8.3.2; extra == "dev"
 
 
16
 
17
  ---
18
  title: Photo Classification API
@@ -168,6 +172,46 @@ Error handling and HTTP boundaries:
168
  - `pytest -q`
169
  - `pytest -q -m integration`
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  ## Project layout
172
 
173
  ```
 
11
  Requires-Dist: transformers==4.44.2
12
  Requires-Dist: uvicorn[standard]==0.30.6
13
  Provides-Extra: dev
14
+ Requires-Dist: click==8.1.7; extra == "dev"
15
+ Requires-Dist: datasets==2.21.0; extra == "dev"
16
  Requires-Dist: httpx==0.27.2; extra == "dev"
17
  Requires-Dist: pytest==8.3.2; extra == "dev"
18
+ Requires-Dist: requests==2.32.3; extra == "dev"
19
+ Requires-Dist: tqdm==4.66.5; extra == "dev"
20
 
21
  ---
22
  title: Photo Classification API
 
172
  - `pytest -q`
173
  - `pytest -q -m integration`
174
 
175
+ ## Eval scripts
176
+
177
+ Use the lightweight evaluator in `scripts/classify_dataset.py` to run a label set against
178
+ local images and capture timings:
179
+
180
+ ```bash
181
+ uv run python scripts/classify_dataset.py \
182
+ --label-set label-dataset/personal-photos-lite-v1.json \
183
+ --images /path/to/images \
184
+ --csv results/summary.csv
185
+ ```
186
+
187
+ Makefile shortcuts:
188
+
189
+ - `make eval-photo-v1`
190
+ - `make eval-dance-v1`
191
+
192
+ ## Eval datasets (download schema)
193
+
194
+ We use a simple, reproducible layout for evaluation datasets created by `scripts/dataset_prep.py`:
195
+
196
+ ```
197
+ data_eval/
198
+ photos/ # normalized images from Open Images V7 sample
199
+ dance/ # normalized images from X-Dance sample
200
+ ```
201
+
202
+ Download and normalize (recommended):
203
+
204
+ ```bash
205
+ uv run python scripts/dataset_prep.py --out data_eval --target photos --n 800 --normalize
206
+ uv run python scripts/dataset_prep.py --out data_eval --target dance --n 800 --normalize
207
+ ```
208
+
209
+ Normalize your own folder into the same schema:
210
+
211
+ ```bash
212
+ uv run python scripts/dataset_prep.py --normalize-only --in-dir /path/to/images --out data_eval/photos
213
+ ```
214
+
215
  ## Project layout
216
 
217
  ```
src/photo_classification.egg-info/requires.txt CHANGED
@@ -6,5 +6,9 @@ transformers==4.44.2
6
  uvicorn[standard]==0.30.6
7
 
8
  [dev]
 
 
9
  httpx==0.27.2
10
  pytest==8.3.2
 
 
 
6
  uvicorn[standard]==0.30.6
7
 
8
  [dev]
9
+ click==8.1.7
10
+ datasets==2.21.0
11
  httpx==0.27.2
12
  pytest==8.3.2
13
+ requests==2.32.3
14
+ tqdm==4.66.5
uv.lock CHANGED
@@ -2,6 +2,62 @@ version = 1
2
  revision = 2
3
  requires-python = "==3.12.*"
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  [[package]]
6
  name = "annotated-types"
7
  version = "0.7.0"
@@ -24,6 +80,15 @@ wheels = [
24
  { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
25
  ]
26
 
 
 
 
 
 
 
 
 
 
27
  [[package]]
28
  name = "certifi"
29
  version = "2026.1.4"
@@ -60,14 +125,14 @@ wheels = [
60
 
61
  [[package]]
62
  name = "click"
63
- version = "8.3.1"
64
  source = { registry = "https://pypi.org/simple" }
65
  dependencies = [
66
  { name = "colorama", marker = "sys_platform == 'win32'" },
67
  ]
68
- sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
69
  wheels = [
70
- { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
71
  ]
72
 
73
  [[package]]
@@ -79,6 +144,40 @@ wheels = [
79
  { 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" },
80
  ]
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  [[package]]
83
  name = "fastapi"
84
  version = "0.115.6"
@@ -102,13 +201,43 @@ wheels = [
102
  { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
103
  ]
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  [[package]]
106
  name = "fsspec"
107
- version = "2026.1.0"
108
  source = { registry = "https://pypi.org/simple" }
109
- sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" }
110
  wheels = [
111
- { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" },
 
 
 
 
 
112
  ]
113
 
114
  [[package]]
@@ -278,6 +407,49 @@ wheels = [
278
  { 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" },
279
  ]
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  [[package]]
282
  name = "networkx"
283
  version = "3.6.1"
@@ -422,6 +594,27 @@ wheels = [
422
  { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
423
  ]
424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  [[package]]
426
  name = "photo-classification"
427
  version = "0.1.0"
@@ -437,18 +630,26 @@ dependencies = [
437
 
438
  [package.optional-dependencies]
439
  dev = [
 
 
440
  { name = "httpx" },
441
  { name = "pytest" },
 
 
442
  ]
443
 
444
  [package.metadata]
445
  requires-dist = [
 
 
446
  { name = "fastapi", specifier = "==0.115.6" },
447
  { name = "httpx", marker = "extra == 'dev'", specifier = "==0.27.2" },
448
  { name = "pillow", specifier = "==10.4.0" },
449
  { name = "pydantic", specifier = "==2.8.2" },
450
  { name = "pytest", marker = "extra == 'dev'", specifier = "==8.3.2" },
 
451
  { name = "torch", specifier = "==2.3.1" },
 
452
  { name = "transformers", specifier = "==4.44.2" },
453
  { name = "uvicorn", extras = ["standard"], specifier = "==0.30.6" },
454
  ]
@@ -482,6 +683,45 @@ wheels = [
482
  { 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" },
483
  ]
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  [[package]]
486
  name = "pydantic"
487
  version = "2.8.2"
@@ -534,6 +774,18 @@ wheels = [
534
  { url = "https://files.pythonhosted.org/packages/0f/f9/cf155cf32ca7d6fa3601bc4c5dd19086af4b320b706919d48a4c79081cf9/pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", size = 341802, upload-time = "2024-07-25T10:39:57.834Z" },
535
  ]
536
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  [[package]]
538
  name = "python-dotenv"
539
  version = "1.2.1"
@@ -543,6 +795,15 @@ wheels = [
543
  { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
544
  ]
545
 
 
 
 
 
 
 
 
 
 
546
  [[package]]
547
  name = "pyyaml"
548
  version = "6.0.3"
@@ -587,7 +848,7 @@ wheels = [
587
 
588
  [[package]]
589
  name = "requests"
590
- version = "2.32.5"
591
  source = { registry = "https://pypi.org/simple" }
592
  dependencies = [
593
  { name = "certifi" },
@@ -595,9 +856,9 @@ dependencies = [
595
  { name = "idna" },
596
  { name = "urllib3" },
597
  ]
598
- sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
599
  wheels = [
600
- { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
601
  ]
602
 
603
  [[package]]
@@ -622,6 +883,15 @@ wheels = [
622
  { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" },
623
  ]
624
 
 
 
 
 
 
 
 
 
 
625
  [[package]]
626
  name = "sniffio"
627
  version = "1.3.1"
@@ -720,14 +990,14 @@ wheels = [
720
 
721
  [[package]]
722
  name = "tqdm"
723
- version = "4.67.1"
724
  source = { registry = "https://pypi.org/simple" }
725
  dependencies = [
726
  { name = "colorama", marker = "sys_platform == 'win32'" },
727
  ]
728
- sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
729
  wheels = [
730
- { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
731
  ]
732
 
733
  [[package]]
@@ -760,6 +1030,15 @@ wheels = [
760
  { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
761
  ]
762
 
 
 
 
 
 
 
 
 
 
763
  [[package]]
764
  name = "urllib3"
765
  version = "2.6.3"
@@ -848,3 +1127,56 @@ wheels = [
848
  { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
849
  { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
850
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  revision = 2
3
  requires-python = "==3.12.*"
4
 
5
+ [[package]]
6
+ name = "aiohappyeyeballs"
7
+ version = "2.6.1"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "aiohttp"
16
+ version = "3.13.3"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "aiohappyeyeballs" },
20
+ { name = "aiosignal" },
21
+ { name = "attrs" },
22
+ { name = "frozenlist" },
23
+ { name = "multidict" },
24
+ { name = "propcache" },
25
+ { name = "yarl" },
26
+ ]
27
+ sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" }
28
+ wheels = [
29
+ { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" },
30
+ { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" },
31
+ { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" },
32
+ { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" },
33
+ { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" },
34
+ { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" },
35
+ { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" },
36
+ { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" },
37
+ { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" },
38
+ { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" },
39
+ { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" },
40
+ { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" },
41
+ { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" },
42
+ { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" },
43
+ { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" },
44
+ { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" },
45
+ { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" },
46
+ ]
47
+
48
+ [[package]]
49
+ name = "aiosignal"
50
+ version = "1.4.0"
51
+ source = { registry = "https://pypi.org/simple" }
52
+ dependencies = [
53
+ { name = "frozenlist" },
54
+ { name = "typing-extensions" },
55
+ ]
56
+ sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" }
57
+ wheels = [
58
+ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
59
+ ]
60
+
61
  [[package]]
62
  name = "annotated-types"
63
  version = "0.7.0"
 
80
  { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
81
  ]
82
 
83
+ [[package]]
84
+ name = "attrs"
85
+ version = "25.4.0"
86
+ source = { registry = "https://pypi.org/simple" }
87
+ sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
88
+ wheels = [
89
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
90
+ ]
91
+
92
  [[package]]
93
  name = "certifi"
94
  version = "2026.1.4"
 
125
 
126
  [[package]]
127
  name = "click"
128
+ version = "8.1.7"
129
  source = { registry = "https://pypi.org/simple" }
130
  dependencies = [
131
  { name = "colorama", marker = "sys_platform == 'win32'" },
132
  ]
133
+ sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" }
134
  wheels = [
135
+ { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" },
136
  ]
137
 
138
  [[package]]
 
144
  { 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" },
145
  ]
146
 
147
+ [[package]]
148
+ name = "datasets"
149
+ version = "2.21.0"
150
+ source = { registry = "https://pypi.org/simple" }
151
+ dependencies = [
152
+ { name = "aiohttp" },
153
+ { name = "dill" },
154
+ { name = "filelock" },
155
+ { name = "fsspec", extra = ["http"] },
156
+ { name = "huggingface-hub" },
157
+ { name = "multiprocess" },
158
+ { name = "numpy" },
159
+ { name = "packaging" },
160
+ { name = "pandas" },
161
+ { name = "pyarrow" },
162
+ { name = "pyyaml" },
163
+ { name = "requests" },
164
+ { name = "tqdm" },
165
+ { name = "xxhash" },
166
+ ]
167
+ sdist = { url = "https://files.pythonhosted.org/packages/e5/a5/38719e5cff7aa0537a6be37d21cc1fdd7096e9565e8fce2d46a822e10b5b/datasets-2.21.0.tar.gz", hash = "sha256:998f85a8460f1bd982e5bd058f8a0808eef424249e3df1e8cdd594ccd0dc8ba2", size = 2215317, upload-time = "2024-08-14T06:40:44.314Z" }
168
+ wheels = [
169
+ { url = "https://files.pythonhosted.org/packages/72/b3/33c4ad44fa020e3757e9b2fad8a5de53d9079b501e6bbc45bdd18f82f893/datasets-2.21.0-py3-none-any.whl", hash = "sha256:25e4e097110ce28824b746a107727ada94024cba11db8bc588d468414692b65a", size = 527251, upload-time = "2024-08-14T06:40:39.612Z" },
170
+ ]
171
+
172
+ [[package]]
173
+ name = "dill"
174
+ version = "0.3.8"
175
+ source = { registry = "https://pypi.org/simple" }
176
+ sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847, upload-time = "2024-01-27T23:42:16.145Z" }
177
+ wheels = [
178
+ { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252, upload-time = "2024-01-27T23:42:14.239Z" },
179
+ ]
180
+
181
  [[package]]
182
  name = "fastapi"
183
  version = "0.115.6"
 
201
  { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
202
  ]
203
 
204
+ [[package]]
205
+ name = "frozenlist"
206
+ version = "1.8.0"
207
+ source = { registry = "https://pypi.org/simple" }
208
+ sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" }
209
+ wheels = [
210
+ { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" },
211
+ { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" },
212
+ { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" },
213
+ { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" },
214
+ { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" },
215
+ { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" },
216
+ { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" },
217
+ { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" },
218
+ { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" },
219
+ { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" },
220
+ { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" },
221
+ { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" },
222
+ { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" },
223
+ { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" },
224
+ { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" },
225
+ { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" },
226
+ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
227
+ ]
228
+
229
  [[package]]
230
  name = "fsspec"
231
+ version = "2024.6.1"
232
  source = { registry = "https://pypi.org/simple" }
233
+ sdist = { url = "https://files.pythonhosted.org/packages/90/b6/eba5024a9889fcfff396db543a34bef0ab9d002278f163129f9f01005960/fsspec-2024.6.1.tar.gz", hash = "sha256:fad7d7e209dd4c1208e3bbfda706620e0da5142bebbd9c384afb95b07e798e49", size = 284584, upload-time = "2024-06-27T14:35:45.467Z" }
234
  wheels = [
235
+ { url = "https://files.pythonhosted.org/packages/5e/44/73bea497ac69bafde2ee4269292fa3b41f1198f4bb7bbaaabde30ad29d4a/fsspec-2024.6.1-py3-none-any.whl", hash = "sha256:3cb443f8bcd2efb31295a5b9fdb02aee81d8452c80d28f97a6d0959e6cee101e", size = 177561, upload-time = "2024-06-27T14:35:42.023Z" },
236
+ ]
237
+
238
+ [package.optional-dependencies]
239
+ http = [
240
+ { name = "aiohttp" },
241
  ]
242
 
243
  [[package]]
 
407
  { 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" },
408
  ]
409
 
410
+ [[package]]
411
+ name = "multidict"
412
+ version = "6.7.0"
413
+ source = { registry = "https://pypi.org/simple" }
414
+ sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" }
415
+ wheels = [
416
+ { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" },
417
+ { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" },
418
+ { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" },
419
+ { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" },
420
+ { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" },
421
+ { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" },
422
+ { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" },
423
+ { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" },
424
+ { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" },
425
+ { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" },
426
+ { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" },
427
+ { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" },
428
+ { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" },
429
+ { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" },
430
+ { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" },
431
+ { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" },
432
+ { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" },
433
+ { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" },
434
+ { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" },
435
+ ]
436
+
437
+ [[package]]
438
+ name = "multiprocess"
439
+ version = "0.70.16"
440
+ source = { registry = "https://pypi.org/simple" }
441
+ dependencies = [
442
+ { name = "dill" },
443
+ ]
444
+ sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603, upload-time = "2024-01-28T18:52:34.85Z" }
445
+ wheels = [
446
+ { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824, upload-time = "2024-01-28T18:52:26.062Z" },
447
+ { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519, upload-time = "2024-01-28T18:52:28.115Z" },
448
+ { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741, upload-time = "2024-01-28T18:52:29.395Z" },
449
+ { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628, upload-time = "2024-01-28T18:52:30.853Z" },
450
+ { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351, upload-time = "2024-01-28T18:52:31.981Z" },
451
+ ]
452
+
453
  [[package]]
454
  name = "networkx"
455
  version = "3.6.1"
 
594
  { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
595
  ]
596
 
597
+ [[package]]
598
+ name = "pandas"
599
+ version = "2.3.3"
600
+ source = { registry = "https://pypi.org/simple" }
601
+ dependencies = [
602
+ { name = "numpy" },
603
+ { name = "python-dateutil" },
604
+ { name = "pytz" },
605
+ { name = "tzdata" },
606
+ ]
607
+ 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" }
608
+ wheels = [
609
+ { 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" },
610
+ { 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" },
611
+ { 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" },
612
+ { 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" },
613
+ { 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" },
614
+ { 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" },
615
+ { 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" },
616
+ ]
617
+
618
  [[package]]
619
  name = "photo-classification"
620
  version = "0.1.0"
 
630
 
631
  [package.optional-dependencies]
632
  dev = [
633
+ { name = "click" },
634
+ { name = "datasets" },
635
  { name = "httpx" },
636
  { name = "pytest" },
637
+ { name = "requests" },
638
+ { name = "tqdm" },
639
  ]
640
 
641
  [package.metadata]
642
  requires-dist = [
643
+ { name = "click", marker = "extra == 'dev'", specifier = "==8.1.7" },
644
+ { name = "datasets", marker = "extra == 'dev'", specifier = "==2.21.0" },
645
  { name = "fastapi", specifier = "==0.115.6" },
646
  { name = "httpx", marker = "extra == 'dev'", specifier = "==0.27.2" },
647
  { name = "pillow", specifier = "==10.4.0" },
648
  { name = "pydantic", specifier = "==2.8.2" },
649
  { name = "pytest", marker = "extra == 'dev'", specifier = "==8.3.2" },
650
+ { name = "requests", marker = "extra == 'dev'", specifier = "==2.32.3" },
651
  { name = "torch", specifier = "==2.3.1" },
652
+ { name = "tqdm", marker = "extra == 'dev'", specifier = "==4.66.5" },
653
  { name = "transformers", specifier = "==4.44.2" },
654
  { name = "uvicorn", extras = ["standard"], specifier = "==0.30.6" },
655
  ]
 
683
  { 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" },
684
  ]
685
 
686
+ [[package]]
687
+ name = "propcache"
688
+ version = "0.4.1"
689
+ source = { registry = "https://pypi.org/simple" }
690
+ sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" }
691
+ wheels = [
692
+ { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" },
693
+ { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" },
694
+ { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" },
695
+ { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" },
696
+ { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" },
697
+ { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" },
698
+ { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" },
699
+ { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" },
700
+ { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" },
701
+ { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" },
702
+ { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" },
703
+ { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" },
704
+ { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" },
705
+ { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" },
706
+ { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" },
707
+ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" },
708
+ ]
709
+
710
+ [[package]]
711
+ name = "pyarrow"
712
+ version = "22.0.0"
713
+ source = { registry = "https://pypi.org/simple" }
714
+ sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" }
715
+ wheels = [
716
+ { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" },
717
+ { url = "https://files.pythonhosted.org/packages/b1/d0/f9ad86fe809efd2bcc8be32032fa72e8b0d112b01ae56a053006376c5930/pyarrow-22.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:12fe549c9b10ac98c91cf791d2945e878875d95508e1a5d14091a7aaa66d9cf8", size = 35989906, upload-time = "2025-10-24T10:05:29.485Z" },
718
+ { url = "https://files.pythonhosted.org/packages/b4/a8/f910afcb14630e64d673f15904ec27dd31f1e009b77033c365c84e8c1e1d/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:334f900ff08ce0423407af97e6c26ad5d4e3b0763645559ece6fbf3747d6a8f5", size = 45021677, upload-time = "2025-10-24T10:05:38.274Z" },
719
+ { url = "https://files.pythonhosted.org/packages/13/95/aec81f781c75cd10554dc17a25849c720d54feafb6f7847690478dcf5ef8/pyarrow-22.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c6c791b09c57ed76a18b03f2631753a4960eefbbca80f846da8baefc6491fcfe", size = 47726315, upload-time = "2025-10-24T10:05:47.314Z" },
720
+ { url = "https://files.pythonhosted.org/packages/bb/d4/74ac9f7a54cfde12ee42734ea25d5a3c9a45db78f9def949307a92720d37/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c3200cb41cdbc65156e5f8c908d739b0dfed57e890329413da2748d1a2cd1a4e", size = 47990906, upload-time = "2025-10-24T10:05:58.254Z" },
721
+ { url = "https://files.pythonhosted.org/packages/2e/71/fedf2499bf7a95062eafc989ace56572f3343432570e1c54e6599d5b88da/pyarrow-22.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ac93252226cf288753d8b46280f4edf3433bf9508b6977f8dd8526b521a1bbb9", size = 50306783, upload-time = "2025-10-24T10:06:08.08Z" },
722
+ { url = "https://files.pythonhosted.org/packages/68/ed/b202abd5a5b78f519722f3d29063dda03c114711093c1995a33b8e2e0f4b/pyarrow-22.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:44729980b6c50a5f2bfcc2668d36c569ce17f8b17bccaf470c4313dcbbf13c9d", size = 27972883, upload-time = "2025-10-24T10:06:14.204Z" },
723
+ ]
724
+
725
  [[package]]
726
  name = "pydantic"
727
  version = "2.8.2"
 
774
  { url = "https://files.pythonhosted.org/packages/0f/f9/cf155cf32ca7d6fa3601bc4c5dd19086af4b320b706919d48a4c79081cf9/pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", size = 341802, upload-time = "2024-07-25T10:39:57.834Z" },
775
  ]
776
 
777
+ [[package]]
778
+ name = "python-dateutil"
779
+ version = "2.9.0.post0"
780
+ source = { registry = "https://pypi.org/simple" }
781
+ dependencies = [
782
+ { name = "six" },
783
+ ]
784
+ 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" }
785
+ wheels = [
786
+ { 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" },
787
+ ]
788
+
789
  [[package]]
790
  name = "python-dotenv"
791
  version = "1.2.1"
 
795
  { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
796
  ]
797
 
798
+ [[package]]
799
+ name = "pytz"
800
+ version = "2025.2"
801
+ source = { registry = "https://pypi.org/simple" }
802
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
803
+ wheels = [
804
+ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
805
+ ]
806
+
807
  [[package]]
808
  name = "pyyaml"
809
  version = "6.0.3"
 
848
 
849
  [[package]]
850
  name = "requests"
851
+ version = "2.32.3"
852
  source = { registry = "https://pypi.org/simple" }
853
  dependencies = [
854
  { name = "certifi" },
 
856
  { name = "idna" },
857
  { name = "urllib3" },
858
  ]
859
+ sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
860
  wheels = [
861
+ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
862
  ]
863
 
864
  [[package]]
 
883
  { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" },
884
  ]
885
 
886
+ [[package]]
887
+ name = "six"
888
+ version = "1.17.0"
889
+ source = { registry = "https://pypi.org/simple" }
890
+ 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" }
891
+ wheels = [
892
+ { 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" },
893
+ ]
894
+
895
  [[package]]
896
  name = "sniffio"
897
  version = "1.3.1"
 
990
 
991
  [[package]]
992
  name = "tqdm"
993
+ version = "4.66.5"
994
  source = { registry = "https://pypi.org/simple" }
995
  dependencies = [
996
  { name = "colorama", marker = "sys_platform == 'win32'" },
997
  ]
998
+ sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504, upload-time = "2024-08-03T22:35:40.339Z" }
999
  wheels = [
1000
+ { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351, upload-time = "2024-08-03T22:35:36.644Z" },
1001
  ]
1002
 
1003
  [[package]]
 
1030
  { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
1031
  ]
1032
 
1033
+ [[package]]
1034
+ name = "tzdata"
1035
+ version = "2025.3"
1036
+ source = { registry = "https://pypi.org/simple" }
1037
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
1038
+ wheels = [
1039
+ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
1040
+ ]
1041
+
1042
  [[package]]
1043
  name = "urllib3"
1044
  version = "2.6.3"
 
1127
  { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
1128
  { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
1129
  ]
1130
+
1131
+ [[package]]
1132
+ name = "xxhash"
1133
+ version = "3.6.0"
1134
+ source = { registry = "https://pypi.org/simple" }
1135
+ sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" }
1136
+ wheels = [
1137
+ { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" },
1138
+ { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" },
1139
+ { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" },
1140
+ { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" },
1141
+ { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" },
1142
+ { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" },
1143
+ { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" },
1144
+ { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" },
1145
+ { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" },
1146
+ { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" },
1147
+ { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" },
1148
+ { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" },
1149
+ { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" },
1150
+ { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" },
1151
+ { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" },
1152
+ ]
1153
+
1154
+ [[package]]
1155
+ name = "yarl"
1156
+ version = "1.22.0"
1157
+ source = { registry = "https://pypi.org/simple" }
1158
+ dependencies = [
1159
+ { name = "idna" },
1160
+ { name = "multidict" },
1161
+ { name = "propcache" },
1162
+ ]
1163
+ sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" }
1164
+ wheels = [
1165
+ { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" },
1166
+ { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" },
1167
+ { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" },
1168
+ { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" },
1169
+ { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" },
1170
+ { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" },
1171
+ { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" },
1172
+ { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" },
1173
+ { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" },
1174
+ { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" },
1175
+ { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" },
1176
+ { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" },
1177
+ { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" },
1178
+ { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" },
1179
+ { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" },
1180
+ { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" },
1181
+ { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" },
1182
+ ]