esandorfi commited on
Commit
9847fe5
Β·
1 Parent(s): cb08b10

Apply enhancements

Browse files
Files changed (50) hide show
  1. .python-version +1 -0
  2. Dockerfile +15 -5
  3. Makefile +29 -0
  4. README.md +165 -31
  5. main.py +0 -163
  6. pyproject.toml +30 -0
  7. {app β†’ src/app}/__init__.py +0 -0
  8. src/app/__pycache__/__init__.cpython-312.pyc +0 -0
  9. src/app/__pycache__/banks.cpython-312.pyc +0 -0
  10. src/app/__pycache__/clip_service.cpython-312.pyc +0 -0
  11. src/app/__pycache__/clip_store.cpython-312.pyc +0 -0
  12. src/app/__pycache__/deps.cpython-312.pyc +0 -0
  13. src/app/__pycache__/image_io.cpython-312.pyc +0 -0
  14. src/app/__pycache__/label_hash.cpython-312.pyc +0 -0
  15. src/app/__pycache__/logging_utils.cpython-312.pyc +0 -0
  16. src/app/__pycache__/main.cpython-312.pyc +0 -0
  17. src/app/__pycache__/middleware.cpython-312.pyc +0 -0
  18. src/app/__pycache__/registry.cpython-312.pyc +0 -0
  19. src/app/__pycache__/results.cpython-312.pyc +0 -0
  20. src/app/__pycache__/schemas.cpython-312.pyc +0 -0
  21. src/app/__pycache__/settings.cpython-312.pyc +0 -0
  22. app_factory.py β†’ src/app/app_factory.py +0 -0
  23. banks.py β†’ src/app/banks.py +0 -0
  24. clip_service.py β†’ src/app/clip_service.py +0 -0
  25. clip_store.py β†’ src/app/clip_store.py +12 -1
  26. deps.py β†’ src/app/deps.py +0 -0
  27. image_io.py β†’ src/app/image_io.py +0 -0
  28. label_hash.py β†’ src/app/label_hash.py +0 -0
  29. logging_utils.py β†’ src/app/logging_utils.py +0 -0
  30. src/app/main.py +265 -0
  31. middleware.py β†’ src/app/middleware.py +0 -0
  32. registry.py β†’ src/app/registry.py +0 -0
  33. results.py β†’ src/app/results.py +0 -0
  34. schemas.py β†’ src/app/schemas.py +1 -1
  35. settings.py β†’ src/app/settings.py +0 -0
  36. src/photo_classification.egg-info/PKG-INFO +192 -0
  37. src/photo_classification.egg-info/SOURCES.txt +24 -0
  38. src/photo_classification.egg-info/dependency_links.txt +1 -0
  39. src/photo_classification.egg-info/requires.txt +10 -0
  40. src/photo_classification.egg-info/top_level.txt +1 -0
  41. tests/__init__.py +1 -0
  42. tests/__pycache__/__init__.cpython-312.pyc +0 -0
  43. tests/__pycache__/conftest.cpython-312-pytest-8.3.2.pyc +0 -0
  44. tests/__pycache__/fakes.cpython-312.pyc +0 -0
  45. tests/__pycache__/test_api.cpython-312-pytest-8.3.2.pyc +0 -0
  46. tests/__pycache__/test_integration_real_clip.cpython-312-pytest-8.3.2.pyc +0 -0
  47. conftest.py β†’ tests/conftest.py +0 -0
  48. fakes.py β†’ tests/fakes.py +0 -0
  49. test_api.py β†’ tests/test_api.py +0 -0
  50. uv.lock +850 -0
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
Dockerfile CHANGED
@@ -3,15 +3,25 @@ FROM python:3.12-slim
3
  ENV PYTHONDONTWRITEBYTECODE=1 \
4
  PYTHONUNBUFFERED=1
5
 
6
- WORKDIR /app
7
-
8
  RUN apt-get update && apt-get install -y --no-install-recommends \
9
  git \
10
  && rm -rf /var/lib/apt/lists/*
11
 
12
- COPY requirements.txt .
13
- RUN pip install --no-cache-dir -r requirements.txt
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- COPY app ./app
16
 
17
  CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
 
3
  ENV PYTHONDONTWRITEBYTECODE=1 \
4
  PYTHONUNBUFFERED=1
5
 
 
 
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
  git \
8
  && rm -rf /var/lib/apt/lists/*
9
 
10
+ RUN useradd -m -u 1000 user
11
+ USER user
12
+
13
+ ENV HOME=/home/user \
14
+ PATH=/home/user/.local/bin:$PATH
15
+
16
+ WORKDIR $HOME/app
17
+
18
+ RUN pip install --no-cache-dir --upgrade pip
19
+ RUN pip install --no-cache-dir --user uv
20
+
21
+ COPY --chown=user pyproject.toml .
22
+ COPY --chown=user src ./src
23
+ RUN uv sync --no-dev
24
 
25
+ ENV PATH="$HOME/app/.venv/bin:$PATH"
26
 
27
  CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
Makefile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .PHONY: help docker-build docker-run local-install local-run local-test local-test-integration
2
+
3
+ help:
4
+ @echo "---------------------------------------------------"
5
+ @echo "Targets:"
6
+ @echo " docker-build Build Docker image"
7
+ @echo " docker-run Run Docker image on :7860"
8
+ @echo " local-install Sync deps with uv"
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
+
13
+ docker-build:
14
+ docker build -t photo-classification .
15
+
16
+ docker-run:
17
+ docker run --rm -p 7860:7860 photo-classification
18
+
19
+ local-install:
20
+ uv sync --extra dev --python 3.12
21
+
22
+ local-run:
23
+ uv run uvicorn app.main:app --host 0.0.0.0 --port 7860
24
+
25
+ local-test:
26
+ uv run pytest -q
27
+
28
+ local-test-integration:
29
+ uv run pytest -q -m integration
README.md CHANGED
@@ -1,42 +1,176 @@
1
- '''
2
- .
3
- β”œβ”€β”€ Dockerfile
4
- β”œβ”€β”€ requirements.txt
5
- └── app
6
- β”œβ”€β”€ __init__.py
7
- β”œβ”€β”€ banks.py
8
- β”œβ”€β”€ clip_service.py
9
- β”œβ”€β”€ clip_store.py
10
- β”œβ”€β”€ deps.py
11
- β”œβ”€β”€ image_io.py
12
- β”œβ”€β”€ label_hash.py
13
- β”œβ”€β”€ logging_utils.py
14
- β”œβ”€β”€ main.py
15
- β”œβ”€β”€ middleware.py
16
- β”œβ”€β”€ registry.py
17
- β”œβ”€β”€ results.py
18
- β”œβ”€β”€ schemas.py
19
- └── settings.py
20
- '''
21
-
22
-
23
- Recommended runtime config
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  {
25
  "domain_top_n": 1,
26
  "top_k": 3
27
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- How to use the guard (simple policy)
 
 
 
30
 
31
- Recommended default call:
32
 
33
- domain_top_n = 3
 
 
34
 
35
- top_k = 4
36
 
37
- Then in your client logic:
 
 
 
 
 
 
 
38
 
39
- if non_dance appears in chosen_domains and
40
 
41
- its top label score is high vs dance,
42
- treat it as β€œnot dancing” and skip dance-style conclusions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Photo Classification API
3
+ emoji: πŸ“·
4
+ colorFrom: blue
5
+ colorTo: gray
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Photo Classification API
11
+
12
+ ```
13
+ ____ _ _ ____ _
14
+ | _ \| |__ ___ | |_ ___ / ___| | __ _ ___ ___
15
+ | |_) | '_ \ / _ \| __/ _ \ | | | |/ _` / __/ __|
16
+ | __/| | | | (_) | || (_) | | |___| | (_| \__ \__ \
17
+ |_| |_| |_|\___/ \__\___/ \____|_|\__,_|___/___/
18
+ ```
19
+
20
+ A small, prompt-driven photo classification API built on CLIP. You upload a label set
21
+ (domains + labels with prompts), then classify images against that taxonomy without
22
+ any fine-tuning.
23
+
24
+ ## Why this exists
25
+
26
+ - Fast taxonomy iteration: change labels or prompts without retraining.
27
+ - Simple deployment: CPU-only CLIP inference by default.
28
+ - Clear outputs: domain hits, chosen domains, and label hits with scores.
29
+
30
+ ## Models
31
+
32
+ - Text and image embeddings are produced by CLIP (`openai/clip-vit-base-patch32`).
33
+ - All inference runs on CPU unless you adapt `ClipStore` to use GPU.
34
+ - Scoring uses cosine-like similarity with CLIP's logit scale and a softmax.
35
+
36
+ ## Label datasets
37
+
38
+ Label sets are JSON files with `domains` and `labels_by_domain`, each item defining:
39
+
40
+ - `id`: stable identifier
41
+ - `display`: human-readable name
42
+ - `prompt`: the text prompt used for CLIP embedding
43
+
44
+ Examples:
45
+
46
+ - `label-dataset/personal-photos-lite-v1.json`
47
+ - `label-dataset/personal-photos-large-v1.json`
48
+ - `label-dataset/scene-dance-formation-group-v1.json`
49
+
50
+ Use these as starting points or create your own taxonomy.
51
+
52
+ ## API quickstart
53
+
54
+ 1) Start the service (Docker or `uvicorn app.main:app`).
55
+ 2) Upload a label set.
56
+ 3) Optionally activate a label set.
57
+ 4) Classify images.
58
+
59
+ Endpoints:
60
+
61
+ - `POST /api/v1/label-sets` (upload)
62
+ - `GET /api/v1/label-sets` (list)
63
+ - `POST /api/v1/label-sets/{label_set_hash}/activate` (set default)
64
+ - `POST /api/v1/classify` (classify image)
65
+
66
+ `/api/v1/classify` body:
67
+
68
+ ```json
69
+ {
70
+ "image_base64": "<base64>",
71
+ "domain_top_n": 2,
72
+ "top_k": 5
73
+ }
74
+ ```
75
+
76
+ Response includes `domain_hits`, `chosen_domains`, `label_hits`, and timings.
77
+
78
+ ## Recommended runtime config
79
+
80
+ Default params for stable results:
81
+
82
+ ```json
83
  {
84
  "domain_top_n": 1,
85
  "top_k": 3
86
  }
87
+ ```
88
+
89
+ Guard policy example:
90
+
91
+ - Use `domain_top_n = 3`, `top_k = 4`.
92
+ - If `non_dance` appears in `chosen_domains` and its top label score is high vs
93
+ `dance`, treat the photo as "not dancing" and skip dance-style conclusions.
94
+
95
+ ## Architecture
96
+
97
+ - API layer: FastAPI endpoints and request/response schemas (`src/app/main.py`, `src/app/schemas.py`).
98
+ - Use-case layer: two-stage classification (domain -> labels) (`src/app/clip_service.py`).
99
+ - Model layer: CLIP model + processor + embedding banks (`src/app/clip_store.py`, `src/app/banks.py`).
100
+ - Runtime support: registry, settings, logging, middleware (`src/app/registry.py`, `src/app/settings.py`,
101
+ `src/app/logging_utils.py`, `src/app/middleware.py`).
102
+
103
+ ## Coding rules (deeper)
104
+
105
+ Separation of concerns:
106
 
107
+ - API layer does IO only: parse input, validate, load image, call use-case, return typed DTOs.
108
+ - Use-case layer owns business logic: the two-stage classification and result shaping.
109
+ - Model layer owns ML specifics: CLIP loading, text/image encoding, and logit scaling.
110
+ - Data layer owns taxonomy inputs: JSON label sets, hashing, and embedding banks.
111
 
112
+ API vs use-case vs torch:
113
 
114
+ - API should not import torch or transformers; it deals in base64, Pydantic models, and HTTP.
115
+ - Use-case should not depend on HTTP; it accepts a bank + image and returns a typed result.
116
+ - Torch code lives in `ClipStore` only; the rest of the code treats embeddings as opaque tensors.
117
 
118
+ Typed outputs and clean steps:
119
 
120
+ - Each step returns a typed value (`LabelSetBank`, `ClassificationResult`, `Hit` lists).
121
+ - Keep operations in small pure functions or methods that express a single step:
122
+ - validate input
123
+ - load/normalize image
124
+ - encode image
125
+ - score domains
126
+ - merge label banks
127
+ - score labels
128
 
129
+ Dataset usage:
130
 
131
+ - Label sets are data, not code. Changes to taxonomy are done in JSON files or user payloads.
132
+ - Stable hashes (`label_set_hash`) are derived from canonical JSON for reproducibility.
133
+
134
+ Avoid old PyTorch habits:
135
+
136
+ - No training loops, optimizers, or manual grad handling; this is inference-only.
137
+ - Use `torch.no_grad()` and normalized embeddings for stable cosine-like comparisons.
138
+ - Keep tensors on the same device; `ClipStore` owns device placement.
139
+ - Prefer small, readable tensor ops over complex pipelines.
140
+
141
+ Error handling and HTTP boundaries:
142
+
143
+ - Decode/validate base64 and size limits in `image_io`, not inside ML code.
144
+ - Convert internal errors to HTTP responses at the boundary (e.g., 400/404/413).
145
+ - Log JSON events with request IDs for traceability.
146
+
147
+ ## Tests
148
+
149
+ - Fast, deterministic tests use fakes for classifier and store (`tests/fakes.py`).
150
+ - Integration test optionally loads real CLIP (`tests/test_integration_real_clip.py`).
151
+ - Run:
152
+ - `pytest -q`
153
+ - `pytest -q -m integration`
154
+
155
+ ## Project layout
156
+
157
+ ```
158
+ .
159
+ β”œβ”€β”€ Dockerfile
160
+ β”œβ”€β”€ requirements.txt
161
+ └── src
162
+ └── app
163
+ β”œβ”€β”€ banks.py
164
+ β”œβ”€β”€ clip_service.py
165
+ β”œβ”€β”€ clip_store.py
166
+ β”œβ”€β”€ deps.py
167
+ β”œβ”€β”€ image_io.py
168
+ β”œβ”€β”€ label_hash.py
169
+ β”œβ”€β”€ logging_utils.py
170
+ β”œβ”€β”€ main.py
171
+ β”œβ”€β”€ middleware.py
172
+ β”œβ”€β”€ registry.py
173
+ β”œβ”€β”€ results.py
174
+ β”œβ”€β”€ schemas.py
175
+ └── settings.py
176
+ ```
main.py DELETED
@@ -1,163 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from fastapi import FastAPI, HTTPException, Request, Depends
4
- from fastapi.responses import JSONResponse
5
-
6
- from app.clip_store import ClipStore
7
- from app.clip_service import TwoStageClassifier
8
- from app.deps import get_bank, get_request_id, resolve_bank
9
- from app.image_io import load_image_from_base64
10
- from app.logging_utils import setup_logging, log_json
11
- from app.middleware import RequestIdMiddleware
12
- from app.registry import LabelSetRegistry
13
- from app.schemas import (
14
- ActivateResponse,
15
- ClassifyRequest,
16
- ClassifyResponse,
17
- Hit,
18
- LabelSet,
19
- LabelSetCreateResponse,
20
- LabelSetInfo,
21
- )
22
- from app.settings import settings
23
-
24
-
25
- logger = setup_logging()
26
-
27
- def build_app() -> FastAPI:
28
- logger = setup_logging()
29
-
30
- app = FastAPI(
31
- title="Photo Classification API",
32
- version="1.0.0",
33
- docs_url="/docs",
34
- redoc_url=None,
35
- openapi_url="/openapi.json",
36
- )
37
- app.add_middleware(RequestIdMiddleware)
38
-
39
- store = ClipStore()
40
- classifier = TwoStageClassifier(store=store)
41
- registry = LabelSetRegistry(banks={})
42
-
43
- # register routes here (same code as before, using closure vars)
44
-
45
- return app
46
-
47
- app = build_app()
48
-
49
- store = ClipStore()
50
- classifier = TwoStageClassifier(store=store)
51
- registry = LabelSetRegistry(banks={})
52
-
53
-
54
- @app.exception_handler(Exception)
55
- async def unhandled_exception_handler(request: Request, exc: Exception):
56
- rid = getattr(request.state, "request_id", None)
57
- log_json(logger, event="error.unhandled", request_id=rid, error=str(exc), path=str(request.url.path))
58
- return JSONResponse(status_code=500, content={"detail": "internal server error"})
59
-
60
-
61
- @app.post("/api/v1/label-sets", response_model=LabelSetCreateResponse)
62
- def create_label_set(payload: LabelSet, request_id: str = Depends(get_request_id)) -> LabelSetCreateResponse:
63
- bank = store.build_bank(payload)
64
- registry.upsert(bank)
65
-
66
- label_count = sum(len(b.ids) for b in bank.labels_by_domain.values())
67
- is_default = (registry.default_hash == bank.label_set_hash)
68
-
69
- log_json(
70
- logger,
71
- event="label_sets.upsert",
72
- request_id=request_id,
73
- label_set_hash=bank.label_set_hash,
74
- name=bank.name,
75
- domain_count=len(bank.domains.ids),
76
- label_count=label_count,
77
- is_default=is_default,
78
- )
79
-
80
- return LabelSetCreateResponse(
81
- label_set_hash=bank.label_set_hash,
82
- name=bank.name,
83
- domain_count=len(bank.domains.ids),
84
- label_count=label_count,
85
- is_default=is_default,
86
- )
87
-
88
-
89
- @app.get("/api/v1/label-sets", response_model=list[LabelSetInfo])
90
- def list_label_sets(request_id: str = Depends(get_request_id)) -> list[LabelSetInfo]:
91
- out: list[LabelSetInfo] = []
92
- for bank in registry.banks.values():
93
- label_count = sum(len(b.ids) for b in bank.labels_by_domain.values())
94
- out.append(
95
- LabelSetInfo(
96
- label_set_hash=bank.label_set_hash,
97
- name=bank.name,
98
- domain_count=len(bank.domains.ids),
99
- label_count=label_count,
100
- is_default=(registry.default_hash == bank.label_set_hash),
101
- )
102
- )
103
-
104
- log_json(logger, event="label_sets.list", request_id=request_id, count=len(out))
105
- return out
106
-
107
-
108
- @app.post("/api/v1/label-sets/{label_set_hash}/activate", response_model=ActivateResponse)
109
- def activate_label_set(label_set_hash: str, request_id: str = Depends(get_request_id)) -> ActivateResponse:
110
- try:
111
- registry.activate(label_set_hash)
112
- except KeyError:
113
- raise HTTPException(status_code=404, detail="Unknown label_set_hash")
114
-
115
- log_json(logger, event="label_sets.activate", request_id=request_id, default_label_set_hash=label_set_hash)
116
- return ActivateResponse(default_label_set_hash=label_set_hash)
117
-
118
-
119
- @app.post("/api/v1/classify", response_model=ClassifyResponse)
120
- def classify(
121
- payload: ClassifyRequest,
122
- request: Request,
123
- request_id: str = Depends(get_request_id),
124
- label_set_hash: Optional[str] = Query(default=None, description="If omitted, uses the default label set."),
125
- ) -> ClassifyResponse:
126
- bank = resolve_bank(registry, label_set_hash)
127
-
128
- image = load_image_from_base64(
129
- payload.image_base64,
130
- max_bytes=settings.max_image_mb * 1024 * 1024,
131
- )
132
-
133
- res = classifier.classify(
134
- bank=bank,
135
- image=image,
136
- domain_top_n=payload.domain_top_n or settings.default_domain_top_n,
137
- top_k=payload.top_k or settings.default_top_k,
138
- )
139
-
140
- log_json(
141
- logger,
142
- event="classify",
143
- request_id=request_id,
144
- label_set_hash=bank.label_set_hash,
145
- model_id=settings.clip_model_id,
146
- domain_top_n=payload.domain_top_n,
147
- top_k=payload.top_k,
148
- chosen_domains=res.chosen_domains,
149
- elapsed_ms=res.timings.total_ms,
150
- elapsed_domain_ms=res.timings.domain_ms,
151
- elapsed_labels_ms=res.timings.labels_ms,
152
- )
153
-
154
- return ClassifyResponse(
155
- label_set_hash=bank.label_set_hash,
156
- model_id=settings.clip_model_id,
157
- domain_hits=[Hit(id=i, score=s) for i, s in res.domain_hits],
158
- chosen_domains=res.chosen_domains,
159
- label_hits=[Hit(id=i, score=s) for i, s in res.label_hits],
160
- elapsed_ms=res.timings.total_ms,
161
- elapsed_domain_ms=res.timings.domain_ms,
162
- elapsed_labels_ms=res.timings.labels_ms,
163
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "photo-classification"
7
+ version = "0.1.0"
8
+ description = "Prompt-driven photo classification API built on CLIP"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12,<3.13"
11
+ dependencies = [
12
+ "fastapi==0.115.6",
13
+ "pillow==10.4.0",
14
+ "pydantic==2.8.2",
15
+ "torch==2.3.1",
16
+ "transformers==4.44.2",
17
+ "uvicorn[standard]==0.30.6",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "httpx==0.27.2",
23
+ "pytest==8.3.2",
24
+ ]
25
+
26
+ [tool.setuptools]
27
+ package-dir = {"" = "src"}
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
{app β†’ src/app}/__init__.py RENAMED
File without changes
src/app/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (149 Bytes). View file
 
src/app/__pycache__/banks.cpython-312.pyc ADDED
Binary file (964 Bytes). View file
 
src/app/__pycache__/clip_service.cpython-312.pyc ADDED
Binary file (4.86 kB). View file
 
src/app/__pycache__/clip_store.cpython-312.pyc ADDED
Binary file (5.74 kB). View file
 
src/app/__pycache__/deps.cpython-312.pyc ADDED
Binary file (1.74 kB). View file
 
src/app/__pycache__/image_io.cpython-312.pyc ADDED
Binary file (1.17 kB). View file
 
src/app/__pycache__/label_hash.cpython-312.pyc ADDED
Binary file (737 Bytes). View file
 
src/app/__pycache__/logging_utils.cpython-312.pyc ADDED
Binary file (1.57 kB). View file
 
src/app/__pycache__/main.cpython-312.pyc ADDED
Binary file (14 kB). View file
 
src/app/__pycache__/middleware.cpython-312.pyc ADDED
Binary file (1.09 kB). View file
 
src/app/__pycache__/registry.cpython-312.pyc ADDED
Binary file (1.67 kB). View file
 
src/app/__pycache__/results.cpython-312.pyc ADDED
Binary file (938 Bytes). View file
 
src/app/__pycache__/schemas.cpython-312.pyc ADDED
Binary file (3.27 kB). View file
 
src/app/__pycache__/settings.cpython-312.pyc ADDED
Binary file (705 Bytes). View file
 
app_factory.py β†’ src/app/app_factory.py RENAMED
File without changes
banks.py β†’ src/app/banks.py RENAMED
File without changes
clip_service.py β†’ src/app/clip_service.py RENAMED
File without changes
clip_store.py β†’ src/app/clip_store.py RENAMED
@@ -1,5 +1,7 @@
1
  from __future__ import annotations
2
 
 
 
3
  import torch
4
  from transformers import CLIPModel, CLIPProcessor
5
 
@@ -18,7 +20,16 @@ class ClipStore:
18
  def __init__(self) -> None:
19
  self.device = torch.device("cpu")
20
  self.model = CLIPModel.from_pretrained(settings.clip_model_id).to(self.device).eval()
21
- self.processor = CLIPProcessor.from_pretrained(settings.clip_model_id)
 
 
 
 
 
 
 
 
 
22
 
23
  def build_bank(self, label_set: LabelSet) -> LabelSetBank:
24
  payload = label_set.model_dump()
 
1
  from __future__ import annotations
2
 
3
+ import warnings
4
+
5
  import torch
6
  from transformers import CLIPModel, CLIPProcessor
7
 
 
20
  def __init__(self) -> None:
21
  self.device = torch.device("cpu")
22
  self.model = CLIPModel.from_pretrained(settings.clip_model_id).to(self.device).eval()
23
+ with warnings.catch_warnings():
24
+ warnings.filterwarnings(
25
+ "ignore",
26
+ message="`clean_up_tokenization_spaces` was not set",
27
+ category=FutureWarning,
28
+ module="transformers.tokenization_utils_base",
29
+ )
30
+ self.processor = CLIPProcessor.from_pretrained(settings.clip_model_id)
31
+ if hasattr(self.processor, "tokenizer") and hasattr(self.processor.tokenizer, "clean_up_tokenization_spaces"):
32
+ self.processor.tokenizer.clean_up_tokenization_spaces = True
33
 
34
  def build_bank(self, label_set: LabelSet) -> LabelSetBank:
35
  payload = label_set.model_dump()
deps.py β†’ src/app/deps.py RENAMED
File without changes
image_io.py β†’ src/app/image_io.py RENAMED
File without changes
label_hash.py β†’ src/app/label_hash.py RENAMED
File without changes
logging_utils.py β†’ src/app/logging_utils.py RENAMED
File without changes
src/app/main.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from contextlib import asynccontextmanager
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ from fastapi import Depends, FastAPI, HTTPException, Query, Request, Response
8
+ from fastapi.responses import JSONResponse
9
+
10
+ from app.clip_store import ClipStore
11
+ from app.clip_service import TwoStageClassifier
12
+ from app.deps import get_request_id, resolve_bank
13
+ from app.image_io import load_image_from_base64
14
+ from app.logging_utils import setup_logging, log_json
15
+ from app.middleware import RequestIdMiddleware
16
+ from app.registry import LabelSetRegistry
17
+ from app.schemas import (
18
+ ActivateResponse,
19
+ ClassifyRequest,
20
+ ClassifyResponse,
21
+ Hit,
22
+ LabelSet,
23
+ LabelSetCreateResponse,
24
+ LabelSetInfo,
25
+ )
26
+ from app.settings import settings
27
+
28
+
29
+ logger = setup_logging()
30
+
31
+ BANNER = r"""
32
+ ____ _ _ ____ _
33
+ | _ \| |__ ___ | |_ ___ / ___| | __ _ ___ ___
34
+ | |_) | '_ \ / _ \| __/ _ \ | | | |/ _` / __/ __|
35
+ | __/| | | | (_) | || (_) | | |___| | (_| \__ \__ \
36
+ |_| |_| |_|\___/ \__\___/ \____|_|\__,_|___/___/
37
+ """
38
+
39
+
40
+ @dataclass
41
+ class Resources:
42
+ store: ClipStore
43
+ classifier: TwoStageClassifier
44
+ registry: LabelSetRegistry
45
+
46
+
47
+ async def _maybe_aclose(obj) -> None:
48
+ aclose = getattr(obj, "aclose", None)
49
+ if callable(aclose):
50
+ await aclose()
51
+ return
52
+ close = getattr(obj, "close", None)
53
+ if callable(close):
54
+ close()
55
+
56
+
57
+ @asynccontextmanager
58
+ async def lifespan(app: FastAPI):
59
+ store = ClipStore()
60
+ classifier = TwoStageClassifier(store=store)
61
+ registry = LabelSetRegistry(banks={})
62
+
63
+ app.state.resources = Resources(store=store, classifier=classifier, registry=registry)
64
+ try:
65
+ yield
66
+ finally:
67
+ await _maybe_aclose(registry)
68
+ await _maybe_aclose(classifier)
69
+ await _maybe_aclose(store)
70
+
71
+
72
+ def get_resources(request: Request) -> Resources:
73
+ return request.app.state.resources
74
+
75
+
76
+ def get_store(res: Resources = Depends(get_resources)) -> ClipStore:
77
+ return res.store
78
+
79
+
80
+ def get_classifier(res: Resources = Depends(get_resources)) -> TwoStageClassifier:
81
+ return res.classifier
82
+
83
+
84
+ def get_registry(res: Resources = Depends(get_resources)) -> LabelSetRegistry:
85
+ return res.registry
86
+
87
+
88
+ def create_app(*, resources: Resources | None = None) -> FastAPI:
89
+ app = FastAPI(
90
+ lifespan=lifespan,
91
+ title="Photo Classification API",
92
+ version="1.0.0",
93
+ description=f"```\n{BANNER.strip()}\n```",
94
+ docs_url="/docs",
95
+ redoc_url=None,
96
+ openapi_url="/openapi.json",
97
+ )
98
+ app.add_middleware(RequestIdMiddleware)
99
+
100
+ if resources is not None:
101
+ app.state.resources = resources
102
+
103
+ @asynccontextmanager
104
+ async def _lifespan_override(_app: FastAPI):
105
+ _app.state.resources = resources
106
+ try:
107
+ yield
108
+ finally:
109
+ await _maybe_aclose(resources.registry)
110
+ await _maybe_aclose(resources.classifier)
111
+ await _maybe_aclose(resources.store)
112
+
113
+ app.router.lifespan_context = _lifespan_override
114
+
115
+ @app.get("/favicon.ico", include_in_schema=False)
116
+ def favicon() -> Response:
117
+ svg = (
118
+ "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'>"
119
+ "<rect width='64' height='64' rx='12' fill='#1f2937'/>"
120
+ "<circle cx='24' cy='28' r='10' fill='#f59e0b'/>"
121
+ "<circle cx='44' cy='28' r='10' fill='#60a5fa'/>"
122
+ "<rect x='18' y='38' width='28' height='10' rx='5' fill='#e5e7eb'/>"
123
+ "</svg>"
124
+ )
125
+ return Response(content=svg, media_type="image/svg+xml")
126
+
127
+ @app.exception_handler(Exception)
128
+ async def unhandled_exception_handler(request: Request, exc: Exception):
129
+ rid = getattr(request.state, "request_id", None)
130
+ log_json(logger, event="error.unhandled", request_id=rid, error=str(exc), path=str(request.url.path))
131
+ return JSONResponse(status_code=500, content={"detail": "internal server error"})
132
+
133
+ @app.post("/api/v1/label-sets", response_model=LabelSetCreateResponse)
134
+ def create_label_set(
135
+ payload: LabelSet,
136
+ request_id: str = Depends(get_request_id),
137
+ store: ClipStore = Depends(get_store),
138
+ registry: LabelSetRegistry = Depends(get_registry),
139
+ ) -> LabelSetCreateResponse:
140
+ bank = store.build_bank(payload)
141
+ registry.upsert(bank)
142
+
143
+ label_count = sum(len(b.ids) for b in bank.labels_by_domain.values())
144
+ is_default = (registry.default_hash == bank.label_set_hash)
145
+
146
+ log_json(
147
+ logger,
148
+ event="label_sets.upsert",
149
+ request_id=request_id,
150
+ label_set_hash=bank.label_set_hash,
151
+ name=bank.name,
152
+ domain_count=len(bank.domains.ids),
153
+ label_count=label_count,
154
+ is_default=is_default,
155
+ )
156
+
157
+ return LabelSetCreateResponse(
158
+ label_set_hash=bank.label_set_hash,
159
+ name=bank.name,
160
+ domain_count=len(bank.domains.ids),
161
+ label_count=label_count,
162
+ is_default=is_default,
163
+ )
164
+
165
+ @app.get("/api/v1/label-sets", response_model=list[LabelSetInfo])
166
+ def list_label_sets(
167
+ request_id: str = Depends(get_request_id),
168
+ registry: LabelSetRegistry = Depends(get_registry),
169
+ ) -> list[LabelSetInfo]:
170
+ out: list[LabelSetInfo] = []
171
+ for bank in registry.banks.values():
172
+ label_count = sum(len(b.ids) for b in bank.labels_by_domain.values())
173
+ out.append(
174
+ LabelSetInfo(
175
+ label_set_hash=bank.label_set_hash,
176
+ name=bank.name,
177
+ domain_count=len(bank.domains.ids),
178
+ label_count=label_count,
179
+ is_default=(registry.default_hash == bank.label_set_hash),
180
+ )
181
+ )
182
+
183
+ log_json(logger, event="label_sets.list", request_id=request_id, count=len(out))
184
+ return out
185
+
186
+ @app.post("/api/v1/label-sets/{label_set_hash}/activate", response_model=ActivateResponse)
187
+ def activate_label_set(
188
+ label_set_hash: str,
189
+ request_id: str = Depends(get_request_id),
190
+ registry: LabelSetRegistry = Depends(get_registry),
191
+ ) -> ActivateResponse:
192
+ try:
193
+ registry.activate(label_set_hash)
194
+ except KeyError:
195
+ raise HTTPException(status_code=404, detail="Unknown label_set_hash")
196
+
197
+ log_json(logger, event="label_sets.activate", request_id=request_id, default_label_set_hash=label_set_hash)
198
+ return ActivateResponse(default_label_set_hash=label_set_hash)
199
+
200
+ @app.post("/api/v1/classify", response_model=ClassifyResponse)
201
+ def classify(
202
+ payload: ClassifyRequest,
203
+ request: Request,
204
+ request_id: str = Depends(get_request_id),
205
+ label_set_hash: Optional[str] = Query(default=None, description="If omitted, uses the default label set."),
206
+ classifier: TwoStageClassifier = Depends(get_classifier),
207
+ registry: LabelSetRegistry = Depends(get_registry),
208
+ ) -> ClassifyResponse:
209
+ bank = resolve_bank(registry, label_set_hash)
210
+
211
+ image = load_image_from_base64(
212
+ payload.image_base64,
213
+ max_bytes=settings.max_image_mb * 1024 * 1024,
214
+ )
215
+
216
+ res = classifier.classify(
217
+ bank=bank,
218
+ image=image,
219
+ domain_top_n=payload.domain_top_n or settings.default_domain_top_n,
220
+ top_k=payload.top_k or settings.default_top_k,
221
+ )
222
+
223
+ log_json(
224
+ logger,
225
+ event="classify",
226
+ request_id=request_id,
227
+ label_set_hash=bank.label_set_hash,
228
+ model_id=settings.clip_model_id,
229
+ domain_top_n=payload.domain_top_n,
230
+ top_k=payload.top_k,
231
+ chosen_domains=res.chosen_domains,
232
+ elapsed_ms=res.timings.total_ms,
233
+ elapsed_domain_ms=res.timings.domain_ms,
234
+ elapsed_labels_ms=res.timings.labels_ms,
235
+ )
236
+
237
+ return ClassifyResponse(
238
+ label_set_hash=bank.label_set_hash,
239
+ model_id=settings.clip_model_id,
240
+ domain_hits=[Hit(id=i, score=s) for i, s in res.domain_hits],
241
+ chosen_domains=res.chosen_domains,
242
+ label_hits=[Hit(id=i, score=s) for i, s in res.label_hits],
243
+ elapsed_ms=res.timings.total_ms,
244
+ elapsed_domain_ms=res.timings.domain_ms,
245
+ elapsed_labels_ms=res.timings.labels_ms,
246
+ )
247
+
248
+ return app
249
+
250
+
251
+ def build_app(
252
+ store: ClipStore | None = None,
253
+ classifier: TwoStageClassifier | None = None,
254
+ registry: LabelSetRegistry | None = None,
255
+ ) -> FastAPI:
256
+ if store is None and classifier is None and registry is None:
257
+ return create_app()
258
+ store = store or ClipStore()
259
+ classifier = classifier or TwoStageClassifier(store=store)
260
+ registry = registry or LabelSetRegistry(banks={})
261
+ resources = Resources(store=store, classifier=classifier, registry=registry)
262
+ return create_app(resources=resources)
263
+
264
+
265
+ app = create_app()
middleware.py β†’ src/app/middleware.py RENAMED
File without changes
registry.py β†’ src/app/registry.py RENAMED
File without changes
results.py β†’ src/app/results.py RENAMED
File without changes
schemas.py β†’ src/app/schemas.py RENAMED
@@ -60,7 +60,7 @@ class Hit(BaseModel):
60
 
61
 
62
  class ClassifyResponse(BaseModel):
63
- model_config = ConfigDict(extra="forbid")
64
  label_set_hash: str
65
  model_id: str
66
  domain_hits: List[Hit]
 
60
 
61
 
62
  class ClassifyResponse(BaseModel):
63
+ model_config = ConfigDict(extra="forbid", protected_namespaces=())
64
  label_set_hash: str
65
  model_id: str
66
  domain_hits: List[Hit]
settings.py β†’ src/app/settings.py RENAMED
File without changes
src/photo_classification.egg-info/PKG-INFO ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: photo-classification
3
+ Version: 0.1.0
4
+ Summary: Prompt-driven photo classification API built on CLIP
5
+ Requires-Python: <3.13,>=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: fastapi==0.115.6
8
+ Requires-Dist: pillow==10.4.0
9
+ Requires-Dist: pydantic==2.8.2
10
+ 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
19
+ emoji: πŸ“·
20
+ colorFrom: blue
21
+ colorTo: gray
22
+ sdk: docker
23
+ app_port: 7860
24
+ ---
25
+
26
+ # Photo Classification API
27
+
28
+ ```
29
+ ____ _ _ ____ _
30
+ | _ \| |__ ___ | |_ ___ / ___| | __ _ ___ ___
31
+ | |_) | '_ \ / _ \| __/ _ \ | | | |/ _` / __/ __|
32
+ | __/| | | | (_) | || (_) | | |___| | (_| \__ \__ \
33
+ |_| |_| |_|\___/ \__\___/ \____|_|\__,_|___/___/
34
+ ```
35
+
36
+ A small, prompt-driven photo classification API built on CLIP. You upload a label set
37
+ (domains + labels with prompts), then classify images against that taxonomy without
38
+ any fine-tuning.
39
+
40
+ ## Why this exists
41
+
42
+ - Fast taxonomy iteration: change labels or prompts without retraining.
43
+ - Simple deployment: CPU-only CLIP inference by default.
44
+ - Clear outputs: domain hits, chosen domains, and label hits with scores.
45
+
46
+ ## Models
47
+
48
+ - Text and image embeddings are produced by CLIP (`openai/clip-vit-base-patch32`).
49
+ - All inference runs on CPU unless you adapt `ClipStore` to use GPU.
50
+ - Scoring uses cosine-like similarity with CLIP's logit scale and a softmax.
51
+
52
+ ## Label datasets
53
+
54
+ Label sets are JSON files with `domains` and `labels_by_domain`, each item defining:
55
+
56
+ - `id`: stable identifier
57
+ - `display`: human-readable name
58
+ - `prompt`: the text prompt used for CLIP embedding
59
+
60
+ Examples:
61
+
62
+ - `label-dataset/personal-photos-lite-v1.json`
63
+ - `label-dataset/personal-photos-large-v1.json`
64
+ - `label-dataset/scene-dance-formation-group-v1.json`
65
+
66
+ Use these as starting points or create your own taxonomy.
67
+
68
+ ## API quickstart
69
+
70
+ 1) Start the service (Docker or `uvicorn app.main:app`).
71
+ 2) Upload a label set.
72
+ 3) Optionally activate a label set.
73
+ 4) Classify images.
74
+
75
+ Endpoints:
76
+
77
+ - `POST /api/v1/label-sets` (upload)
78
+ - `GET /api/v1/label-sets` (list)
79
+ - `POST /api/v1/label-sets/{label_set_hash}/activate` (set default)
80
+ - `POST /api/v1/classify` (classify image)
81
+
82
+ `/api/v1/classify` body:
83
+
84
+ ```json
85
+ {
86
+ "image_base64": "<base64>",
87
+ "domain_top_n": 2,
88
+ "top_k": 5
89
+ }
90
+ ```
91
+
92
+ Response includes `domain_hits`, `chosen_domains`, `label_hits`, and timings.
93
+
94
+ ## Recommended runtime config
95
+
96
+ Default params for stable results:
97
+
98
+ ```json
99
+ {
100
+ "domain_top_n": 1,
101
+ "top_k": 3
102
+ }
103
+ ```
104
+
105
+ Guard policy example:
106
+
107
+ - Use `domain_top_n = 3`, `top_k = 4`.
108
+ - If `non_dance` appears in `chosen_domains` and its top label score is high vs
109
+ `dance`, treat the photo as "not dancing" and skip dance-style conclusions.
110
+
111
+ ## Architecture
112
+
113
+ - API layer: FastAPI endpoints and request/response schemas (`src/app/main.py`, `src/app/schemas.py`).
114
+ - Use-case layer: two-stage classification (domain -> labels) (`src/app/clip_service.py`).
115
+ - Model layer: CLIP model + processor + embedding banks (`src/app/clip_store.py`, `src/app/banks.py`).
116
+ - Runtime support: registry, settings, logging, middleware (`src/app/registry.py`, `src/app/settings.py`,
117
+ `src/app/logging_utils.py`, `src/app/middleware.py`).
118
+
119
+ ## Coding rules (deeper)
120
+
121
+ Separation of concerns:
122
+
123
+ - API layer does IO only: parse input, validate, load image, call use-case, return typed DTOs.
124
+ - Use-case layer owns business logic: the two-stage classification and result shaping.
125
+ - Model layer owns ML specifics: CLIP loading, text/image encoding, and logit scaling.
126
+ - Data layer owns taxonomy inputs: JSON label sets, hashing, and embedding banks.
127
+
128
+ API vs use-case vs torch:
129
+
130
+ - API should not import torch or transformers; it deals in base64, Pydantic models, and HTTP.
131
+ - Use-case should not depend on HTTP; it accepts a bank + image and returns a typed result.
132
+ - Torch code lives in `ClipStore` only; the rest of the code treats embeddings as opaque tensors.
133
+
134
+ Typed outputs and clean steps:
135
+
136
+ - Each step returns a typed value (`LabelSetBank`, `ClassificationResult`, `Hit` lists).
137
+ - Keep operations in small pure functions or methods that express a single step:
138
+ - validate input
139
+ - load/normalize image
140
+ - encode image
141
+ - score domains
142
+ - merge label banks
143
+ - score labels
144
+
145
+ Dataset usage:
146
+
147
+ - Label sets are data, not code. Changes to taxonomy are done in JSON files or user payloads.
148
+ - Stable hashes (`label_set_hash`) are derived from canonical JSON for reproducibility.
149
+
150
+ Avoid old PyTorch habits:
151
+
152
+ - No training loops, optimizers, or manual grad handling; this is inference-only.
153
+ - Use `torch.no_grad()` and normalized embeddings for stable cosine-like comparisons.
154
+ - Keep tensors on the same device; `ClipStore` owns device placement.
155
+ - Prefer small, readable tensor ops over complex pipelines.
156
+
157
+ Error handling and HTTP boundaries:
158
+
159
+ - Decode/validate base64 and size limits in `image_io`, not inside ML code.
160
+ - Convert internal errors to HTTP responses at the boundary (e.g., 400/404/413).
161
+ - Log JSON events with request IDs for traceability.
162
+
163
+ ## Tests
164
+
165
+ - Fast, deterministic tests use fakes for classifier and store (`tests/fakes.py`).
166
+ - Integration test optionally loads real CLIP (`tests/test_integration_real_clip.py`).
167
+ - Run:
168
+ - `pytest -q`
169
+ - `pytest -q -m integration`
170
+
171
+ ## Project layout
172
+
173
+ ```
174
+ .
175
+ β”œβ”€β”€ Dockerfile
176
+ β”œβ”€β”€ requirements.txt
177
+ └── src
178
+ └── app
179
+ β”œβ”€β”€ banks.py
180
+ β”œβ”€β”€ clip_service.py
181
+ β”œβ”€β”€ clip_store.py
182
+ β”œβ”€β”€ deps.py
183
+ β”œβ”€β”€ image_io.py
184
+ β”œβ”€β”€ label_hash.py
185
+ β”œβ”€β”€ logging_utils.py
186
+ β”œβ”€β”€ main.py
187
+ β”œβ”€β”€ middleware.py
188
+ β”œβ”€β”€ registry.py
189
+ β”œβ”€β”€ results.py
190
+ β”œβ”€β”€ schemas.py
191
+ └── settings.py
192
+ ```
src/photo_classification.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ README.md
2
+ pyproject.toml
3
+ src/app/__init__.py
4
+ src/app/app_factory.py
5
+ src/app/banks.py
6
+ src/app/clip_service.py
7
+ src/app/clip_store.py
8
+ src/app/deps.py
9
+ src/app/image_io.py
10
+ src/app/label_hash.py
11
+ src/app/logging_utils.py
12
+ src/app/main.py
13
+ src/app/middleware.py
14
+ src/app/registry.py
15
+ src/app/results.py
16
+ src/app/schemas.py
17
+ src/app/settings.py
18
+ src/photo_classification.egg-info/PKG-INFO
19
+ src/photo_classification.egg-info/SOURCES.txt
20
+ src/photo_classification.egg-info/dependency_links.txt
21
+ src/photo_classification.egg-info/requires.txt
22
+ src/photo_classification.egg-info/top_level.txt
23
+ tests/test_api.py
24
+ tests/test_integration_real_clip.py
src/photo_classification.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
src/photo_classification.egg-info/requires.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.115.6
2
+ pillow==10.4.0
3
+ pydantic==2.8.2
4
+ torch==2.3.1
5
+ transformers==4.44.2
6
+ uvicorn[standard]==0.30.6
7
+
8
+ [dev]
9
+ httpx==0.27.2
10
+ pytest==8.3.2
src/photo_classification.egg-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ app
tests/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Test package marker for src layout imports."""
tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (209 Bytes). View file
 
tests/__pycache__/conftest.cpython-312-pytest-8.3.2.pyc ADDED
Binary file (2.3 kB). View file
 
tests/__pycache__/fakes.cpython-312.pyc ADDED
Binary file (3.29 kB). View file
 
tests/__pycache__/test_api.cpython-312-pytest-8.3.2.pyc ADDED
Binary file (14.2 kB). View file
 
tests/__pycache__/test_integration_real_clip.cpython-312-pytest-8.3.2.pyc ADDED
Binary file (6.21 kB). View file
 
conftest.py β†’ tests/conftest.py RENAMED
File without changes
fakes.py β†’ tests/fakes.py RENAMED
File without changes
test_api.py β†’ tests/test_api.py RENAMED
File without changes
uv.lock ADDED
@@ -0,0 +1,850 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version = 1
2
+ revision = 2
3
+ requires-python = "==3.12.*"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.12.1"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "idna" },
20
+ { name = "typing-extensions" },
21
+ ]
22
+ sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
23
+ 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"
30
+ source = { registry = "https://pypi.org/simple" }
31
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
32
+ wheels = [
33
+ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "charset-normalizer"
38
+ version = "3.4.4"
39
+ source = { registry = "https://pypi.org/simple" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
41
+ wheels = [
42
+ { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
43
+ { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
44
+ { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
45
+ { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
46
+ { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
47
+ { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
48
+ { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
49
+ { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
50
+ { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
51
+ { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
52
+ { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
53
+ { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
54
+ { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
55
+ { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
56
+ { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
57
+ { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
58
+ { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
59
+ ]
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]]
74
+ name = "colorama"
75
+ version = "0.4.6"
76
+ source = { registry = "https://pypi.org/simple" }
77
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
78
+ 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"
85
+ source = { registry = "https://pypi.org/simple" }
86
+ dependencies = [
87
+ { name = "pydantic" },
88
+ { name = "starlette" },
89
+ { name = "typing-extensions" },
90
+ ]
91
+ sdist = { url = "https://files.pythonhosted.org/packages/93/72/d83b98cd106541e8f5e5bfab8ef2974ab45a62e8a6c5b5e6940f26d2ed4b/fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654", size = 301336, upload-time = "2024-12-03T22:46:01.629Z" }
92
+ wheels = [
93
+ { url = "https://files.pythonhosted.org/packages/52/b3/7e4df40e585df024fac2f80d1a2d579c854ac37109675db2b0cc22c0bb9e/fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305", size = 94843, upload-time = "2024-12-03T22:45:59.368Z" },
94
+ ]
95
+
96
+ [[package]]
97
+ name = "filelock"
98
+ version = "3.20.3"
99
+ source = { registry = "https://pypi.org/simple" }
100
+ sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
101
+ 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]]
115
+ name = "h11"
116
+ version = "0.16.0"
117
+ source = { registry = "https://pypi.org/simple" }
118
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
119
+ wheels = [
120
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
121
+ ]
122
+
123
+ [[package]]
124
+ name = "hf-xet"
125
+ version = "1.2.0"
126
+ source = { registry = "https://pypi.org/simple" }
127
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" }
128
+ wheels = [
129
+ { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" },
130
+ { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" },
131
+ { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" },
132
+ { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" },
133
+ { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" },
134
+ { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" },
135
+ { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" },
136
+ ]
137
+
138
+ [[package]]
139
+ name = "httpcore"
140
+ version = "1.0.9"
141
+ source = { registry = "https://pypi.org/simple" }
142
+ dependencies = [
143
+ { name = "certifi" },
144
+ { name = "h11" },
145
+ ]
146
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
147
+ wheels = [
148
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
149
+ ]
150
+
151
+ [[package]]
152
+ name = "httptools"
153
+ version = "0.7.1"
154
+ source = { registry = "https://pypi.org/simple" }
155
+ sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" }
156
+ wheels = [
157
+ { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" },
158
+ { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" },
159
+ { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" },
160
+ { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" },
161
+ { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" },
162
+ { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" },
163
+ { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" },
164
+ ]
165
+
166
+ [[package]]
167
+ name = "httpx"
168
+ version = "0.27.2"
169
+ source = { registry = "https://pypi.org/simple" }
170
+ dependencies = [
171
+ { name = "anyio" },
172
+ { name = "certifi" },
173
+ { name = "httpcore" },
174
+ { name = "idna" },
175
+ { name = "sniffio" },
176
+ ]
177
+ sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189, upload-time = "2024-08-27T12:54:01.334Z" }
178
+ wheels = [
179
+ { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395, upload-time = "2024-08-27T12:53:59.653Z" },
180
+ ]
181
+
182
+ [[package]]
183
+ name = "huggingface-hub"
184
+ version = "0.36.0"
185
+ source = { registry = "https://pypi.org/simple" }
186
+ dependencies = [
187
+ { name = "filelock" },
188
+ { name = "fsspec" },
189
+ { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
190
+ { name = "packaging" },
191
+ { name = "pyyaml" },
192
+ { name = "requests" },
193
+ { name = "tqdm" },
194
+ { name = "typing-extensions" },
195
+ ]
196
+ sdist = { url = "https://files.pythonhosted.org/packages/98/63/4910c5fa9128fdadf6a9c5ac138e8b1b6cee4ca44bf7915bbfbce4e355ee/huggingface_hub-0.36.0.tar.gz", hash = "sha256:47b3f0e2539c39bf5cde015d63b72ec49baff67b6931c3d97f3f84532e2b8d25", size = 463358, upload-time = "2025-10-23T12:12:01.413Z" }
197
+ wheels = [
198
+ { url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d", size = 566094, upload-time = "2025-10-23T12:11:59.557Z" },
199
+ ]
200
+
201
+ [[package]]
202
+ name = "idna"
203
+ version = "3.11"
204
+ source = { registry = "https://pypi.org/simple" }
205
+ sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
206
+ wheels = [
207
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
208
+ ]
209
+
210
+ [[package]]
211
+ name = "iniconfig"
212
+ version = "2.3.0"
213
+ source = { registry = "https://pypi.org/simple" }
214
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
215
+ wheels = [
216
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
217
+ ]
218
+
219
+ [[package]]
220
+ name = "intel-openmp"
221
+ version = "2021.4.0"
222
+ source = { registry = "https://pypi.org/simple" }
223
+ wheels = [
224
+ { url = "https://files.pythonhosted.org/packages/45/18/527f247d673ff84c38e0b353b6901539b99e83066cd505be42ad341ab16d/intel_openmp-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:6e863d8fd3d7e8ef389d52cf97a50fe2afe1a19247e8c0d168ce021546f96fc9", size = 1860605, upload-time = "2021-09-28T17:03:44.748Z" },
225
+ { url = "https://files.pythonhosted.org/packages/6f/21/b590c0cc3888b24f2ac9898c41d852d7454a1695fbad34bee85dba6dc408/intel_openmp-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:eef4c8bcc8acefd7f5cd3b9384dbf73d59e2c99fc56545712ded913f43c4a94f", size = 3516906, upload-time = "2021-09-28T17:03:50.453Z" },
226
+ ]
227
+
228
+ [[package]]
229
+ name = "jinja2"
230
+ version = "3.1.6"
231
+ source = { registry = "https://pypi.org/simple" }
232
+ dependencies = [
233
+ { name = "markupsafe" },
234
+ ]
235
+ sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
236
+ wheels = [
237
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
238
+ ]
239
+
240
+ [[package]]
241
+ name = "markupsafe"
242
+ version = "3.0.3"
243
+ source = { registry = "https://pypi.org/simple" }
244
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
245
+ wheels = [
246
+ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
247
+ { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
248
+ { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
249
+ { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
250
+ { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
251
+ { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
252
+ { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
253
+ { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
254
+ { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
255
+ { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
256
+ { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
257
+ ]
258
+
259
+ [[package]]
260
+ name = "mkl"
261
+ version = "2021.4.0"
262
+ source = { registry = "https://pypi.org/simple" }
263
+ dependencies = [
264
+ { name = "intel-openmp" },
265
+ { name = "tbb" },
266
+ ]
267
+ wheels = [
268
+ { url = "https://files.pythonhosted.org/packages/ce/c6/892fe3bc91e811b78e4f85653864f2d92541d5e5c306b0cb3c2311e9ca64/mkl-2021.4.0-py2.py3-none-win32.whl", hash = "sha256:439c640b269a5668134e3dcbcea4350459c4a8bc46469669b2d67e07e3d330e8", size = 129048357, upload-time = "2021-09-28T17:08:58.256Z" },
269
+ { url = "https://files.pythonhosted.org/packages/fe/1c/5f6dbf18e8b73e0a5472466f0ea8d48ce9efae39bd2ff38cebf8dce61259/mkl-2021.4.0-py2.py3-none-win_amd64.whl", hash = "sha256:ceef3cafce4c009dd25f65d7ad0d833a0fbadc3d8903991ec92351fe5de1e718", size = 228499609, upload-time = "2021-09-28T17:09:19.683Z" },
270
+ ]
271
+
272
+ [[package]]
273
+ name = "mpmath"
274
+ version = "1.3.0"
275
+ source = { registry = "https://pypi.org/simple" }
276
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
277
+ 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"
284
+ source = { registry = "https://pypi.org/simple" }
285
+ sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
286
+ wheels = [
287
+ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
288
+ ]
289
+
290
+ [[package]]
291
+ name = "numpy"
292
+ version = "2.4.1"
293
+ source = { registry = "https://pypi.org/simple" }
294
+ sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" }
295
+ wheels = [
296
+ { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" },
297
+ { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" },
298
+ { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" },
299
+ { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" },
300
+ { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" },
301
+ { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" },
302
+ { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" },
303
+ { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" },
304
+ { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" },
305
+ { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" },
306
+ { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" },
307
+ ]
308
+
309
+ [[package]]
310
+ name = "nvidia-cublas-cu12"
311
+ version = "12.1.3.1"
312
+ source = { registry = "https://pypi.org/simple" }
313
+ wheels = [
314
+ { url = "https://files.pythonhosted.org/packages/37/6d/121efd7382d5b0284239f4ab1fc1590d86d34ed4a4a2fdb13b30ca8e5740/nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728", size = 410594774, upload-time = "2023-04-19T15:50:03.519Z" },
315
+ ]
316
+
317
+ [[package]]
318
+ name = "nvidia-cuda-cupti-cu12"
319
+ version = "12.1.105"
320
+ source = { registry = "https://pypi.org/simple" }
321
+ wheels = [
322
+ { url = "https://files.pythonhosted.org/packages/7e/00/6b218edd739ecfc60524e585ba8e6b00554dd908de2c9c66c1af3e44e18d/nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e", size = 14109015, upload-time = "2023-04-19T15:47:32.502Z" },
323
+ ]
324
+
325
+ [[package]]
326
+ name = "nvidia-cuda-nvrtc-cu12"
327
+ version = "12.1.105"
328
+ source = { registry = "https://pypi.org/simple" }
329
+ wheels = [
330
+ { url = "https://files.pythonhosted.org/packages/b6/9f/c64c03f49d6fbc56196664d05dba14e3a561038a81a638eeb47f4d4cfd48/nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2", size = 23671734, upload-time = "2023-04-19T15:48:32.42Z" },
331
+ ]
332
+
333
+ [[package]]
334
+ name = "nvidia-cuda-runtime-cu12"
335
+ version = "12.1.105"
336
+ source = { registry = "https://pypi.org/simple" }
337
+ wheels = [
338
+ { url = "https://files.pythonhosted.org/packages/eb/d5/c68b1d2cdfcc59e72e8a5949a37ddb22ae6cade80cd4a57a84d4c8b55472/nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40", size = 823596, upload-time = "2023-04-19T15:47:22.471Z" },
339
+ ]
340
+
341
+ [[package]]
342
+ name = "nvidia-cudnn-cu12"
343
+ version = "8.9.2.26"
344
+ source = { registry = "https://pypi.org/simple" }
345
+ dependencies = [
346
+ { name = "nvidia-cublas-cu12" },
347
+ ]
348
+ wheels = [
349
+ { url = "https://files.pythonhosted.org/packages/ff/74/a2e2be7fb83aaedec84f391f082cf765dfb635e7caa9b49065f73e4835d8/nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9", size = 731725872, upload-time = "2023-06-01T19:24:57.328Z" },
350
+ ]
351
+
352
+ [[package]]
353
+ name = "nvidia-cufft-cu12"
354
+ version = "11.0.2.54"
355
+ source = { registry = "https://pypi.org/simple" }
356
+ wheels = [
357
+ { url = "https://files.pythonhosted.org/packages/86/94/eb540db023ce1d162e7bea9f8f5aa781d57c65aed513c33ee9a5123ead4d/nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56", size = 121635161, upload-time = "2023-04-19T15:50:46Z" },
358
+ ]
359
+
360
+ [[package]]
361
+ name = "nvidia-curand-cu12"
362
+ version = "10.3.2.106"
363
+ source = { registry = "https://pypi.org/simple" }
364
+ wheels = [
365
+ { url = "https://files.pythonhosted.org/packages/44/31/4890b1c9abc496303412947fc7dcea3d14861720642b49e8ceed89636705/nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0", size = 56467784, upload-time = "2023-04-19T15:51:04.804Z" },
366
+ ]
367
+
368
+ [[package]]
369
+ name = "nvidia-cusolver-cu12"
370
+ version = "11.4.5.107"
371
+ source = { registry = "https://pypi.org/simple" }
372
+ dependencies = [
373
+ { name = "nvidia-cublas-cu12" },
374
+ { name = "nvidia-cusparse-cu12" },
375
+ { name = "nvidia-nvjitlink-cu12" },
376
+ ]
377
+ wheels = [
378
+ { url = "https://files.pythonhosted.org/packages/bc/1d/8de1e5c67099015c834315e333911273a8c6aaba78923dd1d1e25fc5f217/nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd", size = 124161928, upload-time = "2023-04-19T15:51:25.781Z" },
379
+ ]
380
+
381
+ [[package]]
382
+ name = "nvidia-cusparse-cu12"
383
+ version = "12.1.0.106"
384
+ source = { registry = "https://pypi.org/simple" }
385
+ dependencies = [
386
+ { name = "nvidia-nvjitlink-cu12" },
387
+ ]
388
+ wheels = [
389
+ { url = "https://files.pythonhosted.org/packages/65/5b/cfaeebf25cd9fdec14338ccb16f6b2c4c7fa9163aefcf057d86b9cc248bb/nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c", size = 195958278, upload-time = "2023-04-19T15:51:49.939Z" },
390
+ ]
391
+
392
+ [[package]]
393
+ name = "nvidia-nccl-cu12"
394
+ version = "2.20.5"
395
+ source = { registry = "https://pypi.org/simple" }
396
+ wheels = [
397
+ { url = "https://files.pythonhosted.org/packages/4b/2a/0a131f572aa09f741c30ccd45a8e56316e8be8dfc7bc19bf0ab7cfef7b19/nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56", size = 176249402, upload-time = "2024-03-06T04:30:20.663Z" },
398
+ ]
399
+
400
+ [[package]]
401
+ name = "nvidia-nvjitlink-cu12"
402
+ version = "12.9.86"
403
+ source = { registry = "https://pypi.org/simple" }
404
+ wheels = [
405
+ { url = "https://files.pythonhosted.org/packages/46/0c/c75bbfb967457a0b7670b8ad267bfc4fffdf341c074e0a80db06c24ccfd4/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:e3f1171dbdc83c5932a45f0f4c99180a70de9bd2718c1ab77d14104f6d7147f9", size = 39748338, upload-time = "2025-06-05T20:10:25.613Z" },
406
+ ]
407
+
408
+ [[package]]
409
+ name = "nvidia-nvtx-cu12"
410
+ version = "12.1.105"
411
+ source = { registry = "https://pypi.org/simple" }
412
+ wheels = [
413
+ { url = "https://files.pythonhosted.org/packages/da/d3/8057f0587683ed2fcd4dbfbdfdfa807b9160b809976099d36b8f60d08f03/nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5", size = 99138, upload-time = "2023-04-19T15:48:43.556Z" },
414
+ ]
415
+
416
+ [[package]]
417
+ name = "packaging"
418
+ version = "25.0"
419
+ source = { registry = "https://pypi.org/simple" }
420
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
421
+ 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"
428
+ source = { editable = "." }
429
+ dependencies = [
430
+ { name = "fastapi" },
431
+ { name = "pillow" },
432
+ { name = "pydantic" },
433
+ { name = "torch" },
434
+ { name = "transformers" },
435
+ { name = "uvicorn", extra = ["standard"] },
436
+ ]
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
+ ]
455
+ provides-extras = ["dev"]
456
+
457
+ [[package]]
458
+ name = "pillow"
459
+ version = "10.4.0"
460
+ source = { registry = "https://pypi.org/simple" }
461
+ sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" }
462
+ wheels = [
463
+ { url = "https://files.pythonhosted.org/packages/05/cb/0353013dc30c02a8be34eb91d25e4e4cf594b59e5a55ea1128fde1e5f8ea/pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", size = 3509350, upload-time = "2024-07-01T09:46:17.177Z" },
464
+ { url = "https://files.pythonhosted.org/packages/e7/cf/5c558a0f247e0bf9cec92bff9b46ae6474dd736f6d906315e60e4075f737/pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", size = 3374980, upload-time = "2024-07-01T09:46:19.169Z" },
465
+ { url = "https://files.pythonhosted.org/packages/84/48/6e394b86369a4eb68b8a1382c78dc092245af517385c086c5094e3b34428/pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", size = 4343799, upload-time = "2024-07-01T09:46:21.883Z" },
466
+ { url = "https://files.pythonhosted.org/packages/3b/f3/a8c6c11fa84b59b9df0cd5694492da8c039a24cd159f0f6918690105c3be/pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", size = 4459973, upload-time = "2024-07-01T09:46:24.321Z" },
467
+ { url = "https://files.pythonhosted.org/packages/7d/1b/c14b4197b80150fb64453585247e6fb2e1d93761fa0fa9cf63b102fde822/pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", size = 4370054, upload-time = "2024-07-01T09:46:26.825Z" },
468
+ { url = "https://files.pythonhosted.org/packages/55/77/40daddf677897a923d5d33329acd52a2144d54a9644f2a5422c028c6bf2d/pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", size = 4539484, upload-time = "2024-07-01T09:46:29.355Z" },
469
+ { url = "https://files.pythonhosted.org/packages/40/54/90de3e4256b1207300fb2b1d7168dd912a2fb4b2401e439ba23c2b2cabde/pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", size = 4477375, upload-time = "2024-07-01T09:46:31.756Z" },
470
+ { url = "https://files.pythonhosted.org/packages/13/24/1bfba52f44193860918ff7c93d03d95e3f8748ca1de3ceaf11157a14cf16/pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", size = 4608773, upload-time = "2024-07-01T09:46:33.73Z" },
471
+ { url = "https://files.pythonhosted.org/packages/55/04/5e6de6e6120451ec0c24516c41dbaf80cce1b6451f96561235ef2429da2e/pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", size = 2235690, upload-time = "2024-07-01T09:46:36.587Z" },
472
+ { url = "https://files.pythonhosted.org/packages/74/0a/d4ce3c44bca8635bd29a2eab5aa181b654a734a29b263ca8efe013beea98/pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", size = 2554951, upload-time = "2024-07-01T09:46:38.777Z" },
473
+ { url = "https://files.pythonhosted.org/packages/b5/ca/184349ee40f2e92439be9b3502ae6cfc43ac4b50bc4fc6b3de7957563894/pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", size = 2243427, upload-time = "2024-07-01T09:46:43.15Z" },
474
+ ]
475
+
476
+ [[package]]
477
+ name = "pluggy"
478
+ version = "1.6.0"
479
+ source = { registry = "https://pypi.org/simple" }
480
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
481
+ 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"
488
+ source = { registry = "https://pypi.org/simple" }
489
+ dependencies = [
490
+ { name = "annotated-types" },
491
+ { name = "pydantic-core" },
492
+ { name = "typing-extensions" },
493
+ ]
494
+ sdist = { url = "https://files.pythonhosted.org/packages/8c/99/d0a5dca411e0a017762258013ba9905cd6e7baa9a3fd1fe8b6529472902e/pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", size = 739834, upload-time = "2024-07-04T02:59:49.416Z" }
495
+ wheels = [
496
+ { url = "https://files.pythonhosted.org/packages/1f/fa/b7f815b8c9ad021c07f88875b601222ef5e70619391ade4a49234d12d278/pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8", size = 423875, upload-time = "2024-07-04T02:59:45.33Z" },
497
+ ]
498
+
499
+ [[package]]
500
+ name = "pydantic-core"
501
+ version = "2.20.1"
502
+ source = { registry = "https://pypi.org/simple" }
503
+ dependencies = [
504
+ { name = "typing-extensions" },
505
+ ]
506
+ sdist = { url = "https://files.pythonhosted.org/packages/12/e3/0d5ad91211dba310f7ded335f4dad871172b9cc9ce204f5a56d76ccd6247/pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4", size = 388371, upload-time = "2024-07-03T17:04:13.963Z" }
507
+ wheels = [
508
+ { url = "https://files.pythonhosted.org/packages/6f/47/ef0d60ae23c41aced42921728650460dc831a0adf604bfa66b76028cb4d0/pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231", size = 1839225, upload-time = "2024-07-03T17:01:15.981Z" },
509
+ { url = "https://files.pythonhosted.org/packages/6a/23/430f2878c9cd977a61bb39f71751d9310ec55cee36b3d5bf1752c6341fd0/pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9", size = 1768604, upload-time = "2024-07-03T17:01:18.188Z" },
510
+ { url = "https://files.pythonhosted.org/packages/9e/2b/ec4e7225dee79e0dc80ccc3c35ab33cc2c4bbb8a1a7ecf060e5e453651ec/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f", size = 1789767, upload-time = "2024-07-03T17:01:20.86Z" },
511
+ { url = "https://files.pythonhosted.org/packages/64/b0/38b24a1fa6d2f96af3148362e10737ec073768cd44d3ec21dca3be40a519/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52", size = 1772061, upload-time = "2024-07-03T17:01:23.9Z" },
512
+ { url = "https://files.pythonhosted.org/packages/5e/da/bb73274c42cb60decfa61e9eb0c9029da78b3b9af0a9de0309dbc8ff87b6/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237", size = 1974573, upload-time = "2024-07-03T17:01:26.318Z" },
513
+ { url = "https://files.pythonhosted.org/packages/c8/65/41693110fb3552556180460daffdb8bbeefb87fc026fd9aa4b849374015c/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe", size = 2625596, upload-time = "2024-07-03T17:01:28.775Z" },
514
+ { url = "https://files.pythonhosted.org/packages/09/b3/a5a54b47cccd1ab661ed5775235c5e06924753c2d4817737c5667bfa19a8/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e", size = 2099064, upload-time = "2024-07-03T17:01:30.962Z" },
515
+ { url = "https://files.pythonhosted.org/packages/52/fa/443a7a6ea54beaba45ff3a59f3d3e6e3004b7460bcfb0be77bcf98719d3b/pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24", size = 1900345, upload-time = "2024-07-03T17:01:33.634Z" },
516
+ { url = "https://files.pythonhosted.org/packages/8e/e6/9aca9ffae60f9cdf0183069de3e271889b628d0fb175913fcb3db5618fb1/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1", size = 1968252, upload-time = "2024-07-03T17:01:36.291Z" },
517
+ { url = "https://files.pythonhosted.org/packages/46/5e/6c716810ea20a6419188992973a73c2fb4eb99cd382368d0637ddb6d3c99/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", size = 2119191, upload-time = "2024-07-03T17:01:38.905Z" },
518
+ { url = "https://files.pythonhosted.org/packages/06/fc/6123b00a9240fbb9ae0babad7a005d51103d9a5d39c957a986f5cdd0c271/pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", size = 1717788, upload-time = "2024-07-03T17:01:41.329Z" },
519
+ { url = "https://files.pythonhosted.org/packages/d5/36/e61ad5a46607a469e2786f398cd671ebafcd9fb17f09a2359985c7228df5/pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", size = 1898188, upload-time = "2024-07-03T17:01:44.155Z" },
520
+ ]
521
+
522
+ [[package]]
523
+ name = "pytest"
524
+ version = "8.3.2"
525
+ source = { registry = "https://pypi.org/simple" }
526
+ dependencies = [
527
+ { name = "colorama", marker = "sys_platform == 'win32'" },
528
+ { name = "iniconfig" },
529
+ { name = "packaging" },
530
+ { name = "pluggy" },
531
+ ]
532
+ sdist = { url = "https://files.pythonhosted.org/packages/b4/8c/9862305bdcd6020bc7b45b1b5e7397a6caf1a33d3025b9a003b39075ffb2/pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce", size = 1439314, upload-time = "2024-07-25T10:40:00.159Z" }
533
+ 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"
540
+ source = { registry = "https://pypi.org/simple" }
541
+ sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
542
+ 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"
549
+ source = { registry = "https://pypi.org/simple" }
550
+ sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
551
+ wheels = [
552
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
553
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
554
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
555
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
556
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
557
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
558
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
559
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
560
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
561
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
562
+ ]
563
+
564
+ [[package]]
565
+ name = "regex"
566
+ version = "2026.1.15"
567
+ source = { registry = "https://pypi.org/simple" }
568
+ sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" }
569
+ wheels = [
570
+ { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" },
571
+ { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" },
572
+ { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" },
573
+ { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" },
574
+ { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" },
575
+ { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" },
576
+ { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" },
577
+ { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" },
578
+ { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" },
579
+ { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" },
580
+ { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" },
581
+ { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" },
582
+ { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" },
583
+ { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" },
584
+ { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" },
585
+ { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" },
586
+ ]
587
+
588
+ [[package]]
589
+ name = "requests"
590
+ version = "2.32.5"
591
+ source = { registry = "https://pypi.org/simple" }
592
+ dependencies = [
593
+ { name = "certifi" },
594
+ { name = "charset-normalizer" },
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]]
604
+ name = "safetensors"
605
+ version = "0.7.0"
606
+ source = { registry = "https://pypi.org/simple" }
607
+ sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" }
608
+ wheels = [
609
+ { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" },
610
+ { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" },
611
+ { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" },
612
+ { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" },
613
+ { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" },
614
+ { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" },
615
+ { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" },
616
+ { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" },
617
+ { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" },
618
+ { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" },
619
+ { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" },
620
+ { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" },
621
+ { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" },
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"
628
+ source = { registry = "https://pypi.org/simple" }
629
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
630
+ wheels = [
631
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
632
+ ]
633
+
634
+ [[package]]
635
+ name = "starlette"
636
+ version = "0.41.3"
637
+ source = { registry = "https://pypi.org/simple" }
638
+ dependencies = [
639
+ { name = "anyio" },
640
+ ]
641
+ sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159, upload-time = "2024-11-18T19:45:04.283Z" }
642
+ wheels = [
643
+ { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225, upload-time = "2024-11-18T19:45:02.027Z" },
644
+ ]
645
+
646
+ [[package]]
647
+ name = "sympy"
648
+ version = "1.14.0"
649
+ source = { registry = "https://pypi.org/simple" }
650
+ dependencies = [
651
+ { name = "mpmath" },
652
+ ]
653
+ sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
654
+ wheels = [
655
+ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
656
+ ]
657
+
658
+ [[package]]
659
+ name = "tbb"
660
+ version = "2021.13.1"
661
+ source = { registry = "https://pypi.org/simple" }
662
+ wheels = [
663
+ { url = "https://files.pythonhosted.org/packages/fb/8a/5062b00c378c051e26507e5eca8d3b5c91ed63f8a2139f6f0f422be84b02/tbb-2021.13.1-py3-none-win32.whl", hash = "sha256:00f5e5a70051650ddd0ab6247c0549521968339ec21002e475cd23b1cbf46d66", size = 248994, upload-time = "2024-08-07T15:10:08.934Z" },
664
+ { url = "https://files.pythonhosted.org/packages/9b/24/84ce997e8ae6296168a74d0d9c4dde572d90fb23fd7c0b219c30ff71e00e/tbb-2021.13.1-py3-none-win_amd64.whl", hash = "sha256:cbf024b2463fdab3ebe3fa6ff453026358e6b903839c80d647e08ad6d0796ee9", size = 286908, upload-time = "2024-08-07T15:09:05.677Z" },
665
+ ]
666
+
667
+ [[package]]
668
+ name = "tokenizers"
669
+ version = "0.19.1"
670
+ source = { registry = "https://pypi.org/simple" }
671
+ dependencies = [
672
+ { name = "huggingface-hub" },
673
+ ]
674
+ sdist = { url = "https://files.pythonhosted.org/packages/48/04/2071c150f374aab6d5e92aaec38d0f3c368d227dd9e0469a1f0966ac68d1/tokenizers-0.19.1.tar.gz", hash = "sha256:ee59e6680ed0fdbe6b724cf38bd70400a0c1dd623b07ac729087270caeac88e3", size = 321039, upload-time = "2024-04-17T21:40:41.849Z" }
675
+ wheels = [
676
+ { url = "https://files.pythonhosted.org/packages/63/90/2890cd096898dcdb596ee172cde40c0f54a9cf43b0736aa260a5501252af/tokenizers-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:621d670e1b1c281a1c9698ed89451395d318802ff88d1fc1accff0867a06f153", size = 2530580, upload-time = "2024-04-17T21:37:10.688Z" },
677
+ { url = "https://files.pythonhosted.org/packages/74/d1/f4e1e950adb36675dfd8f9d0f4be644f3f3aaf22a5677a4f5c81282b662e/tokenizers-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d924204a3dbe50b75630bd16f821ebda6a5f729928df30f582fb5aade90c818a", size = 2436682, upload-time = "2024-04-17T21:37:12.966Z" },
678
+ { url = "https://files.pythonhosted.org/packages/ed/30/89b321a16c58d233e301ec15072c0d3ed5014825e72da98604cd3ab2fba1/tokenizers-0.19.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4f3fefdc0446b1a1e6d81cd4c07088ac015665d2e812f6dbba4a06267d1a2c95", size = 3693494, upload-time = "2024-04-17T21:37:14.755Z" },
679
+ { url = "https://files.pythonhosted.org/packages/05/40/fa899f32de483500fbc78befd378fd7afba4270f17db707d1a78c0a4ddc3/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9620b78e0b2d52ef07b0d428323fb34e8ea1219c5eac98c2596311f20f1f9266", size = 3566541, upload-time = "2024-04-17T21:37:17.067Z" },
680
+ { url = "https://files.pythonhosted.org/packages/67/14/e7da32ae5fb4971830f1ef335932fae3fa57e76b537e852f146c850aefdf/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04ce49e82d100594715ac1b2ce87d1a36e61891a91de774755f743babcd0dd52", size = 3430792, upload-time = "2024-04-17T21:37:19.055Z" },
681
+ { url = "https://files.pythonhosted.org/packages/f2/4b/aae61bdb6ab584d2612170801703982ee0e35f8b6adacbeefe5a3b277621/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5c2ff13d157afe413bf7e25789879dd463e5a4abfb529a2d8f8473d8042e28f", size = 3962812, upload-time = "2024-04-17T21:37:21.008Z" },
682
+ { url = "https://files.pythonhosted.org/packages/0a/b6/f7b7ef89c4da7b20256e6eab23d3835f05d1ca8f451d31c16cbfe3cd9eb6/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3174c76efd9d08f836bfccaca7cfec3f4d1c0a4cf3acbc7236ad577cc423c840", size = 4024688, upload-time = "2024-04-17T21:37:23.659Z" },
683
+ { url = "https://files.pythonhosted.org/packages/80/54/12047a69f5b382d7ee72044dc89151a2dd0d13b2c9bdcc22654883704d31/tokenizers-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9d5b6c0e7a1e979bec10ff960fae925e947aab95619a6fdb4c1d8ff3708ce3", size = 3610961, upload-time = "2024-04-17T21:37:26.234Z" },
684
+ { url = "https://files.pythonhosted.org/packages/52/b7/1e8a913d18ac28feeda42d4d2d51781874398fb59cd1c1e2653a4b5742ed/tokenizers-0.19.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a179856d1caee06577220ebcfa332af046d576fb73454b8f4d4b0ba8324423ea", size = 9631367, upload-time = "2024-04-17T21:37:28.752Z" },
685
+ { url = "https://files.pythonhosted.org/packages/ac/3d/2284f6d99f8f21d09352b88b8cfefa24ab88468d962aeb0aa15c20d76b32/tokenizers-0.19.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:952b80dac1a6492170f8c2429bd11fcaa14377e097d12a1dbe0ef2fb2241e16c", size = 9950121, upload-time = "2024-04-17T21:37:31.741Z" },
686
+ { url = "https://files.pythonhosted.org/packages/2a/94/ec3369dbc9b7200c14c8c7a1a04c78b7a7398d0c001e1b7d1ffe30eb93a0/tokenizers-0.19.1-cp312-none-win32.whl", hash = "sha256:01d62812454c188306755c94755465505836fd616f75067abcae529c35edeb57", size = 2044069, upload-time = "2024-04-17T21:37:35.672Z" },
687
+ { url = "https://files.pythonhosted.org/packages/0c/97/80bff6937e0c67d30c0facacd4f0bcf4254e581aa4995c73cef8c8640e56/tokenizers-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:b70bfbe3a82d3e3fb2a5e9b22a39f8d1740c96c68b6ace0086b39074f08ab89a", size = 2214527, upload-time = "2024-04-17T21:37:39.19Z" },
688
+ ]
689
+
690
+ [[package]]
691
+ name = "torch"
692
+ version = "2.3.1"
693
+ source = { registry = "https://pypi.org/simple" }
694
+ dependencies = [
695
+ { name = "filelock" },
696
+ { name = "fsspec" },
697
+ { name = "jinja2" },
698
+ { name = "mkl", marker = "sys_platform == 'win32'" },
699
+ { name = "networkx" },
700
+ { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
701
+ { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
702
+ { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
703
+ { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
704
+ { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
705
+ { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
706
+ { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
707
+ { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
708
+ { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
709
+ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
710
+ { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
711
+ { name = "sympy" },
712
+ { name = "typing-extensions" },
713
+ ]
714
+ wheels = [
715
+ { url = "https://files.pythonhosted.org/packages/f3/82/68ccd49add4d21937f087871350905ffc709f32c92bf95334e7abf442147/torch-2.3.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:a486c0b1976a118805fc7c9641d02df7afbb0c21e6b555d3bb985c9f9601b61a", size = 779079866, upload-time = "2024-06-05T16:39:11.615Z" },
716
+ { url = "https://files.pythonhosted.org/packages/1b/a1/e8b286b85f19dd701a4b853c0554898b1fa69cea552c7d1ec39bc86f59aa/torch-2.3.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:224259821fe3e4c6f7edf1528e4fe4ac779c77addaa74215eb0b63a5c474d66c", size = 86853451, upload-time = "2024-06-05T16:42:05.146Z" },
717
+ { url = "https://files.pythonhosted.org/packages/af/77/cf6ceb000f8a064c7b373fb3471d85bcc39917d175af82fead4a2857c669/torch-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5fdccbf6f1334b2203a61a0e03821d5845f1421defe311dabeae2fc8fbeac2d", size = 159727172, upload-time = "2024-06-05T16:41:33.436Z" },
718
+ { url = "https://files.pythonhosted.org/packages/49/b6/1a2e3d43d4bc4ad7a4575b3745d707a68d5ed00ba263b205b6281bdd0921/torch-2.3.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:3c333dc2ebc189561514eda06e81df22bf8fb64e2384746b2cb9f04f96d1d4c8", size = 60978559, upload-time = "2024-06-05T16:41:27.77Z" },
719
+ ]
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]]
734
+ name = "transformers"
735
+ version = "4.44.2"
736
+ source = { registry = "https://pypi.org/simple" }
737
+ dependencies = [
738
+ { name = "filelock" },
739
+ { name = "huggingface-hub" },
740
+ { name = "numpy" },
741
+ { name = "packaging" },
742
+ { name = "pyyaml" },
743
+ { name = "regex" },
744
+ { name = "requests" },
745
+ { name = "safetensors" },
746
+ { name = "tokenizers" },
747
+ { name = "tqdm" },
748
+ ]
749
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/a3/81de49357a3c6ac4421d48d9662b53293838f217baf3f3bb9eb55f89fab6/transformers-4.44.2.tar.gz", hash = "sha256:36aa17cc92ee154058e426d951684a2dab48751b35b49437896f898931270826", size = 8110312, upload-time = "2024-08-22T16:56:33.522Z" }
750
+ wheels = [
751
+ { url = "https://files.pythonhosted.org/packages/75/35/07c9879163b603f0e464b0f6e6e628a2340cfc7cdc5ca8e7d52d776710d4/transformers-4.44.2-py3-none-any.whl", hash = "sha256:1c02c65e7bfa5e52a634aff3da52138b583fc6f263c1f28d547dc144ba3d412d", size = 9465369, upload-time = "2024-08-22T16:56:29.207Z" },
752
+ ]
753
+
754
+ [[package]]
755
+ name = "typing-extensions"
756
+ version = "4.15.0"
757
+ source = { registry = "https://pypi.org/simple" }
758
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
759
+ 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"
766
+ source = { registry = "https://pypi.org/simple" }
767
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
768
+ wheels = [
769
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
770
+ ]
771
+
772
+ [[package]]
773
+ name = "uvicorn"
774
+ version = "0.30.6"
775
+ source = { registry = "https://pypi.org/simple" }
776
+ dependencies = [
777
+ { name = "click" },
778
+ { name = "h11" },
779
+ ]
780
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/01/5e637e7aa9dd031be5376b9fb749ec20b86f5a5b6a49b87fabd374d5fa9f/uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788", size = 42825, upload-time = "2024-08-13T09:27:35.098Z" }
781
+ wheels = [
782
+ { url = "https://files.pythonhosted.org/packages/f5/8e/cdc7d6263db313030e4c257dd5ba3909ebc4e4fb53ad62d5f09b1a2f5458/uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5", size = 62835, upload-time = "2024-08-13T09:27:33.536Z" },
783
+ ]
784
+
785
+ [package.optional-dependencies]
786
+ standard = [
787
+ { name = "colorama", marker = "sys_platform == 'win32'" },
788
+ { name = "httptools" },
789
+ { name = "python-dotenv" },
790
+ { name = "pyyaml" },
791
+ { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
792
+ { name = "watchfiles" },
793
+ { name = "websockets" },
794
+ ]
795
+
796
+ [[package]]
797
+ name = "uvloop"
798
+ version = "0.22.1"
799
+ source = { registry = "https://pypi.org/simple" }
800
+ sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
801
+ wheels = [
802
+ { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" },
803
+ { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" },
804
+ { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" },
805
+ { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" },
806
+ { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" },
807
+ { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" },
808
+ ]
809
+
810
+ [[package]]
811
+ name = "watchfiles"
812
+ version = "1.1.1"
813
+ source = { registry = "https://pypi.org/simple" }
814
+ dependencies = [
815
+ { name = "anyio" },
816
+ ]
817
+ sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" }
818
+ wheels = [
819
+ { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" },
820
+ { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" },
821
+ { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" },
822
+ { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" },
823
+ { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" },
824
+ { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" },
825
+ { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" },
826
+ { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" },
827
+ { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" },
828
+ { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" },
829
+ { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" },
830
+ { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" },
831
+ { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" },
832
+ ]
833
+
834
+ [[package]]
835
+ name = "websockets"
836
+ version = "16.0"
837
+ source = { registry = "https://pypi.org/simple" }
838
+ sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
839
+ wheels = [
840
+ { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
841
+ { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
842
+ { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
843
+ { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
844
+ { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
845
+ { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
846
+ { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
847
+ { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
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
+ ]