Deploy FaceCloak
Browse files- .gitattributes +3 -0
- README.md +98 -12
- app.py +5 -0
- facecloak/__init__.py +3 -0
- facecloak/__pycache__/__init__.cpython-312.pyc +0 -0
- facecloak/__pycache__/cloaking.cpython-312.pyc +0 -0
- facecloak/__pycache__/deploy.cpython-312.pyc +0 -0
- facecloak/__pycache__/environment.cpython-312.pyc +0 -0
- facecloak/__pycache__/errors.cpython-312.pyc +0 -0
- facecloak/__pycache__/interface.cpython-312.pyc +0 -0
- facecloak/__pycache__/models.cpython-312.pyc +0 -0
- facecloak/__pycache__/pipeline.cpython-312.pyc +0 -0
- facecloak/__pycache__/project.cpython-312.pyc +0 -0
- facecloak/cloaking.py +123 -0
- facecloak/deploy.py +121 -0
- facecloak/environment.py +97 -0
- facecloak/errors.py +7 -0
- facecloak/interface.py +229 -0
- facecloak/models.py +38 -0
- facecloak/pipeline.py +115 -0
- facecloak/project.py +47 -0
- main.py +5 -0
- pyproject.toml +28 -0
- requirements.txt +7 -0
- scripts/create_or_update_space.py +20 -0
- tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_cloaking.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_deploy.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_environment.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_integration.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_interface.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_pipeline.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/__pycache__/test_project.cpython-312-pytest-9.0.3.pyc +0 -0
- tests/conftest.py +7 -0
- tests/fixtures/README.md +9 -0
- tests/fixtures/faces/george_a.jpg +3 -0
- tests/fixtures/faces/obama_a.jpg +3 -0
- tests/fixtures/faces/obama_b.jpg +3 -0
- tests/test_cloaking.py +48 -0
- tests/test_deploy.py +84 -0
- tests/test_environment.py +24 -0
- tests/test_integration.py +40 -0
- tests/test_interface.py +40 -0
- tests/test_pipeline.py +83 -0
- tests/test_project.py +17 -0
- uv.lock +1032 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
tests/fixtures/faces/george_a.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
tests/fixtures/faces/obama_a.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
tests/fixtures/faces/obama_b.jpg filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,12 +1,98 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FaceCloak
|
| 2 |
+
|
| 3 |
+
FaceCloak is a fully local biometric privacy project that uses white-box adversarial optimization to push a face image away from its original embedding while keeping the image visually unchanged for a human viewer.
|
| 4 |
+
|
| 5 |
+
Phases 2 and 3 are now complete in this repository. The app can detect and align a face with MTCNN, extract a 512-dimensional embedding with `InceptionResnetV1`, compare embeddings with cosine similarity, and run a white-box PGD attack that pushes the cloaked face away from its original identity in embedding space.
|
| 6 |
+
|
| 7 |
+
## Phase 2 and 3 Deliverables
|
| 8 |
+
|
| 9 |
+
- Cached MTCNN face detection and alignment on CPU
|
| 10 |
+
- Frozen `InceptionResnetV1(pretrained="vggface2")` embedding extraction
|
| 11 |
+
- Cosine similarity scoring for same-person vs different-person checks
|
| 12 |
+
- L-infinity PGD cloaking over the aligned face tensor
|
| 13 |
+
- Reverse conversion from the standardized tensor back to a downloadable PIL image
|
| 14 |
+
- A Gradio app with comparison and cloaking workflows
|
| 15 |
+
- A programmatic Hugging Face Space deployment helper that reads `FACECLOAK_HF_TOKEN` from `.env`
|
| 16 |
+
- Expanded test coverage, including real-image integration checks
|
| 17 |
+
|
| 18 |
+
## Quickstart
|
| 19 |
+
|
| 20 |
+
These commands keep Python and the `uv` cache inside the repository so the setup stays self-contained.
|
| 21 |
+
|
| 22 |
+
```powershell
|
| 23 |
+
$env:UV_CACHE_DIR = "$PWD/.uv-cache"
|
| 24 |
+
$env:UV_PYTHON_INSTALL_DIR = "$PWD/.uv-python"
|
| 25 |
+
uv python install 3.12
|
| 26 |
+
uv sync
|
| 27 |
+
uv run python main.py
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
The first model-backed run will download the pretrained FaceNet weights into `.torch-cache/`.
|
| 31 |
+
|
| 32 |
+
## App Workflow
|
| 33 |
+
|
| 34 |
+
The Gradio app now exposes two tabs:
|
| 35 |
+
|
| 36 |
+
- `Cloak Face`: upload one portrait, align the primary face, run PGD, and inspect the cloaked face plus perturbation preview
|
| 37 |
+
- `Compare Faces`: upload two portraits and measure cosine similarity between their face embeddings
|
| 38 |
+
|
| 39 |
+
The cloaking engine works on the aligned face crop returned by MTCNN. The tensors entering FaceNet are in the facenet-pytorch standardized range produced by `fixed_image_standardization`, which is why the default perturbation budget is specified on the `[-1, 1]`-style scale instead of in raw `0-255` pixels.
|
| 40 |
+
|
| 41 |
+
## PGD Objective
|
| 42 |
+
|
| 43 |
+
The PGD loop uses an ascent step of the form `delta += alpha * sign(grad)`. Because the step is ascent, the objective is defined as:
|
| 44 |
+
|
| 45 |
+
```text
|
| 46 |
+
objective = -cosine_similarity(original_embedding, cloaked_embedding) - lambda * ||delta||²
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
Maximizing that objective lowers cosine similarity while gently discouraging unnecessarily large perturbations. This is mathematically equivalent to minimizing cosine similarity with a descent update.
|
| 50 |
+
|
| 51 |
+
## Run Tests
|
| 52 |
+
|
| 53 |
+
```powershell
|
| 54 |
+
$env:UV_CACHE_DIR = "$PWD/.uv-cache"
|
| 55 |
+
$env:UV_PYTHON_INSTALL_DIR = "$PWD/.uv-python"
|
| 56 |
+
uv run pytest
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
The suite includes real-image integration tests using public-domain portrait photos in `tests/fixtures/faces/`.
|
| 60 |
+
|
| 61 |
+
## Hugging Face Space Notes
|
| 62 |
+
|
| 63 |
+
Hugging Face Spaces still require a root-level `app.py` and `requirements.txt`. This repository now includes both:
|
| 64 |
+
|
| 65 |
+
- `app.py` exposes the Gradio demo for Spaces
|
| 66 |
+
- `requirements.txt` contains only the direct runtime dependencies, pinned to the versions validated locally
|
| 67 |
+
- `uv.lock` remains the source of truth for fully reproducible local development
|
| 68 |
+
|
| 69 |
+
You can create or update the Space programmatically with the token stored in `.env`:
|
| 70 |
+
|
| 71 |
+
```powershell
|
| 72 |
+
$env:UV_CACHE_DIR = "$PWD/.uv-cache"
|
| 73 |
+
$env:UV_PYTHON_INSTALL_DIR = "$PWD/.uv-python"
|
| 74 |
+
uv run python scripts/create_or_update_space.py
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
The script will:
|
| 78 |
+
|
| 79 |
+
- read `FACECLOAK_HF_TOKEN` from the environment or `.env`
|
| 80 |
+
- determine the Hugging Face username with `whoami`
|
| 81 |
+
- create `username/facecloak` as a Gradio Space on `cpu-basic` hardware if it does not already exist
|
| 82 |
+
- upload the repository contents needed to build the Space
|
| 83 |
+
|
| 84 |
+
## Benchmarks Verified Locally
|
| 85 |
+
|
| 86 |
+
Using the public-domain portrait fixtures bundled for integration tests:
|
| 87 |
+
|
| 88 |
+
- same-person pairs scored above `0.8`
|
| 89 |
+
- different-person pairs scored below `0.3`
|
| 90 |
+
- the default cloaking hyperparameters (`epsilon=0.03`, `num_steps=30`) drove original-vs-cloaked similarity sharply downward on CPU
|
| 91 |
+
|
| 92 |
+
## Verified in Phase 1
|
| 93 |
+
|
| 94 |
+
- `torch` imports successfully
|
| 95 |
+
- `torch.cuda.is_available()` is `False`, which matches the intended CPU deployment target
|
| 96 |
+
- Basic tensor math works
|
| 97 |
+
- The Gradio app builds successfully
|
| 98 |
+
- The repository has automated tests for the environment scaffold
|
app.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from facecloak.interface import APP_CSS, APP_THEME, demo
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
if __name__ == "__main__":
|
| 5 |
+
demo.launch(theme=APP_THEME, css=APP_CSS)
|
facecloak/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FaceCloak package."""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.1.0"
|
facecloak/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (225 Bytes). View file
|
|
|
facecloak/__pycache__/cloaking.cpython-312.pyc
ADDED
|
Binary file (6.28 kB). View file
|
|
|
facecloak/__pycache__/deploy.cpython-312.pyc
ADDED
|
Binary file (4.95 kB). View file
|
|
|
facecloak/__pycache__/environment.cpython-312.pyc
ADDED
|
Binary file (4.91 kB). View file
|
|
|
facecloak/__pycache__/errors.cpython-312.pyc
ADDED
|
Binary file (542 Bytes). View file
|
|
|
facecloak/__pycache__/interface.cpython-312.pyc
ADDED
|
Binary file (10.3 kB). View file
|
|
|
facecloak/__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
facecloak/__pycache__/pipeline.cpython-312.pyc
ADDED
|
Binary file (6.71 kB). View file
|
|
|
facecloak/__pycache__/project.cpython-312.pyc
ADDED
|
Binary file (1.74 kB). View file
|
|
|
facecloak/cloaking.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Projected Gradient Descent cloaking for aligned face tensors."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
|
| 7 |
+
from PIL import Image
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
|
| 11 |
+
from facecloak.models import get_embedding_model
|
| 12 |
+
from facecloak.pipeline import (
|
| 13 |
+
DISPLAY_MAX,
|
| 14 |
+
DISPLAY_MIN,
|
| 15 |
+
_prepare_face_batch,
|
| 16 |
+
perturbation_preview_image,
|
| 17 |
+
standardized_tensor_to_pil,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass(frozen=True, slots=True)
|
| 22 |
+
class CloakHyperparameters:
|
| 23 |
+
epsilon: float = 0.03
|
| 24 |
+
alpha: float | None = None
|
| 25 |
+
num_steps: int = 30
|
| 26 |
+
l2_lambda: float = 0.01
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@dataclass(slots=True)
|
| 30 |
+
class CloakResult:
|
| 31 |
+
original_face_image: Image.Image
|
| 32 |
+
cloaked_face_image: Image.Image
|
| 33 |
+
perturbation_preview: Image.Image
|
| 34 |
+
cloaked_face_tensor: torch.Tensor
|
| 35 |
+
delta_tensor: torch.Tensor
|
| 36 |
+
original_similarity: float
|
| 37 |
+
final_similarity: float
|
| 38 |
+
similarity_drop: float
|
| 39 |
+
loss_history: list[float]
|
| 40 |
+
similarity_history: list[float]
|
| 41 |
+
delta_l_inf: float
|
| 42 |
+
delta_rms: float
|
| 43 |
+
parameters: CloakHyperparameters
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def cloak_face_tensor(
|
| 47 |
+
face_tensor: torch.Tensor,
|
| 48 |
+
*,
|
| 49 |
+
model: torch.nn.Module | None = None,
|
| 50 |
+
parameters: CloakHyperparameters | None = None,
|
| 51 |
+
) -> CloakResult:
|
| 52 |
+
parameters = parameters or CloakHyperparameters()
|
| 53 |
+
if parameters.epsilon <= 0.0:
|
| 54 |
+
raise ValueError("epsilon must be positive.")
|
| 55 |
+
if parameters.num_steps <= 0:
|
| 56 |
+
raise ValueError("num_steps must be positive.")
|
| 57 |
+
if parameters.l2_lambda < 0.0:
|
| 58 |
+
raise ValueError("l2_lambda cannot be negative.")
|
| 59 |
+
|
| 60 |
+
alpha = parameters.alpha if parameters.alpha is not None else parameters.epsilon / 10.0
|
| 61 |
+
if alpha <= 0.0:
|
| 62 |
+
raise ValueError("alpha must be positive.")
|
| 63 |
+
|
| 64 |
+
model = model or get_embedding_model()
|
| 65 |
+
original_batch = _prepare_face_batch(face_tensor).to(
|
| 66 |
+
next(model.parameters()).device
|
| 67 |
+
).detach()
|
| 68 |
+
original_embedding = model(original_batch).detach()
|
| 69 |
+
delta = torch.zeros_like(original_batch, requires_grad=True)
|
| 70 |
+
|
| 71 |
+
loss_history: list[float] = []
|
| 72 |
+
similarity_history: list[float] = []
|
| 73 |
+
|
| 74 |
+
for _ in range(parameters.num_steps):
|
| 75 |
+
if delta.grad is not None:
|
| 76 |
+
delta.grad.zero_()
|
| 77 |
+
|
| 78 |
+
cloaked_batch = torch.clamp(original_batch + delta, DISPLAY_MIN, DISPLAY_MAX)
|
| 79 |
+
cloaked_embedding = model(cloaked_batch)
|
| 80 |
+
cosine = F.cosine_similarity(original_embedding, cloaked_embedding, dim=1).mean()
|
| 81 |
+
|
| 82 |
+
# We use a PGD ascent step, so maximizing -cosine lowers the embedding similarity.
|
| 83 |
+
objective = -cosine - parameters.l2_lambda * delta.pow(2).mean()
|
| 84 |
+
objective.backward()
|
| 85 |
+
|
| 86 |
+
with torch.no_grad():
|
| 87 |
+
delta.add_(alpha * delta.grad.sign())
|
| 88 |
+
delta.clamp_(-parameters.epsilon, parameters.epsilon)
|
| 89 |
+
delta.copy_(
|
| 90 |
+
torch.clamp(original_batch + delta, DISPLAY_MIN, DISPLAY_MAX) - original_batch
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
loss_history.append(float(objective.detach().item()))
|
| 94 |
+
similarity_history.append(float(cosine.detach().item()))
|
| 95 |
+
|
| 96 |
+
final_batch = torch.clamp(original_batch + delta, DISPLAY_MIN, DISPLAY_MAX).detach()
|
| 97 |
+
final_embedding = model(final_batch).detach()
|
| 98 |
+
final_similarity = float(
|
| 99 |
+
F.cosine_similarity(original_embedding, final_embedding, dim=1).mean().item()
|
| 100 |
+
)
|
| 101 |
+
delta_cpu = delta.detach().cpu()
|
| 102 |
+
final_cpu = final_batch.detach().cpu()
|
| 103 |
+
|
| 104 |
+
return CloakResult(
|
| 105 |
+
original_face_image=standardized_tensor_to_pil(original_batch.cpu()),
|
| 106 |
+
cloaked_face_image=standardized_tensor_to_pil(final_cpu),
|
| 107 |
+
perturbation_preview=perturbation_preview_image(delta_cpu),
|
| 108 |
+
cloaked_face_tensor=final_cpu[0],
|
| 109 |
+
delta_tensor=delta_cpu[0],
|
| 110 |
+
original_similarity=1.0,
|
| 111 |
+
final_similarity=final_similarity,
|
| 112 |
+
similarity_drop=1.0 - final_similarity,
|
| 113 |
+
loss_history=loss_history,
|
| 114 |
+
similarity_history=similarity_history,
|
| 115 |
+
delta_l_inf=float(delta_cpu.abs().max().item()),
|
| 116 |
+
delta_rms=float(delta_cpu.pow(2).mean().sqrt().item()),
|
| 117 |
+
parameters=CloakHyperparameters(
|
| 118 |
+
epsilon=parameters.epsilon,
|
| 119 |
+
alpha=alpha,
|
| 120 |
+
num_steps=parameters.num_steps,
|
| 121 |
+
l2_lambda=parameters.l2_lambda,
|
| 122 |
+
),
|
| 123 |
+
)
|
facecloak/deploy.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Programmatic Hugging Face Space creation and upload."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
from huggingface_hub import HfApi, SpaceHardware
|
| 10 |
+
|
| 11 |
+
from facecloak.errors import FaceCloakError
|
| 12 |
+
from facecloak.project import (
|
| 13 |
+
PROJECT_ROOT,
|
| 14 |
+
PROJECT_SLUG,
|
| 15 |
+
SPACE_UPLOAD_ALLOW_PATTERNS,
|
| 16 |
+
SPACE_URL_TEMPLATE,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
HF_TOKEN_ENV_VAR = "FACECLOAK_HF_TOKEN"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass(frozen=True, slots=True)
|
| 23 |
+
class SpaceDeploymentResult:
|
| 24 |
+
repo_id: str
|
| 25 |
+
space_url: str
|
| 26 |
+
runtime_stage: str | None
|
| 27 |
+
hardware: str | None
|
| 28 |
+
commit_oid: str | None
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def read_env_file(env_path: Path | None = None) -> dict[str, str]:
|
| 32 |
+
env_path = env_path or PROJECT_ROOT / ".env"
|
| 33 |
+
if not env_path.exists():
|
| 34 |
+
return {}
|
| 35 |
+
|
| 36 |
+
values: dict[str, str] = {}
|
| 37 |
+
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
| 38 |
+
line = raw_line.strip()
|
| 39 |
+
if not line or line.startswith("#") or "=" not in line:
|
| 40 |
+
continue
|
| 41 |
+
|
| 42 |
+
key, raw_value = line.split("=", 1)
|
| 43 |
+
key = key.strip()
|
| 44 |
+
value = raw_value.strip().strip('"').strip("'")
|
| 45 |
+
values[key] = value
|
| 46 |
+
|
| 47 |
+
return values
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def resolve_hf_token(env_path: Path | None = None) -> str:
|
| 51 |
+
token = os.environ.get(HF_TOKEN_ENV_VAR)
|
| 52 |
+
if token:
|
| 53 |
+
return token
|
| 54 |
+
|
| 55 |
+
token = read_env_file(env_path).get(HF_TOKEN_ENV_VAR)
|
| 56 |
+
if token:
|
| 57 |
+
return token
|
| 58 |
+
|
| 59 |
+
raise FaceCloakError(
|
| 60 |
+
f"{HF_TOKEN_ENV_VAR} was not found. Add it to the environment or to a local .env file."
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def default_space_repo_id(api: HfApi, token: str) -> str:
|
| 65 |
+
whoami = api.whoami(token=token, cache=False)
|
| 66 |
+
username = whoami.get("name")
|
| 67 |
+
if not username:
|
| 68 |
+
raise FaceCloakError("Could not determine the Hugging Face username from the provided token.")
|
| 69 |
+
return f"{username}/{PROJECT_SLUG}"
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def create_or_update_space(
|
| 73 |
+
*,
|
| 74 |
+
repo_id: str | None = None,
|
| 75 |
+
token: str | None = None,
|
| 76 |
+
api: HfApi | None = None,
|
| 77 |
+
folder_path: Path | None = None,
|
| 78 |
+
) -> SpaceDeploymentResult:
|
| 79 |
+
api = api or HfApi()
|
| 80 |
+
token = token or resolve_hf_token()
|
| 81 |
+
repo_id = repo_id or default_space_repo_id(api, token)
|
| 82 |
+
folder_path = folder_path or PROJECT_ROOT
|
| 83 |
+
|
| 84 |
+
api.create_repo(
|
| 85 |
+
repo_id=repo_id,
|
| 86 |
+
token=token,
|
| 87 |
+
repo_type="space",
|
| 88 |
+
exist_ok=True,
|
| 89 |
+
space_sdk="gradio",
|
| 90 |
+
space_hardware=SpaceHardware.CPU_BASIC,
|
| 91 |
+
)
|
| 92 |
+
commit_info = api.upload_folder(
|
| 93 |
+
repo_id=repo_id,
|
| 94 |
+
repo_type="space",
|
| 95 |
+
folder_path=folder_path,
|
| 96 |
+
token=token,
|
| 97 |
+
allow_patterns=list(SPACE_UPLOAD_ALLOW_PATTERNS),
|
| 98 |
+
commit_message="Deploy FaceCloak",
|
| 99 |
+
)
|
| 100 |
+
runtime = api.get_space_runtime(repo_id, token=token)
|
| 101 |
+
|
| 102 |
+
return SpaceDeploymentResult(
|
| 103 |
+
repo_id=repo_id,
|
| 104 |
+
space_url=SPACE_URL_TEMPLATE.format(repo_id=repo_id),
|
| 105 |
+
runtime_stage=getattr(runtime, "stage", None),
|
| 106 |
+
hardware=getattr(runtime, "hardware", None),
|
| 107 |
+
commit_oid=getattr(commit_info, "oid", None),
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def deployment_markdown(result: SpaceDeploymentResult) -> str:
|
| 112 |
+
return "\n".join(
|
| 113 |
+
[
|
| 114 |
+
"### Hugging Face Space",
|
| 115 |
+
f"- Repository: `{result.repo_id}`",
|
| 116 |
+
f"- URL: {result.space_url}",
|
| 117 |
+
f"- Runtime Stage: `{result.runtime_stage or 'unknown'}`",
|
| 118 |
+
f"- Hardware: `{result.hardware or 'unknown'}`",
|
| 119 |
+
f"- Commit OID: `{result.commit_oid or 'unknown'}`",
|
| 120 |
+
]
|
| 121 |
+
)
|
facecloak/environment.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Runtime diagnostics for the local and Hugging Face Space environments."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from importlib.metadata import PackageNotFoundError, version
|
| 7 |
+
import platform
|
| 8 |
+
|
| 9 |
+
from facecloak.project import PHASE_LABEL, PHASE_SUMMARY, TORCH_CACHE_DIR
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass(frozen=True, slots=True)
|
| 13 |
+
class RuntimeReport:
|
| 14 |
+
python_version: str
|
| 15 |
+
operating_system: str
|
| 16 |
+
torch_version: str
|
| 17 |
+
torchvision_version: str
|
| 18 |
+
facenet_pytorch_version: str
|
| 19 |
+
gradio_version: str
|
| 20 |
+
huggingface_hub_version: str
|
| 21 |
+
numpy_version: str
|
| 22 |
+
pillow_version: str
|
| 23 |
+
cuda_available: bool
|
| 24 |
+
device: str
|
| 25 |
+
tensor_sanity: tuple[int, ...]
|
| 26 |
+
torch_cache_dir: str
|
| 27 |
+
phase: str
|
| 28 |
+
status: str
|
| 29 |
+
notes: str
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _installed_version(distribution_name: str) -> str:
|
| 33 |
+
try:
|
| 34 |
+
return version(distribution_name)
|
| 35 |
+
except PackageNotFoundError:
|
| 36 |
+
return "not installed"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def collect_runtime_report() -> RuntimeReport:
|
| 40 |
+
import torch
|
| 41 |
+
|
| 42 |
+
tensor_sanity = tuple((torch.arange(3) + 1).tolist())
|
| 43 |
+
cuda_available = bool(torch.cuda.is_available())
|
| 44 |
+
device = "cuda" if cuda_available else "cpu"
|
| 45 |
+
status = "ready" if tensor_sanity == (1, 2, 3) else "check failed"
|
| 46 |
+
|
| 47 |
+
return RuntimeReport(
|
| 48 |
+
python_version=platform.python_version(),
|
| 49 |
+
operating_system=platform.platform(),
|
| 50 |
+
torch_version=torch.__version__,
|
| 51 |
+
torchvision_version=_installed_version("torchvision"),
|
| 52 |
+
facenet_pytorch_version=_installed_version("facenet-pytorch"),
|
| 53 |
+
gradio_version=_installed_version("gradio"),
|
| 54 |
+
huggingface_hub_version=_installed_version("huggingface-hub"),
|
| 55 |
+
numpy_version=_installed_version("numpy"),
|
| 56 |
+
pillow_version=_installed_version("pillow"),
|
| 57 |
+
cuda_available=cuda_available,
|
| 58 |
+
device=device,
|
| 59 |
+
tensor_sanity=tensor_sanity,
|
| 60 |
+
torch_cache_dir=str(TORCH_CACHE_DIR),
|
| 61 |
+
phase=PHASE_LABEL,
|
| 62 |
+
status=status,
|
| 63 |
+
notes="CPU execution remains the intended deployment target for the Hugging Face Space.",
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def format_runtime_markdown(report: RuntimeReport) -> str:
|
| 68 |
+
cuda_label = "Yes" if report.cuda_available else "No"
|
| 69 |
+
tensor_label = ", ".join(str(value) for value in report.tensor_sanity)
|
| 70 |
+
|
| 71 |
+
return "\n".join(
|
| 72 |
+
[
|
| 73 |
+
"### Runtime Diagnostics",
|
| 74 |
+
f"- Phase: {report.phase}",
|
| 75 |
+
f"- Status: {report.status}",
|
| 76 |
+
f"- Python: `{report.python_version}`",
|
| 77 |
+
f"- Platform: `{report.operating_system}`",
|
| 78 |
+
f"- Torch: `{report.torch_version}`",
|
| 79 |
+
f"- Torchvision: `{report.torchvision_version}`",
|
| 80 |
+
f"- facenet-pytorch: `{report.facenet_pytorch_version}`",
|
| 81 |
+
f"- NumPy: `{report.numpy_version}`",
|
| 82 |
+
f"- Pillow: `{report.pillow_version}`",
|
| 83 |
+
f"- Gradio: `{report.gradio_version}`",
|
| 84 |
+
f"- huggingface-hub: `{report.huggingface_hub_version}`",
|
| 85 |
+
f"- CUDA Available: `{cuda_label}`",
|
| 86 |
+
f"- Active Device: `{report.device}`",
|
| 87 |
+
f"- Tensor Sanity: `[{tensor_label}]`",
|
| 88 |
+
f"- Torch Cache: `{report.torch_cache_dir}`",
|
| 89 |
+
f"- Notes: {report.notes}",
|
| 90 |
+
"",
|
| 91 |
+
PHASE_SUMMARY,
|
| 92 |
+
]
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def render_runtime_markdown() -> str:
|
| 97 |
+
return format_runtime_markdown(collect_runtime_report())
|
facecloak/errors.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""User-facing exceptions for FaceCloak workflows."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class FaceCloakError(RuntimeError):
|
| 7 |
+
"""A readable error that can be surfaced directly in the UI."""
|
facecloak/interface.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gradio interface for the Phase 2 and 3 FaceCloak workflow."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
|
| 7 |
+
from facecloak.cloaking import CloakHyperparameters, cloak_face_tensor
|
| 8 |
+
from facecloak.environment import render_runtime_markdown
|
| 9 |
+
from facecloak.errors import FaceCloakError
|
| 10 |
+
from facecloak.pipeline import (
|
| 11 |
+
cosine_similarity,
|
| 12 |
+
detect_primary_face,
|
| 13 |
+
extract_embedding_numpy,
|
| 14 |
+
)
|
| 15 |
+
from facecloak.project import (
|
| 16 |
+
PHASE_LABEL,
|
| 17 |
+
PHASE_STATUS,
|
| 18 |
+
PHASE_SUMMARY,
|
| 19 |
+
PROJECT_NAME,
|
| 20 |
+
PROJECT_TAGLINE,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
APP_CSS = """
|
| 24 |
+
.gradio-container {
|
| 25 |
+
background:
|
| 26 |
+
radial-gradient(circle at top left, rgba(180, 214, 255, 0.35), transparent 32%),
|
| 27 |
+
radial-gradient(circle at top right, rgba(255, 212, 170, 0.30), transparent 28%),
|
| 28 |
+
linear-gradient(135deg, #f6f2eb 0%, #eef4fb 100%);
|
| 29 |
+
}
|
| 30 |
+
#hero {
|
| 31 |
+
padding-top: 0.75rem;
|
| 32 |
+
}
|
| 33 |
+
.panel {
|
| 34 |
+
border: 1px solid rgba(32, 52, 84, 0.10);
|
| 35 |
+
border-radius: 18px;
|
| 36 |
+
background: rgba(255, 255, 255, 0.82);
|
| 37 |
+
padding: 0.75rem 1rem;
|
| 38 |
+
box-shadow: 0 18px 45px rgba(50, 70, 105, 0.08);
|
| 39 |
+
}
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
APP_THEME = gr.themes.Base(
|
| 43 |
+
primary_hue="amber",
|
| 44 |
+
secondary_hue="blue",
|
| 45 |
+
neutral_hue="slate",
|
| 46 |
+
font=["Georgia", "Palatino Linotype", "serif"],
|
| 47 |
+
font_mono=["Consolas", "Courier New", "monospace"],
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def hero_markdown() -> str:
|
| 52 |
+
return "\n".join(
|
| 53 |
+
[
|
| 54 |
+
f"# {PROJECT_NAME}",
|
| 55 |
+
f"## {PROJECT_TAGLINE}",
|
| 56 |
+
"",
|
| 57 |
+
"A self-contained, locally-run tool for biometric privacy preservation.",
|
| 58 |
+
"",
|
| 59 |
+
f"**{PHASE_LABEL} status:** {PHASE_STATUS}",
|
| 60 |
+
"",
|
| 61 |
+
PHASE_SUMMARY,
|
| 62 |
+
]
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def roadmap_markdown() -> str:
|
| 67 |
+
return "\n".join(
|
| 68 |
+
[
|
| 69 |
+
"### Current Workflow",
|
| 70 |
+
"1. Detect and align the primary face with MTCNN",
|
| 71 |
+
"2. Extract a 512-dimensional embedding with InceptionResnetV1",
|
| 72 |
+
"3. Score cosine similarity for verification",
|
| 73 |
+
"4. Run L-infinity PGD directly on the aligned face tensor",
|
| 74 |
+
"5. Return the cloaked aligned face and a perturbation preview",
|
| 75 |
+
]
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _format_detection_probability(probability: float | None) -> str:
|
| 80 |
+
return "unknown" if probability is None else f"{probability:.4f}"
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def compare_faces(image_a, image_b):
|
| 84 |
+
try:
|
| 85 |
+
if image_a is None or image_b is None:
|
| 86 |
+
raise FaceCloakError("Please provide both images before running a similarity check.")
|
| 87 |
+
|
| 88 |
+
detected_a = detect_primary_face(image_a)
|
| 89 |
+
detected_b = detect_primary_face(image_b)
|
| 90 |
+
embedding_a = extract_embedding_numpy(detected_a.tensor)
|
| 91 |
+
embedding_b = extract_embedding_numpy(detected_b.tensor)
|
| 92 |
+
similarity = cosine_similarity(embedding_a, embedding_b)
|
| 93 |
+
summary = "\n".join(
|
| 94 |
+
[
|
| 95 |
+
"### Similarity Result",
|
| 96 |
+
f"- Cosine Similarity: `{similarity:.4f}`",
|
| 97 |
+
f"- Image A Detection Confidence: `{_format_detection_probability(detected_a.probability)}`",
|
| 98 |
+
f"- Image B Detection Confidence: `{_format_detection_probability(detected_b.probability)}`",
|
| 99 |
+
"- Interpretation: values near `1.0` indicate the same person; values near `0` or below indicate unrelated identities.",
|
| 100 |
+
]
|
| 101 |
+
)
|
| 102 |
+
return detected_a.image, detected_b.image, similarity, summary
|
| 103 |
+
except FaceCloakError as exc:
|
| 104 |
+
raise gr.Error(str(exc)) from exc
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def cloak_face(image, epsilon, num_steps, l2_lambda):
|
| 108 |
+
try:
|
| 109 |
+
if image is None:
|
| 110 |
+
raise FaceCloakError("Please upload an image before running the cloaking attack.")
|
| 111 |
+
|
| 112 |
+
detected = detect_primary_face(image)
|
| 113 |
+
result = cloak_face_tensor(
|
| 114 |
+
detected.tensor,
|
| 115 |
+
parameters=CloakHyperparameters(
|
| 116 |
+
epsilon=float(epsilon),
|
| 117 |
+
num_steps=int(num_steps),
|
| 118 |
+
l2_lambda=float(l2_lambda),
|
| 119 |
+
),
|
| 120 |
+
)
|
| 121 |
+
summary = "\n".join(
|
| 122 |
+
[
|
| 123 |
+
"### Cloaking Result",
|
| 124 |
+
f"- Detection Confidence: `{_format_detection_probability(detected.probability)}`",
|
| 125 |
+
f"- Original Similarity: `{result.original_similarity:.4f}`",
|
| 126 |
+
f"- Final Similarity: `{result.final_similarity:.4f}`",
|
| 127 |
+
f"- Similarity Drop: `{result.similarity_drop:.4f}`",
|
| 128 |
+
f"- Epsilon (L-inf): `{result.parameters.epsilon:.4f}`",
|
| 129 |
+
f"- Alpha: `{result.parameters.alpha:.4f}`",
|
| 130 |
+
f"- Steps: `{result.parameters.num_steps}`",
|
| 131 |
+
f"- Delta L-inf: `{result.delta_l_inf:.4f}`",
|
| 132 |
+
f"- Delta RMS: `{result.delta_rms:.4f}`",
|
| 133 |
+
]
|
| 134 |
+
)
|
| 135 |
+
return (
|
| 136 |
+
result.original_face_image,
|
| 137 |
+
result.cloaked_face_image,
|
| 138 |
+
result.perturbation_preview,
|
| 139 |
+
result.final_similarity,
|
| 140 |
+
summary,
|
| 141 |
+
)
|
| 142 |
+
except FaceCloakError as exc:
|
| 143 |
+
raise gr.Error(str(exc)) from exc
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def build_demo() -> gr.Blocks:
|
| 147 |
+
with gr.Blocks(title=PROJECT_NAME) as demo:
|
| 148 |
+
gr.Markdown(hero_markdown(), elem_id="hero")
|
| 149 |
+
|
| 150 |
+
with gr.Tab("Cloak Face"):
|
| 151 |
+
with gr.Row():
|
| 152 |
+
input_image = gr.Image(label="Input Portrait", type="pil")
|
| 153 |
+
aligned_face = gr.Image(label="Aligned Face", type="pil")
|
| 154 |
+
cloaked_face = gr.Image(label="Cloaked Face", type="pil")
|
| 155 |
+
|
| 156 |
+
with gr.Row():
|
| 157 |
+
perturbation = gr.Image(label="Perturbation Preview", type="pil")
|
| 158 |
+
with gr.Column():
|
| 159 |
+
epsilon = gr.Slider(
|
| 160 |
+
minimum=0.01,
|
| 161 |
+
maximum=0.05,
|
| 162 |
+
value=0.03,
|
| 163 |
+
step=0.005,
|
| 164 |
+
label="Epsilon (L-inf budget)",
|
| 165 |
+
)
|
| 166 |
+
num_steps = gr.Slider(
|
| 167 |
+
minimum=10,
|
| 168 |
+
maximum=60,
|
| 169 |
+
value=30,
|
| 170 |
+
step=5,
|
| 171 |
+
label="PGD Steps",
|
| 172 |
+
)
|
| 173 |
+
l2_lambda = gr.Slider(
|
| 174 |
+
minimum=0.0,
|
| 175 |
+
maximum=0.05,
|
| 176 |
+
value=0.01,
|
| 177 |
+
step=0.005,
|
| 178 |
+
label="L2 Regularization Weight",
|
| 179 |
+
)
|
| 180 |
+
cloak_button = gr.Button("Run Cloaking Attack", variant="primary")
|
| 181 |
+
final_similarity = gr.Number(
|
| 182 |
+
label="Final Original-vs-Cloaked Similarity",
|
| 183 |
+
precision=4,
|
| 184 |
+
)
|
| 185 |
+
cloak_summary = gr.Markdown(elem_classes=["panel"])
|
| 186 |
+
|
| 187 |
+
cloak_button.click(
|
| 188 |
+
fn=cloak_face,
|
| 189 |
+
inputs=[input_image, epsilon, num_steps, l2_lambda],
|
| 190 |
+
outputs=[
|
| 191 |
+
aligned_face,
|
| 192 |
+
cloaked_face,
|
| 193 |
+
perturbation,
|
| 194 |
+
final_similarity,
|
| 195 |
+
cloak_summary,
|
| 196 |
+
],
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
with gr.Tab("Compare Faces"):
|
| 200 |
+
with gr.Row():
|
| 201 |
+
compare_image_a = gr.Image(label="Image A", type="pil")
|
| 202 |
+
compare_image_b = gr.Image(label="Image B", type="pil")
|
| 203 |
+
with gr.Row():
|
| 204 |
+
aligned_a = gr.Image(label="Aligned Face A", type="pil")
|
| 205 |
+
aligned_b = gr.Image(label="Aligned Face B", type="pil")
|
| 206 |
+
compare_button = gr.Button("Compare Embeddings", variant="secondary")
|
| 207 |
+
pair_similarity = gr.Number(label="Cosine Similarity", precision=4)
|
| 208 |
+
pair_summary = gr.Markdown(elem_classes=["panel"])
|
| 209 |
+
compare_button.click(
|
| 210 |
+
fn=compare_faces,
|
| 211 |
+
inputs=[compare_image_a, compare_image_b],
|
| 212 |
+
outputs=[aligned_a, aligned_b, pair_similarity, pair_summary],
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
with gr.Tab("Diagnostics"):
|
| 216 |
+
diagnostics = gr.Markdown(
|
| 217 |
+
value=render_runtime_markdown(),
|
| 218 |
+
elem_classes=["panel"],
|
| 219 |
+
)
|
| 220 |
+
refresh_button = gr.Button("Re-run environment check", variant="secondary")
|
| 221 |
+
refresh_button.click(fn=render_runtime_markdown, outputs=diagnostics)
|
| 222 |
+
|
| 223 |
+
with gr.Accordion("Pipeline Notes", open=False):
|
| 224 |
+
gr.Markdown(roadmap_markdown(), elem_classes=["panel"])
|
| 225 |
+
|
| 226 |
+
return demo
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
demo = build_demo()
|
facecloak/models.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Cached model factories for face detection and embedding extraction."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from functools import lru_cache
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
from facenet_pytorch import InceptionResnetV1, MTCNN
|
| 9 |
+
|
| 10 |
+
from facecloak.project import TORCH_CACHE_DIR
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def configure_torch_cache() -> None:
|
| 14 |
+
"""Point Torch downloads at a repository-local cache."""
|
| 15 |
+
|
| 16 |
+
TORCH_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
| 17 |
+
os.environ.setdefault("TORCH_HOME", str(TORCH_CACHE_DIR))
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@lru_cache(maxsize=1)
|
| 21 |
+
def get_face_detector() -> MTCNN:
|
| 22 |
+
configure_torch_cache()
|
| 23 |
+
return MTCNN(
|
| 24 |
+
image_size=160,
|
| 25 |
+
margin=20,
|
| 26 |
+
keep_all=False,
|
| 27 |
+
post_process=True,
|
| 28 |
+
device="cpu",
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@lru_cache(maxsize=1)
|
| 33 |
+
def get_embedding_model() -> InceptionResnetV1:
|
| 34 |
+
configure_torch_cache()
|
| 35 |
+
model = InceptionResnetV1(pretrained="vggface2", device="cpu").eval()
|
| 36 |
+
for parameter in model.parameters():
|
| 37 |
+
parameter.requires_grad_(False)
|
| 38 |
+
return model
|
facecloak/pipeline.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Face detection, embedding extraction, and similarity helpers."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
from PIL import Image
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from facecloak.errors import FaceCloakError
|
| 13 |
+
from facecloak.models import get_embedding_model, get_face_detector
|
| 14 |
+
|
| 15 |
+
DISPLAY_MIN = -1.0
|
| 16 |
+
DISPLAY_MAX = 1.0
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@dataclass(slots=True)
|
| 20 |
+
class DetectedFace:
|
| 21 |
+
tensor: torch.Tensor
|
| 22 |
+
image: Image.Image
|
| 23 |
+
probability: float | None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def ensure_rgb(image: Image.Image) -> Image.Image:
|
| 27 |
+
return image if image.mode == "RGB" else image.convert("RGB")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _model_device(model: Any) -> torch.device:
|
| 31 |
+
device = getattr(model, "device", None)
|
| 32 |
+
if isinstance(device, torch.device):
|
| 33 |
+
return device
|
| 34 |
+
return torch.device("cpu")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _prepare_face_batch(face_tensor: torch.Tensor) -> torch.Tensor:
|
| 38 |
+
if face_tensor.ndim == 3:
|
| 39 |
+
batch = face_tensor.unsqueeze(0)
|
| 40 |
+
elif face_tensor.ndim == 4:
|
| 41 |
+
batch = face_tensor
|
| 42 |
+
else:
|
| 43 |
+
raise ValueError("Face tensors must have shape (3, H, W) or (N, 3, H, W).")
|
| 44 |
+
|
| 45 |
+
return batch.to(dtype=torch.float32)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def standardized_tensor_to_pil(face_tensor: torch.Tensor) -> Image.Image:
|
| 49 |
+
batch = _prepare_face_batch(face_tensor)
|
| 50 |
+
chw_tensor = batch[0].detach().cpu()
|
| 51 |
+
pixel_tensor = torch.clamp(chw_tensor * 128.0 + 127.5, 0.0, 255.0)
|
| 52 |
+
image_array = pixel_tensor.byte().permute(1, 2, 0).numpy()
|
| 53 |
+
return Image.fromarray(image_array, mode="RGB")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def perturbation_preview_image(delta_tensor: torch.Tensor) -> Image.Image:
|
| 57 |
+
batch = _prepare_face_batch(delta_tensor)
|
| 58 |
+
chw_tensor = batch[0].detach().cpu()
|
| 59 |
+
max_abs = float(chw_tensor.abs().max().item())
|
| 60 |
+
if max_abs == 0.0:
|
| 61 |
+
preview = torch.full_like(chw_tensor, 127.5)
|
| 62 |
+
else:
|
| 63 |
+
preview = (chw_tensor / max_abs) * 127.5 + 127.5
|
| 64 |
+
preview_array = torch.clamp(preview, 0.0, 255.0).byte().permute(1, 2, 0).numpy()
|
| 65 |
+
return Image.fromarray(preview_array, mode="RGB")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def detect_primary_face(image: Image.Image, detector: Any | None = None) -> DetectedFace:
|
| 69 |
+
detector = detector or get_face_detector()
|
| 70 |
+
rgb_image = ensure_rgb(image)
|
| 71 |
+
face_tensor, probability = detector(rgb_image, return_prob=True)
|
| 72 |
+
|
| 73 |
+
if face_tensor is None:
|
| 74 |
+
raise FaceCloakError(
|
| 75 |
+
"No face was detected. Please upload a clear image with one dominant, visible face."
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
return DetectedFace(
|
| 79 |
+
tensor=face_tensor.detach().cpu(),
|
| 80 |
+
image=standardized_tensor_to_pil(face_tensor),
|
| 81 |
+
probability=None if probability is None else float(probability),
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def extract_embedding_tensor(
|
| 86 |
+
face_tensor: torch.Tensor,
|
| 87 |
+
model: Any | None = None,
|
| 88 |
+
) -> torch.Tensor:
|
| 89 |
+
model = model or get_embedding_model()
|
| 90 |
+
batch = _prepare_face_batch(face_tensor).to(_model_device(model))
|
| 91 |
+
return model(batch)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def extract_embedding_numpy(
|
| 95 |
+
face_tensor: torch.Tensor,
|
| 96 |
+
model: Any | None = None,
|
| 97 |
+
) -> np.ndarray:
|
| 98 |
+
with torch.no_grad():
|
| 99 |
+
embedding = extract_embedding_tensor(face_tensor, model=model)
|
| 100 |
+
return embedding[0].detach().cpu().numpy().astype(np.float32)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def cosine_similarity(first: np.ndarray, second: np.ndarray) -> float:
|
| 104 |
+
first_vector = np.asarray(first, dtype=np.float32).reshape(-1)
|
| 105 |
+
second_vector = np.asarray(second, dtype=np.float32).reshape(-1)
|
| 106 |
+
|
| 107 |
+
if first_vector.shape != second_vector.shape:
|
| 108 |
+
raise ValueError("Embedding vectors must have the same shape.")
|
| 109 |
+
|
| 110 |
+
first_norm = np.linalg.norm(first_vector)
|
| 111 |
+
second_norm = np.linalg.norm(second_vector)
|
| 112 |
+
if first_norm == 0.0 or second_norm == 0.0:
|
| 113 |
+
raise ValueError("Cosine similarity is undefined for zero-length vectors.")
|
| 114 |
+
|
| 115 |
+
return float(np.dot(first_vector / first_norm, second_vector / second_norm))
|
facecloak/project.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Project metadata and repository-level constants."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
PROJECT_NAME = "FaceCloak"
|
| 8 |
+
PROJECT_SLUG = "facecloak"
|
| 9 |
+
PROJECT_TAGLINE = "Adversarial Pixel Poisoning for Biometric Privacy Preservation"
|
| 10 |
+
PHASE_LABEL = "Phases 2-3"
|
| 11 |
+
PHASE_STATUS = "Complete"
|
| 12 |
+
PHASE_SUMMARY = (
|
| 13 |
+
"Face detection, embedding extraction, cosine similarity scoring, "
|
| 14 |
+
"and PGD-based face cloaking are now implemented."
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 18 |
+
TORCH_CACHE_DIR = PROJECT_ROOT / ".torch-cache"
|
| 19 |
+
SPACE_URL_TEMPLATE = "https://huggingface.co/spaces/{repo_id}"
|
| 20 |
+
|
| 21 |
+
PINNED_RUNTIME_DEPENDENCIES: tuple[tuple[str, str], ...] = (
|
| 22 |
+
("torch", "2.2.2"),
|
| 23 |
+
("torchvision", "0.17.2"),
|
| 24 |
+
("facenet-pytorch", "2.6.0"),
|
| 25 |
+
("pillow", "10.2.0"),
|
| 26 |
+
("numpy", "1.26.4"),
|
| 27 |
+
("gradio", "6.12.0"),
|
| 28 |
+
("huggingface-hub", "1.10.2"),
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
SPACE_UPLOAD_ALLOW_PATTERNS: tuple[str, ...] = (
|
| 32 |
+
"app.py",
|
| 33 |
+
"main.py",
|
| 34 |
+
"README.md",
|
| 35 |
+
"requirements.txt",
|
| 36 |
+
"pyproject.toml",
|
| 37 |
+
"uv.lock",
|
| 38 |
+
"facecloak/**",
|
| 39 |
+
"scripts/**",
|
| 40 |
+
"tests/**",
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def requirements_lines() -> list[str]:
|
| 45 |
+
"""Return the direct runtime dependencies for Hugging Face Spaces."""
|
| 46 |
+
|
| 47 |
+
return [f"{name}=={version}" for name, version in PINNED_RUNTIME_DEPENDENCIES]
|
main.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from facecloak.interface import APP_CSS, APP_THEME, demo
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
if __name__ == "__main__":
|
| 5 |
+
demo.launch(theme=APP_THEME, css=APP_CSS)
|
pyproject.toml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "facecloak"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Local adversarial pixel poisoning toolkit for biometric privacy preservation."
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.12,<3.13"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"facenet-pytorch==2.6.0",
|
| 9 |
+
"gradio==6.12.0",
|
| 10 |
+
"huggingface-hub==1.10.2",
|
| 11 |
+
"numpy==1.26.4",
|
| 12 |
+
"pillow==10.2.0",
|
| 13 |
+
"torch==2.2.2",
|
| 14 |
+
"torchvision==0.17.2",
|
| 15 |
+
]
|
| 16 |
+
|
| 17 |
+
[dependency-groups]
|
| 18 |
+
dev = [
|
| 19 |
+
"pytest==9.0.3",
|
| 20 |
+
"pytest-cov==7.1.0",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
[tool.pytest.ini_options]
|
| 24 |
+
addopts = "-q -p no:cacheprovider"
|
| 25 |
+
testpaths = ["tests"]
|
| 26 |
+
markers = [
|
| 27 |
+
"integration: tests that exercise the real facenet-pytorch models and sample portraits",
|
| 28 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.2.2
|
| 2 |
+
torchvision==0.17.2
|
| 3 |
+
facenet-pytorch==2.6.0
|
| 4 |
+
pillow==10.2.0
|
| 5 |
+
numpy==1.26.4
|
| 6 |
+
gradio==6.12.0
|
| 7 |
+
huggingface-hub==1.10.2
|
scripts/create_or_update_space.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 7 |
+
if str(PROJECT_ROOT) not in sys.path:
|
| 8 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 9 |
+
|
| 10 |
+
from facecloak.deploy import create_or_update_space, deployment_markdown
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def main() -> None:
|
| 14 |
+
repo_id = sys.argv[1] if len(sys.argv) > 1 else None
|
| 15 |
+
result = create_or_update_space(repo_id=repo_id)
|
| 16 |
+
print(deployment_markdown(result))
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
if __name__ == "__main__":
|
| 20 |
+
main()
|
tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (647 Bytes). View file
|
|
|
tests/__pycache__/test_cloaking.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (7.19 kB). View file
|
|
|
tests/__pycache__/test_deploy.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (7.66 kB). View file
|
|
|
tests/__pycache__/test_environment.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (8.17 kB). View file
|
|
|
tests/__pycache__/test_integration.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (5.07 kB). View file
|
|
|
tests/__pycache__/test_interface.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (7.65 kB). View file
|
|
|
tests/__pycache__/test_pipeline.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (12.2 kB). View file
|
|
|
tests/__pycache__/test_project.cpython-312-pytest-9.0.3.pyc
ADDED
|
Binary file (2.56 kB). View file
|
|
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 5 |
+
|
| 6 |
+
if str(PROJECT_ROOT) not in sys.path:
|
| 7 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
tests/fixtures/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Test Fixtures
|
| 2 |
+
|
| 3 |
+
The portrait images in `tests/fixtures/faces/` are public-domain source images downloaded from Wikimedia Commons so the embedding pipeline can be tested end to end.
|
| 4 |
+
|
| 5 |
+
- `obama_a.jpg`: Official portrait of Barack Obama (cropped)
|
| 6 |
+
- `obama_b.jpg`: Alternate official portrait of Barack Obama
|
| 7 |
+
- `george_a.jpg`: George W. Bush cropped portrait
|
| 8 |
+
|
| 9 |
+
These fixtures are used only for local verification of face detection, embedding extraction, and cloaking behavior.
|
tests/fixtures/faces/george_a.jpg
ADDED
|
Git LFS Details
|
tests/fixtures/faces/obama_a.jpg
ADDED
|
Git LFS Details
|
tests/fixtures/faces/obama_b.jpg
ADDED
|
Git LFS Details
|
tests/test_cloaking.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pytest
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
|
| 5 |
+
from facecloak.cloaking import CloakHyperparameters, cloak_face_tensor
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TinyEmbeddingModel(torch.nn.Module):
|
| 9 |
+
def __init__(self) -> None:
|
| 10 |
+
super().__init__()
|
| 11 |
+
self.device = torch.device("cpu")
|
| 12 |
+
self.scale = torch.nn.Parameter(torch.tensor(1.0), requires_grad=False)
|
| 13 |
+
|
| 14 |
+
def forward(self, batch: torch.Tensor) -> torch.Tensor:
|
| 15 |
+
flattened = batch.reshape(batch.shape[0], -1)
|
| 16 |
+
features = flattened[:, :4] * self.scale
|
| 17 |
+
return F.normalize(features, p=2, dim=1)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_cloak_face_tensor_reduces_similarity_and_respects_budget() -> None:
|
| 21 |
+
model = TinyEmbeddingModel()
|
| 22 |
+
face_tensor = torch.tensor(
|
| 23 |
+
[
|
| 24 |
+
[[0.9, 0.8], [0.7, 0.6]],
|
| 25 |
+
[[0.1, 0.2], [0.3, 0.4]],
|
| 26 |
+
[[0.5, 0.4], [0.3, 0.2]],
|
| 27 |
+
],
|
| 28 |
+
dtype=torch.float32,
|
| 29 |
+
)
|
| 30 |
+
result = cloak_face_tensor(
|
| 31 |
+
face_tensor,
|
| 32 |
+
model=model,
|
| 33 |
+
parameters=CloakHyperparameters(epsilon=0.2, num_steps=10, l2_lambda=0.0),
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
assert result.final_similarity < result.original_similarity
|
| 37 |
+
assert result.delta_l_inf == pytest.approx(0.2, abs=1e-6)
|
| 38 |
+
assert len(result.loss_history) == 10
|
| 39 |
+
assert len(result.similarity_history) == 10
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def test_cloak_face_tensor_validates_hyperparameters() -> None:
|
| 43 |
+
with pytest.raises(ValueError):
|
| 44 |
+
cloak_face_tensor(
|
| 45 |
+
torch.zeros(3, 2, 2),
|
| 46 |
+
model=TinyEmbeddingModel(),
|
| 47 |
+
parameters=CloakHyperparameters(epsilon=0.0),
|
| 48 |
+
)
|
tests/test_deploy.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import shutil
|
| 3 |
+
import uuid
|
| 4 |
+
|
| 5 |
+
from facecloak.deploy import (
|
| 6 |
+
HF_TOKEN_ENV_VAR,
|
| 7 |
+
create_or_update_space,
|
| 8 |
+
default_space_repo_id,
|
| 9 |
+
read_env_file,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class DummyRuntime:
|
| 14 |
+
stage = "BUILDING"
|
| 15 |
+
hardware = "cpu-basic"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class DummyCommit:
|
| 19 |
+
oid = "abc123"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class DummyApi:
|
| 23 |
+
def __init__(self) -> None:
|
| 24 |
+
self.created = None
|
| 25 |
+
self.uploaded = None
|
| 26 |
+
|
| 27 |
+
def whoami(self, token, cache=False):
|
| 28 |
+
return {"name": "example-user"}
|
| 29 |
+
|
| 30 |
+
def create_repo(self, **kwargs):
|
| 31 |
+
self.created = kwargs
|
| 32 |
+
|
| 33 |
+
def upload_folder(self, **kwargs):
|
| 34 |
+
self.uploaded = kwargs
|
| 35 |
+
return DummyCommit()
|
| 36 |
+
|
| 37 |
+
def get_space_runtime(self, repo_id, token=None):
|
| 38 |
+
return DummyRuntime()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _local_temp_dir(name: str) -> Path:
|
| 42 |
+
path = Path("tests") / "_tmp" / f"{name}_{uuid.uuid4().hex}"
|
| 43 |
+
path.mkdir(parents=True, exist_ok=True)
|
| 44 |
+
return path
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_read_env_file_parses_simple_key_values() -> None:
|
| 48 |
+
temp_dir = _local_temp_dir("env")
|
| 49 |
+
env_path = temp_dir / ".env"
|
| 50 |
+
env_path.write_text(f"{HF_TOKEN_ENV_VAR}=hf_test_token\nOTHER=value\n", encoding="utf-8")
|
| 51 |
+
|
| 52 |
+
values = read_env_file(env_path)
|
| 53 |
+
|
| 54 |
+
assert values[HF_TOKEN_ENV_VAR] == "hf_test_token"
|
| 55 |
+
assert values["OTHER"] == "value"
|
| 56 |
+
shutil.rmtree(temp_dir)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def test_default_space_repo_id_uses_hf_username() -> None:
|
| 60 |
+
repo_id = default_space_repo_id(DummyApi(), "hf_test_token")
|
| 61 |
+
|
| 62 |
+
assert repo_id == "example-user/facecloak"
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def test_create_or_update_space_uses_space_repo_settings() -> None:
|
| 66 |
+
api = DummyApi()
|
| 67 |
+
temp_dir = _local_temp_dir("deploy")
|
| 68 |
+
folder_path = temp_dir / "repo"
|
| 69 |
+
folder_path.mkdir()
|
| 70 |
+
(folder_path / "app.py").write_text("print('ok')\n", encoding="utf-8")
|
| 71 |
+
|
| 72 |
+
result = create_or_update_space(
|
| 73 |
+
api=api,
|
| 74 |
+
token="hf_test_token",
|
| 75 |
+
repo_id="example-user/facecloak",
|
| 76 |
+
folder_path=folder_path,
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
assert api.created["repo_type"] == "space"
|
| 80 |
+
assert api.created["space_sdk"] == "gradio"
|
| 81 |
+
assert api.uploaded["repo_type"] == "space"
|
| 82 |
+
assert result.repo_id == "example-user/facecloak"
|
| 83 |
+
assert result.runtime_stage == "BUILDING"
|
| 84 |
+
shutil.rmtree(temp_dir)
|
tests/test_environment.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from facecloak.environment import collect_runtime_report, render_runtime_markdown
|
| 2 |
+
from facecloak.project import PHASE_LABEL
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def test_collect_runtime_report_returns_expected_values() -> None:
|
| 6 |
+
report = collect_runtime_report()
|
| 7 |
+
|
| 8 |
+
assert report.phase == PHASE_LABEL
|
| 9 |
+
assert report.status == "ready"
|
| 10 |
+
assert report.tensor_sanity == (1, 2, 3)
|
| 11 |
+
assert report.device == ("cuda" if report.cuda_available else "cpu")
|
| 12 |
+
assert report.torch_version.startswith("2.2.2")
|
| 13 |
+
assert report.facenet_pytorch_version == "2.6.0"
|
| 14 |
+
assert report.gradio_version == "6.12.0"
|
| 15 |
+
assert report.huggingface_hub_version == "1.10.2"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_render_runtime_markdown_contains_environment_summary() -> None:
|
| 19 |
+
markdown = render_runtime_markdown()
|
| 20 |
+
|
| 21 |
+
assert "Runtime Diagnostics" in markdown
|
| 22 |
+
assert "CUDA Available" in markdown
|
| 23 |
+
assert "Torch Cache" in markdown
|
| 24 |
+
assert "PGD-based face cloaking" in markdown
|
tests/test_integration.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from facecloak.cloaking import CloakHyperparameters, cloak_face_tensor
|
| 7 |
+
from facecloak.pipeline import cosine_similarity, detect_primary_face, extract_embedding_numpy
|
| 8 |
+
|
| 9 |
+
FIXTURE_DIR = Path("tests/fixtures/faces")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@pytest.mark.integration
|
| 13 |
+
def test_real_face_similarity_pipeline_matches_expected_ranges() -> None:
|
| 14 |
+
obama_a = Image.open(FIXTURE_DIR / "obama_a.jpg").convert("RGB")
|
| 15 |
+
obama_b = Image.open(FIXTURE_DIR / "obama_b.jpg").convert("RGB")
|
| 16 |
+
george_a = Image.open(FIXTURE_DIR / "george_a.jpg").convert("RGB")
|
| 17 |
+
|
| 18 |
+
obama_a_embedding = extract_embedding_numpy(detect_primary_face(obama_a).tensor)
|
| 19 |
+
obama_b_embedding = extract_embedding_numpy(detect_primary_face(obama_b).tensor)
|
| 20 |
+
george_embedding = extract_embedding_numpy(detect_primary_face(george_a).tensor)
|
| 21 |
+
|
| 22 |
+
same_score = cosine_similarity(obama_a_embedding, obama_b_embedding)
|
| 23 |
+
different_score = cosine_similarity(obama_a_embedding, george_embedding)
|
| 24 |
+
|
| 25 |
+
assert same_score > 0.8
|
| 26 |
+
assert different_score < 0.3
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@pytest.mark.integration
|
| 30 |
+
def test_real_face_cloaking_substantially_lowers_similarity() -> None:
|
| 31 |
+
obama_a = Image.open(FIXTURE_DIR / "obama_a.jpg").convert("RGB")
|
| 32 |
+
detected = detect_primary_face(obama_a)
|
| 33 |
+
|
| 34 |
+
result = cloak_face_tensor(
|
| 35 |
+
detected.tensor,
|
| 36 |
+
parameters=CloakHyperparameters(epsilon=0.03, num_steps=15, l2_lambda=0.01),
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
assert result.final_similarity < 0.35
|
| 40 |
+
assert result.delta_l_inf <= 0.03 + 1e-6
|
tests/test_interface.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from PIL import Image
|
| 3 |
+
|
| 4 |
+
from facecloak.interface import build_demo, hero_markdown, roadmap_markdown
|
| 5 |
+
from facecloak.project import PROJECT_NAME, PROJECT_TAGLINE
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_hero_markdown_contains_project_identity() -> None:
|
| 9 |
+
text = hero_markdown()
|
| 10 |
+
|
| 11 |
+
assert PROJECT_NAME in text
|
| 12 |
+
assert PROJECT_TAGLINE in text
|
| 13 |
+
assert "Phases 2-3 status" in text
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_roadmap_markdown_mentions_current_pipeline() -> None:
|
| 17 |
+
text = roadmap_markdown()
|
| 18 |
+
|
| 19 |
+
assert "Detect and align the primary face with MTCNN" in text
|
| 20 |
+
assert "Extract a 512-dimensional embedding" in text
|
| 21 |
+
assert "Run L-infinity PGD" in text
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_build_demo_returns_gradio_blocks_with_project_title() -> None:
|
| 25 |
+
demo = build_demo()
|
| 26 |
+
|
| 27 |
+
assert isinstance(demo, gr.Blocks)
|
| 28 |
+
assert demo.title == PROJECT_NAME
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_compare_faces_requires_both_images() -> None:
|
| 32 |
+
from facecloak.interface import compare_faces
|
| 33 |
+
|
| 34 |
+
blank = Image.new("RGB", (16, 16), "white")
|
| 35 |
+
try:
|
| 36 |
+
compare_faces(blank, None)
|
| 37 |
+
except gr.Error as exc:
|
| 38 |
+
assert "Please provide both images" in str(exc)
|
| 39 |
+
else:
|
| 40 |
+
raise AssertionError("compare_faces should reject missing inputs.")
|
tests/test_pipeline.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from PIL import Image
|
| 3 |
+
import pytest
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from facecloak.errors import FaceCloakError
|
| 7 |
+
from facecloak.pipeline import (
|
| 8 |
+
cosine_similarity,
|
| 9 |
+
detect_primary_face,
|
| 10 |
+
extract_embedding_tensor,
|
| 11 |
+
perturbation_preview_image,
|
| 12 |
+
standardized_tensor_to_pil,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class DummyDetector:
|
| 17 |
+
def __call__(self, _image, return_prob=False):
|
| 18 |
+
face_tensor = torch.zeros(3, 160, 160)
|
| 19 |
+
probability = 0.97
|
| 20 |
+
if return_prob:
|
| 21 |
+
return face_tensor, probability
|
| 22 |
+
return face_tensor
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class MissingFaceDetector:
|
| 26 |
+
def __call__(self, _image, return_prob=False):
|
| 27 |
+
if return_prob:
|
| 28 |
+
return None, None
|
| 29 |
+
return None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class DummyEmbeddingModel(torch.nn.Module):
|
| 33 |
+
def __init__(self) -> None:
|
| 34 |
+
super().__init__()
|
| 35 |
+
self.device = torch.device("cpu")
|
| 36 |
+
self.projection = torch.nn.Linear(3 * 4 * 4, 4, bias=False)
|
| 37 |
+
with torch.no_grad():
|
| 38 |
+
self.projection.weight.copy_(torch.eye(4, 3 * 4 * 4))
|
| 39 |
+
|
| 40 |
+
def forward(self, batch: torch.Tensor) -> torch.Tensor:
|
| 41 |
+
flat = batch.reshape(batch.shape[0], -1)
|
| 42 |
+
return self.projection(flat)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def test_detect_primary_face_returns_probability_and_image() -> None:
|
| 46 |
+
detected = detect_primary_face(Image.new("RGB", (64, 64), "white"), detector=DummyDetector())
|
| 47 |
+
|
| 48 |
+
assert detected.tensor.shape == (3, 160, 160)
|
| 49 |
+
assert detected.probability == pytest.approx(0.97)
|
| 50 |
+
assert detected.image.size == (160, 160)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def test_detect_primary_face_raises_readable_error_when_no_face_found() -> None:
|
| 54 |
+
with pytest.raises(FaceCloakError):
|
| 55 |
+
detect_primary_face(Image.new("RGB", (64, 64), "white"), detector=MissingFaceDetector())
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def test_extract_embedding_tensor_preserves_gradients() -> None:
|
| 59 |
+
face_tensor = torch.ones(3, 4, 4, requires_grad=True)
|
| 60 |
+
model = DummyEmbeddingModel()
|
| 61 |
+
|
| 62 |
+
embedding = extract_embedding_tensor(face_tensor, model=model)
|
| 63 |
+
embedding.sum().backward()
|
| 64 |
+
|
| 65 |
+
assert embedding.shape == (1, 4)
|
| 66 |
+
assert face_tensor.grad is not None
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def test_cosine_similarity_behaves_for_identical_and_opposite_vectors() -> None:
|
| 70 |
+
same = cosine_similarity(np.array([1.0, 2.0]), np.array([1.0, 2.0]))
|
| 71 |
+
opposite = cosine_similarity(np.array([1.0, 0.0]), np.array([-1.0, 0.0]))
|
| 72 |
+
|
| 73 |
+
assert same == pytest.approx(1.0)
|
| 74 |
+
assert opposite == pytest.approx(-1.0)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def test_tensor_visualization_helpers_return_pil_images() -> None:
|
| 78 |
+
face_tensor = torch.zeros(3, 160, 160)
|
| 79 |
+
face_image = standardized_tensor_to_pil(face_tensor)
|
| 80 |
+
delta_preview = perturbation_preview_image(torch.zeros(3, 160, 160))
|
| 81 |
+
|
| 82 |
+
assert face_image.size == (160, 160)
|
| 83 |
+
assert delta_preview.size == (160, 160)
|
tests/test_project.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
from facecloak.project import PHASE_LABEL, requirements_lines
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_requirements_file_matches_pinned_runtime_dependencies() -> None:
|
| 7 |
+
requirements = [
|
| 8 |
+
line.strip()
|
| 9 |
+
for line in Path("requirements.txt").read_text(encoding="utf-8").splitlines()
|
| 10 |
+
if line.strip()
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
assert requirements == requirements_lines()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_phase_label_tracks_current_delivery() -> None:
|
| 17 |
+
assert PHASE_LABEL == "Phases 2-3"
|
uv.lock
ADDED
|
@@ -0,0 +1,1032 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
revision = 3
|
| 3 |
+
requires-python = "==3.12.*"
|
| 4 |
+
resolution-markers = [
|
| 5 |
+
"sys_platform == 'win32'",
|
| 6 |
+
"sys_platform == 'emscripten'",
|
| 7 |
+
"sys_platform != 'emscripten' and sys_platform != 'win32'",
|
| 8 |
+
]
|
| 9 |
+
|
| 10 |
+
[[package]]
|
| 11 |
+
name = "annotated-doc"
|
| 12 |
+
version = "0.0.4"
|
| 13 |
+
source = { registry = "https://pypi.org/simple" }
|
| 14 |
+
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
| 15 |
+
wheels = [
|
| 16 |
+
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
[[package]]
|
| 20 |
+
name = "annotated-types"
|
| 21 |
+
version = "0.7.0"
|
| 22 |
+
source = { registry = "https://pypi.org/simple" }
|
| 23 |
+
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" }
|
| 24 |
+
wheels = [
|
| 25 |
+
{ 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" },
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
[[package]]
|
| 29 |
+
name = "anyio"
|
| 30 |
+
version = "4.13.0"
|
| 31 |
+
source = { registry = "https://pypi.org/simple" }
|
| 32 |
+
dependencies = [
|
| 33 |
+
{ name = "idna" },
|
| 34 |
+
{ name = "typing-extensions" },
|
| 35 |
+
]
|
| 36 |
+
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
| 37 |
+
wheels = [
|
| 38 |
+
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
[[package]]
|
| 42 |
+
name = "brotli"
|
| 43 |
+
version = "1.2.0"
|
| 44 |
+
source = { registry = "https://pypi.org/simple" }
|
| 45 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" }
|
| 46 |
+
wheels = [
|
| 47 |
+
{ url = "https://files.pythonhosted.org/packages/11/ee/b0a11ab2315c69bb9b45a2aaed022499c9c24a205c3a49c3513b541a7967/brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84", size = 861543, upload-time = "2025-11-05T18:38:24.183Z" },
|
| 48 |
+
{ url = "https://files.pythonhosted.org/packages/e1/2f/29c1459513cd35828e25531ebfcbf3e92a5e49f560b1777a9af7203eb46e/brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b", size = 444288, upload-time = "2025-11-05T18:38:25.139Z" },
|
| 49 |
+
{ url = "https://files.pythonhosted.org/packages/3d/6f/feba03130d5fceadfa3a1bb102cb14650798c848b1df2a808356f939bb16/brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d", size = 1528071, upload-time = "2025-11-05T18:38:26.081Z" },
|
| 50 |
+
{ url = "https://files.pythonhosted.org/packages/2b/38/f3abb554eee089bd15471057ba85f47e53a44a462cfce265d9bf7088eb09/brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca", size = 1626913, upload-time = "2025-11-05T18:38:27.284Z" },
|
| 51 |
+
{ url = "https://files.pythonhosted.org/packages/03/a7/03aa61fbc3c5cbf99b44d158665f9b0dd3d8059be16c460208d9e385c837/brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f", size = 1419762, upload-time = "2025-11-05T18:38:28.295Z" },
|
| 52 |
+
{ url = "https://files.pythonhosted.org/packages/21/1b/0374a89ee27d152a5069c356c96b93afd1b94eae83f1e004b57eb6ce2f10/brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28", size = 1484494, upload-time = "2025-11-05T18:38:29.29Z" },
|
| 53 |
+
{ url = "https://files.pythonhosted.org/packages/cf/57/69d4fe84a67aef4f524dcd075c6eee868d7850e85bf01d778a857d8dbe0a/brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7", size = 1593302, upload-time = "2025-11-05T18:38:30.639Z" },
|
| 54 |
+
{ url = "https://files.pythonhosted.org/packages/d5/3b/39e13ce78a8e9a621c5df3aeb5fd181fcc8caba8c48a194cd629771f6828/brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036", size = 1487913, upload-time = "2025-11-05T18:38:31.618Z" },
|
| 55 |
+
{ url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362, upload-time = "2025-11-05T18:38:32.939Z" },
|
| 56 |
+
{ url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115, upload-time = "2025-11-05T18:38:33.765Z" },
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
[[package]]
|
| 60 |
+
name = "certifi"
|
| 61 |
+
version = "2026.2.25"
|
| 62 |
+
source = { registry = "https://pypi.org/simple" }
|
| 63 |
+
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
|
| 64 |
+
wheels = [
|
| 65 |
+
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
[[package]]
|
| 69 |
+
name = "charset-normalizer"
|
| 70 |
+
version = "3.4.7"
|
| 71 |
+
source = { registry = "https://pypi.org/simple" }
|
| 72 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
|
| 73 |
+
wheels = [
|
| 74 |
+
{ url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
|
| 75 |
+
{ url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
|
| 76 |
+
{ url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
|
| 77 |
+
{ url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
|
| 78 |
+
{ url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
|
| 79 |
+
{ url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
|
| 80 |
+
{ url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
|
| 81 |
+
{ url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
|
| 82 |
+
{ url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
|
| 83 |
+
{ url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
|
| 84 |
+
{ url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
|
| 85 |
+
{ url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
|
| 86 |
+
{ url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
|
| 87 |
+
{ url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
|
| 88 |
+
{ url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
|
| 89 |
+
{ url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
|
| 90 |
+
{ url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
|
| 91 |
+
]
|
| 92 |
+
|
| 93 |
+
[[package]]
|
| 94 |
+
name = "click"
|
| 95 |
+
version = "8.3.2"
|
| 96 |
+
source = { registry = "https://pypi.org/simple" }
|
| 97 |
+
dependencies = [
|
| 98 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 99 |
+
]
|
| 100 |
+
sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" }
|
| 101 |
+
wheels = [
|
| 102 |
+
{ url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" },
|
| 103 |
+
]
|
| 104 |
+
|
| 105 |
+
[[package]]
|
| 106 |
+
name = "colorama"
|
| 107 |
+
version = "0.4.6"
|
| 108 |
+
source = { registry = "https://pypi.org/simple" }
|
| 109 |
+
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" }
|
| 110 |
+
wheels = [
|
| 111 |
+
{ 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" },
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
[[package]]
|
| 115 |
+
name = "coverage"
|
| 116 |
+
version = "7.13.5"
|
| 117 |
+
source = { registry = "https://pypi.org/simple" }
|
| 118 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" }
|
| 119 |
+
wheels = [
|
| 120 |
+
{ url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" },
|
| 121 |
+
{ url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" },
|
| 122 |
+
{ url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" },
|
| 123 |
+
{ url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" },
|
| 124 |
+
{ url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" },
|
| 125 |
+
{ url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" },
|
| 126 |
+
{ url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" },
|
| 127 |
+
{ url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" },
|
| 128 |
+
{ url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" },
|
| 129 |
+
{ url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" },
|
| 130 |
+
{ url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" },
|
| 131 |
+
{ url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" },
|
| 132 |
+
{ url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" },
|
| 133 |
+
{ url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" },
|
| 134 |
+
{ url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" },
|
| 135 |
+
{ url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" },
|
| 136 |
+
]
|
| 137 |
+
|
| 138 |
+
[[package]]
|
| 139 |
+
name = "facecloak"
|
| 140 |
+
version = "0.1.0"
|
| 141 |
+
source = { virtual = "." }
|
| 142 |
+
dependencies = [
|
| 143 |
+
{ name = "facenet-pytorch" },
|
| 144 |
+
{ name = "gradio" },
|
| 145 |
+
{ name = "huggingface-hub" },
|
| 146 |
+
{ name = "numpy" },
|
| 147 |
+
{ name = "pillow" },
|
| 148 |
+
{ name = "torch" },
|
| 149 |
+
{ name = "torchvision" },
|
| 150 |
+
]
|
| 151 |
+
|
| 152 |
+
[package.dev-dependencies]
|
| 153 |
+
dev = [
|
| 154 |
+
{ name = "pytest" },
|
| 155 |
+
{ name = "pytest-cov" },
|
| 156 |
+
]
|
| 157 |
+
|
| 158 |
+
[package.metadata]
|
| 159 |
+
requires-dist = [
|
| 160 |
+
{ name = "facenet-pytorch", specifier = "==2.6.0" },
|
| 161 |
+
{ name = "gradio", specifier = "==6.12.0" },
|
| 162 |
+
{ name = "huggingface-hub", specifier = "==1.10.2" },
|
| 163 |
+
{ name = "numpy", specifier = "==1.26.4" },
|
| 164 |
+
{ name = "pillow", specifier = "==10.2.0" },
|
| 165 |
+
{ name = "torch", specifier = "==2.2.2" },
|
| 166 |
+
{ name = "torchvision", specifier = "==0.17.2" },
|
| 167 |
+
]
|
| 168 |
+
|
| 169 |
+
[package.metadata.requires-dev]
|
| 170 |
+
dev = [
|
| 171 |
+
{ name = "pytest", specifier = "==9.0.3" },
|
| 172 |
+
{ name = "pytest-cov", specifier = "==7.1.0" },
|
| 173 |
+
]
|
| 174 |
+
|
| 175 |
+
[[package]]
|
| 176 |
+
name = "facenet-pytorch"
|
| 177 |
+
version = "2.6.0"
|
| 178 |
+
source = { registry = "https://pypi.org/simple" }
|
| 179 |
+
dependencies = [
|
| 180 |
+
{ name = "numpy" },
|
| 181 |
+
{ name = "pillow" },
|
| 182 |
+
{ name = "requests" },
|
| 183 |
+
{ name = "torch" },
|
| 184 |
+
{ name = "torchvision" },
|
| 185 |
+
{ name = "tqdm" },
|
| 186 |
+
]
|
| 187 |
+
wheels = [
|
| 188 |
+
{ url = "https://files.pythonhosted.org/packages/ed/2e/2d56386bc2f834cdc683743903852cf1428b4e5ee119f16cf808b589d3cd/facenet_pytorch-2.6.0-py3-none-any.whl", hash = "sha256:ecb82b27beb226d106f2219efe8f829b01b87a8595badd01545679bdb9f19cca", size = 1881930, upload-time = "2024-04-29T17:50:14.053Z" },
|
| 189 |
+
]
|
| 190 |
+
|
| 191 |
+
[[package]]
|
| 192 |
+
name = "fastapi"
|
| 193 |
+
version = "0.135.3"
|
| 194 |
+
source = { registry = "https://pypi.org/simple" }
|
| 195 |
+
dependencies = [
|
| 196 |
+
{ name = "annotated-doc" },
|
| 197 |
+
{ name = "pydantic" },
|
| 198 |
+
{ name = "starlette" },
|
| 199 |
+
{ name = "typing-extensions" },
|
| 200 |
+
{ name = "typing-inspection" },
|
| 201 |
+
]
|
| 202 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f7/e6/7adb4c5fa231e82c35b8f5741a9f2d055f520c29af5546fd70d3e8e1cd2e/fastapi-0.135.3.tar.gz", hash = "sha256:bd6d7caf1a2bdd8d676843cdcd2287729572a1ef524fc4d65c17ae002a1be654", size = 396524, upload-time = "2026-04-01T16:23:58.188Z" }
|
| 203 |
+
wheels = [
|
| 204 |
+
{ url = "https://files.pythonhosted.org/packages/84/a4/5caa2de7f917a04ada20018eccf60d6cc6145b0199d55ca3711b0fc08312/fastapi-0.135.3-py3-none-any.whl", hash = "sha256:9b0f590c813acd13d0ab43dd8494138eb58e484bfac405db1f3187cfc5810d98", size = 117734, upload-time = "2026-04-01T16:23:59.328Z" },
|
| 205 |
+
]
|
| 206 |
+
|
| 207 |
+
[[package]]
|
| 208 |
+
name = "filelock"
|
| 209 |
+
version = "3.28.0"
|
| 210 |
+
source = { registry = "https://pypi.org/simple" }
|
| 211 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/17/6e8890271880903e3538660a21d63a6c1fea969ac71d0d6b608b78727fa9/filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6", size = 56474, upload-time = "2026-04-14T22:54:33.625Z" }
|
| 212 |
+
wheels = [
|
| 213 |
+
{ url = "https://files.pythonhosted.org/packages/3b/21/2f728888c45033d34a417bfcd248ea2564c9e08ab1bfd301377cf05d5586/filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db", size = 39189, upload-time = "2026-04-14T22:54:32.037Z" },
|
| 214 |
+
]
|
| 215 |
+
|
| 216 |
+
[[package]]
|
| 217 |
+
name = "fsspec"
|
| 218 |
+
version = "2026.3.0"
|
| 219 |
+
source = { registry = "https://pypi.org/simple" }
|
| 220 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547, upload-time = "2026-03-27T19:11:14.892Z" }
|
| 221 |
+
wheels = [
|
| 222 |
+
{ url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" },
|
| 223 |
+
]
|
| 224 |
+
|
| 225 |
+
[[package]]
|
| 226 |
+
name = "gradio"
|
| 227 |
+
version = "6.12.0"
|
| 228 |
+
source = { registry = "https://pypi.org/simple" }
|
| 229 |
+
dependencies = [
|
| 230 |
+
{ name = "anyio" },
|
| 231 |
+
{ name = "brotli" },
|
| 232 |
+
{ name = "fastapi" },
|
| 233 |
+
{ name = "gradio-client" },
|
| 234 |
+
{ name = "groovy" },
|
| 235 |
+
{ name = "hf-gradio" },
|
| 236 |
+
{ name = "httpx" },
|
| 237 |
+
{ name = "huggingface-hub" },
|
| 238 |
+
{ name = "jinja2" },
|
| 239 |
+
{ name = "markupsafe" },
|
| 240 |
+
{ name = "numpy" },
|
| 241 |
+
{ name = "orjson" },
|
| 242 |
+
{ name = "packaging" },
|
| 243 |
+
{ name = "pandas" },
|
| 244 |
+
{ name = "pillow" },
|
| 245 |
+
{ name = "pydantic" },
|
| 246 |
+
{ name = "pydub" },
|
| 247 |
+
{ name = "python-multipart" },
|
| 248 |
+
{ name = "pytz" },
|
| 249 |
+
{ name = "pyyaml" },
|
| 250 |
+
{ name = "safehttpx" },
|
| 251 |
+
{ name = "semantic-version" },
|
| 252 |
+
{ name = "starlette" },
|
| 253 |
+
{ name = "tomlkit" },
|
| 254 |
+
{ name = "typer" },
|
| 255 |
+
{ name = "typing-extensions" },
|
| 256 |
+
{ name = "uvicorn" },
|
| 257 |
+
]
|
| 258 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9c/a3/d7b0f752d010a9ccf7ab938d787c912d7ebb43cd3424754e0266a53b4273/gradio-6.12.0.tar.gz", hash = "sha256:5a7808e7ef82cdcb711608275d6779d3368072f93777d649661ffae968af8107", size = 35955906, upload-time = "2026-04-10T20:57:02.335Z" }
|
| 259 |
+
wheels = [
|
| 260 |
+
{ url = "https://files.pythonhosted.org/packages/dd/30/d7c61ec8c3f4ad337e20090d771c20d4b61e84aede8d92dc784ab32efa3c/gradio-6.12.0-py3-none-any.whl", hash = "sha256:5587f9ab7b8bfb757e0de9dffebadc63bb354dec5dde22fd7f0266df4b8bd271", size = 19633159, upload-time = "2026-04-10T20:56:59.084Z" },
|
| 261 |
+
]
|
| 262 |
+
|
| 263 |
+
[[package]]
|
| 264 |
+
name = "gradio-client"
|
| 265 |
+
version = "2.4.1"
|
| 266 |
+
source = { registry = "https://pypi.org/simple" }
|
| 267 |
+
dependencies = [
|
| 268 |
+
{ name = "fsspec" },
|
| 269 |
+
{ name = "httpx" },
|
| 270 |
+
{ name = "huggingface-hub" },
|
| 271 |
+
{ name = "packaging" },
|
| 272 |
+
{ name = "typing-extensions" },
|
| 273 |
+
]
|
| 274 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5f/5f/d480fb5e61cf47eaae6c4de2c84a5f130ebcaa2a0a4f36f9f494f53eea35/gradio_client-2.4.1.tar.gz", hash = "sha256:c08783eb3dbdfd6d567bfd912a432fd8ab37b9e633b03e794cc1a0fa105a6d31", size = 58611, upload-time = "2026-04-10T20:57:13.174Z" }
|
| 275 |
+
wheels = [
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/68/72/a4611ed242a7936d38bd5262c6071fff8dda86cab1c06172a1e372783772/gradio_client-2.4.1-py3-none-any.whl", hash = "sha256:e6cef91ddee06e6a10788c5119977a35aee987968e7a87b0c4716809172c33d4", size = 59563, upload-time = "2026-04-10T20:57:11.774Z" },
|
| 277 |
+
]
|
| 278 |
+
|
| 279 |
+
[[package]]
|
| 280 |
+
name = "groovy"
|
| 281 |
+
version = "0.1.2"
|
| 282 |
+
source = { registry = "https://pypi.org/simple" }
|
| 283 |
+
sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" }
|
| 284 |
+
wheels = [
|
| 285 |
+
{ url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" },
|
| 286 |
+
]
|
| 287 |
+
|
| 288 |
+
[[package]]
|
| 289 |
+
name = "h11"
|
| 290 |
+
version = "0.16.0"
|
| 291 |
+
source = { registry = "https://pypi.org/simple" }
|
| 292 |
+
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" }
|
| 293 |
+
wheels = [
|
| 294 |
+
{ 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" },
|
| 295 |
+
]
|
| 296 |
+
|
| 297 |
+
[[package]]
|
| 298 |
+
name = "hf-gradio"
|
| 299 |
+
version = "0.3.2"
|
| 300 |
+
source = { registry = "https://pypi.org/simple" }
|
| 301 |
+
dependencies = [
|
| 302 |
+
{ name = "gradio-client" },
|
| 303 |
+
{ name = "typer" },
|
| 304 |
+
]
|
| 305 |
+
wheels = [
|
| 306 |
+
{ url = "https://files.pythonhosted.org/packages/37/ce/c13bc8dbbf36d4b0b491fac4238e4b3a48d0e119d59b2b4d2b5d1be00b66/hf_gradio-0.3.2-py3-none-any.whl", hash = "sha256:107d9eca07d1d3eea52f9d8f8691890d964aed3873b439932d90197da38458a7", size = 4381, upload-time = "2026-04-15T20:32:10.412Z" },
|
| 307 |
+
]
|
| 308 |
+
|
| 309 |
+
[[package]]
|
| 310 |
+
name = "hf-xet"
|
| 311 |
+
version = "1.4.3"
|
| 312 |
+
source = { registry = "https://pypi.org/simple" }
|
| 313 |
+
sdist = { url = "https://files.pythonhosted.org/packages/53/92/ec9ad04d0b5728dca387a45af7bc98fbb0d73b2118759f5f6038b61a57e8/hf_xet-1.4.3.tar.gz", hash = "sha256:8ddedb73c8c08928c793df2f3401ec26f95be7f7e516a7bee2fbb546f6676113", size = 670477, upload-time = "2026-03-31T22:40:07.874Z" }
|
| 314 |
+
wheels = [
|
| 315 |
+
{ url = "https://files.pythonhosted.org/packages/ac/9f/9c23e4a447b8f83120798f9279d0297a4d1360bdbf59ef49ebec78fe2545/hf_xet-1.4.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d0da85329eaf196e03e90b84c2d0aca53bd4573d097a75f99609e80775f98025", size = 3805048, upload-time = "2026-03-31T22:39:53.105Z" },
|
| 316 |
+
{ url = "https://files.pythonhosted.org/packages/0b/f8/7aacb8e5f4a7899d39c787b5984e912e6c18b11be136ef13947d7a66d265/hf_xet-1.4.3-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e23717ce4186b265f69afa66e6f0069fe7efbf331546f5c313d00e123dc84583", size = 3562178, upload-time = "2026-03-31T22:39:51.295Z" },
|
| 317 |
+
{ url = "https://files.pythonhosted.org/packages/df/9a/a24b26dc8a65f0ecc0fe5be981a19e61e7ca963b85e062c083f3a9100529/hf_xet-1.4.3-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc360b70c815bf340ed56c7b8c63aacf11762a4b099b2fe2c9bd6d6068668c08", size = 4212320, upload-time = "2026-03-31T22:39:42.922Z" },
|
| 318 |
+
{ url = "https://files.pythonhosted.org/packages/53/60/46d493db155d2ee2801b71fb1b0fd67696359047fdd8caee2c914cc50c79/hf_xet-1.4.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:39f2d2e9654cd9b4319885733993807aab6de9dfbd34c42f0b78338d6617421f", size = 3991546, upload-time = "2026-03-31T22:39:41.335Z" },
|
| 319 |
+
{ url = "https://files.pythonhosted.org/packages/bc/f5/067363e1c96c6b17256910830d1b54099d06287e10f4ec6ec4e7e08371fc/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:49ad8a8cead2b56051aa84d7fce3e1335efe68df3cf6c058f22a65513885baac", size = 4193200, upload-time = "2026-03-31T22:40:01.936Z" },
|
| 320 |
+
{ url = "https://files.pythonhosted.org/packages/42/4b/53951592882d9c23080c7644542fda34a3813104e9e11fa1a7d82d419cb8/hf_xet-1.4.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7716d62015477a70ea272d2d68cd7cad140f61c52ee452e133e139abfe2c17ba", size = 4429392, upload-time = "2026-03-31T22:40:03.492Z" },
|
| 321 |
+
{ url = "https://files.pythonhosted.org/packages/8a/21/75a6c175b4e79662ad8e62f46a40ce341d8d6b206b06b4320d07d55b188c/hf_xet-1.4.3-cp37-abi3-win_amd64.whl", hash = "sha256:6b591fcad34e272a5b02607485e4f2a1334aebf1bc6d16ce8eb1eb8978ac2021", size = 3677359, upload-time = "2026-03-31T22:40:13.619Z" },
|
| 322 |
+
{ url = "https://files.pythonhosted.org/packages/8a/7c/44314ecd0e89f8b2b51c9d9e5e7a60a9c1c82024ac471d415860557d3cd8/hf_xet-1.4.3-cp37-abi3-win_arm64.whl", hash = "sha256:7c2c7e20bcfcc946dc67187c203463f5e932e395845d098cc2a93f5b67ca0b47", size = 3533664, upload-time = "2026-03-31T22:40:12.152Z" },
|
| 323 |
+
]
|
| 324 |
+
|
| 325 |
+
[[package]]
|
| 326 |
+
name = "httpcore"
|
| 327 |
+
version = "1.0.9"
|
| 328 |
+
source = { registry = "https://pypi.org/simple" }
|
| 329 |
+
dependencies = [
|
| 330 |
+
{ name = "certifi" },
|
| 331 |
+
{ name = "h11" },
|
| 332 |
+
]
|
| 333 |
+
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" }
|
| 334 |
+
wheels = [
|
| 335 |
+
{ 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" },
|
| 336 |
+
]
|
| 337 |
+
|
| 338 |
+
[[package]]
|
| 339 |
+
name = "httpx"
|
| 340 |
+
version = "0.28.1"
|
| 341 |
+
source = { registry = "https://pypi.org/simple" }
|
| 342 |
+
dependencies = [
|
| 343 |
+
{ name = "anyio" },
|
| 344 |
+
{ name = "certifi" },
|
| 345 |
+
{ name = "httpcore" },
|
| 346 |
+
{ name = "idna" },
|
| 347 |
+
]
|
| 348 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
| 349 |
+
wheels = [
|
| 350 |
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
| 351 |
+
]
|
| 352 |
+
|
| 353 |
+
[[package]]
|
| 354 |
+
name = "huggingface-hub"
|
| 355 |
+
version = "1.10.2"
|
| 356 |
+
source = { registry = "https://pypi.org/simple" }
|
| 357 |
+
dependencies = [
|
| 358 |
+
{ name = "filelock" },
|
| 359 |
+
{ name = "fsspec" },
|
| 360 |
+
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
| 361 |
+
{ name = "httpx" },
|
| 362 |
+
{ name = "packaging" },
|
| 363 |
+
{ name = "pyyaml" },
|
| 364 |
+
{ name = "tqdm" },
|
| 365 |
+
{ name = "typer" },
|
| 366 |
+
{ name = "typing-extensions" },
|
| 367 |
+
]
|
| 368 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0c/4d/00734890c7fcfe2c7ff04f1c1a167186c42b19e370a2dd8cfd8c34fc92c4/huggingface_hub-1.10.2.tar.gz", hash = "sha256:4b276f820483b709dc86a53bcb8183ea496b8d8447c9f7f88a115a12b498a95f", size = 758428, upload-time = "2026-04-14T10:42:28.498Z" }
|
| 369 |
+
wheels = [
|
| 370 |
+
{ url = "https://files.pythonhosted.org/packages/5e/c9/4c1e1216b24bcab140c83acdf8bc89a846ea17cd8a06cd18e3fd308a297f/huggingface_hub-1.10.2-py3-none-any.whl", hash = "sha256:c26c908767cc711493978dc0b4f5747ba7841602997cc98bfd628450a28cf9bc", size = 642581, upload-time = "2026-04-14T10:42:26.563Z" },
|
| 371 |
+
]
|
| 372 |
+
|
| 373 |
+
[[package]]
|
| 374 |
+
name = "idna"
|
| 375 |
+
version = "3.11"
|
| 376 |
+
source = { registry = "https://pypi.org/simple" }
|
| 377 |
+
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" }
|
| 378 |
+
wheels = [
|
| 379 |
+
{ 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" },
|
| 380 |
+
]
|
| 381 |
+
|
| 382 |
+
[[package]]
|
| 383 |
+
name = "iniconfig"
|
| 384 |
+
version = "2.3.0"
|
| 385 |
+
source = { registry = "https://pypi.org/simple" }
|
| 386 |
+
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" }
|
| 387 |
+
wheels = [
|
| 388 |
+
{ 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" },
|
| 389 |
+
]
|
| 390 |
+
|
| 391 |
+
[[package]]
|
| 392 |
+
name = "jinja2"
|
| 393 |
+
version = "3.1.6"
|
| 394 |
+
source = { registry = "https://pypi.org/simple" }
|
| 395 |
+
dependencies = [
|
| 396 |
+
{ name = "markupsafe" },
|
| 397 |
+
]
|
| 398 |
+
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" }
|
| 399 |
+
wheels = [
|
| 400 |
+
{ 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" },
|
| 401 |
+
]
|
| 402 |
+
|
| 403 |
+
[[package]]
|
| 404 |
+
name = "markdown-it-py"
|
| 405 |
+
version = "4.0.0"
|
| 406 |
+
source = { registry = "https://pypi.org/simple" }
|
| 407 |
+
dependencies = [
|
| 408 |
+
{ name = "mdurl" },
|
| 409 |
+
]
|
| 410 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
| 411 |
+
wheels = [
|
| 412 |
+
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
| 413 |
+
]
|
| 414 |
+
|
| 415 |
+
[[package]]
|
| 416 |
+
name = "markupsafe"
|
| 417 |
+
version = "3.0.3"
|
| 418 |
+
source = { registry = "https://pypi.org/simple" }
|
| 419 |
+
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" }
|
| 420 |
+
wheels = [
|
| 421 |
+
{ 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" },
|
| 422 |
+
{ 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" },
|
| 423 |
+
{ 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" },
|
| 424 |
+
{ 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" },
|
| 425 |
+
{ 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" },
|
| 426 |
+
{ 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" },
|
| 427 |
+
{ 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" },
|
| 428 |
+
{ 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" },
|
| 429 |
+
{ 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" },
|
| 430 |
+
{ 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" },
|
| 431 |
+
{ 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" },
|
| 432 |
+
]
|
| 433 |
+
|
| 434 |
+
[[package]]
|
| 435 |
+
name = "mdurl"
|
| 436 |
+
version = "0.1.2"
|
| 437 |
+
source = { registry = "https://pypi.org/simple" }
|
| 438 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
| 439 |
+
wheels = [
|
| 440 |
+
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
| 441 |
+
]
|
| 442 |
+
|
| 443 |
+
[[package]]
|
| 444 |
+
name = "mpmath"
|
| 445 |
+
version = "1.3.0"
|
| 446 |
+
source = { registry = "https://pypi.org/simple" }
|
| 447 |
+
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" }
|
| 448 |
+
wheels = [
|
| 449 |
+
{ 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" },
|
| 450 |
+
]
|
| 451 |
+
|
| 452 |
+
[[package]]
|
| 453 |
+
name = "networkx"
|
| 454 |
+
version = "3.6.1"
|
| 455 |
+
source = { registry = "https://pypi.org/simple" }
|
| 456 |
+
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" }
|
| 457 |
+
wheels = [
|
| 458 |
+
{ 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" },
|
| 459 |
+
]
|
| 460 |
+
|
| 461 |
+
[[package]]
|
| 462 |
+
name = "numpy"
|
| 463 |
+
version = "1.26.4"
|
| 464 |
+
source = { registry = "https://pypi.org/simple" }
|
| 465 |
+
sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" }
|
| 466 |
+
wheels = [
|
| 467 |
+
{ url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" },
|
| 468 |
+
{ url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" },
|
| 469 |
+
{ url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" },
|
| 470 |
+
{ url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" },
|
| 471 |
+
{ url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" },
|
| 472 |
+
{ url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" },
|
| 473 |
+
{ url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" },
|
| 474 |
+
{ url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" },
|
| 475 |
+
]
|
| 476 |
+
|
| 477 |
+
[[package]]
|
| 478 |
+
name = "nvidia-cublas-cu12"
|
| 479 |
+
version = "12.1.3.1"
|
| 480 |
+
source = { registry = "https://pypi.org/simple" }
|
| 481 |
+
wheels = [
|
| 482 |
+
{ 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" },
|
| 483 |
+
]
|
| 484 |
+
|
| 485 |
+
[[package]]
|
| 486 |
+
name = "nvidia-cuda-cupti-cu12"
|
| 487 |
+
version = "12.1.105"
|
| 488 |
+
source = { registry = "https://pypi.org/simple" }
|
| 489 |
+
wheels = [
|
| 490 |
+
{ 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" },
|
| 491 |
+
]
|
| 492 |
+
|
| 493 |
+
[[package]]
|
| 494 |
+
name = "nvidia-cuda-nvrtc-cu12"
|
| 495 |
+
version = "12.1.105"
|
| 496 |
+
source = { registry = "https://pypi.org/simple" }
|
| 497 |
+
wheels = [
|
| 498 |
+
{ 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" },
|
| 499 |
+
]
|
| 500 |
+
|
| 501 |
+
[[package]]
|
| 502 |
+
name = "nvidia-cuda-runtime-cu12"
|
| 503 |
+
version = "12.1.105"
|
| 504 |
+
source = { registry = "https://pypi.org/simple" }
|
| 505 |
+
wheels = [
|
| 506 |
+
{ 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" },
|
| 507 |
+
]
|
| 508 |
+
|
| 509 |
+
[[package]]
|
| 510 |
+
name = "nvidia-cudnn-cu12"
|
| 511 |
+
version = "8.9.2.26"
|
| 512 |
+
source = { registry = "https://pypi.org/simple" }
|
| 513 |
+
dependencies = [
|
| 514 |
+
{ name = "nvidia-cublas-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
| 515 |
+
]
|
| 516 |
+
wheels = [
|
| 517 |
+
{ 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" },
|
| 518 |
+
]
|
| 519 |
+
|
| 520 |
+
[[package]]
|
| 521 |
+
name = "nvidia-cufft-cu12"
|
| 522 |
+
version = "11.0.2.54"
|
| 523 |
+
source = { registry = "https://pypi.org/simple" }
|
| 524 |
+
wheels = [
|
| 525 |
+
{ 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" },
|
| 526 |
+
]
|
| 527 |
+
|
| 528 |
+
[[package]]
|
| 529 |
+
name = "nvidia-curand-cu12"
|
| 530 |
+
version = "10.3.2.106"
|
| 531 |
+
source = { registry = "https://pypi.org/simple" }
|
| 532 |
+
wheels = [
|
| 533 |
+
{ 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" },
|
| 534 |
+
]
|
| 535 |
+
|
| 536 |
+
[[package]]
|
| 537 |
+
name = "nvidia-cusolver-cu12"
|
| 538 |
+
version = "11.4.5.107"
|
| 539 |
+
source = { registry = "https://pypi.org/simple" }
|
| 540 |
+
dependencies = [
|
| 541 |
+
{ name = "nvidia-cublas-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
| 542 |
+
{ name = "nvidia-cusparse-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
| 543 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
| 544 |
+
]
|
| 545 |
+
wheels = [
|
| 546 |
+
{ 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" },
|
| 547 |
+
]
|
| 548 |
+
|
| 549 |
+
[[package]]
|
| 550 |
+
name = "nvidia-cusparse-cu12"
|
| 551 |
+
version = "12.1.0.106"
|
| 552 |
+
source = { registry = "https://pypi.org/simple" }
|
| 553 |
+
dependencies = [
|
| 554 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
| 555 |
+
]
|
| 556 |
+
wheels = [
|
| 557 |
+
{ 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" },
|
| 558 |
+
]
|
| 559 |
+
|
| 560 |
+
[[package]]
|
| 561 |
+
name = "nvidia-nccl-cu12"
|
| 562 |
+
version = "2.19.3"
|
| 563 |
+
source = { registry = "https://pypi.org/simple" }
|
| 564 |
+
wheels = [
|
| 565 |
+
{ url = "https://files.pythonhosted.org/packages/38/00/d0d4e48aef772ad5aebcf70b73028f88db6e5640b36c38e90445b7a57c45/nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl", hash = "sha256:a9734707a2c96443331c1e48c717024aa6678a0e2a4cb66b2c364d18cee6b48d", size = 165987969, upload-time = "2023-10-24T16:16:24.789Z" },
|
| 566 |
+
]
|
| 567 |
+
|
| 568 |
+
[[package]]
|
| 569 |
+
name = "nvidia-nvjitlink-cu12"
|
| 570 |
+
version = "12.9.86"
|
| 571 |
+
source = { registry = "https://pypi.org/simple" }
|
| 572 |
+
wheels = [
|
| 573 |
+
{ 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" },
|
| 574 |
+
]
|
| 575 |
+
|
| 576 |
+
[[package]]
|
| 577 |
+
name = "nvidia-nvtx-cu12"
|
| 578 |
+
version = "12.1.105"
|
| 579 |
+
source = { registry = "https://pypi.org/simple" }
|
| 580 |
+
wheels = [
|
| 581 |
+
{ 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" },
|
| 582 |
+
]
|
| 583 |
+
|
| 584 |
+
[[package]]
|
| 585 |
+
name = "orjson"
|
| 586 |
+
version = "3.11.8"
|
| 587 |
+
source = { registry = "https://pypi.org/simple" }
|
| 588 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9d/1b/2024d06792d0779f9dbc51531b61c24f76c75b9f4ce05e6f3377a1814cea/orjson-3.11.8.tar.gz", hash = "sha256:96163d9cdc5a202703e9ad1b9ae757d5f0ca62f4fa0cc93d1f27b0e180cc404e", size = 5603832, upload-time = "2026-03-31T16:16:27.878Z" }
|
| 589 |
+
wheels = [
|
| 590 |
+
{ url = "https://files.pythonhosted.org/packages/01/f6/8d58b32ab32d9215973a1688aebd098252ee8af1766c0e4e36e7831f0295/orjson-3.11.8-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1cd0b77e77c95758f8e1100139844e99f3ccc87e71e6fc8e1c027e55807c549f", size = 229233, upload-time = "2026-03-31T16:15:12.762Z" },
|
| 591 |
+
{ url = "https://files.pythonhosted.org/packages/a9/8b/2ffe35e71f6b92622e8ea4607bf33ecf7dfb51b3619dcfabfd36cbe2d0a5/orjson-3.11.8-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:6a3d159d5ffa0e3961f353c4b036540996bf8b9697ccc38261c0eac1fd3347a6", size = 128772, upload-time = "2026-03-31T16:15:14.237Z" },
|
| 592 |
+
{ url = "https://files.pythonhosted.org/packages/27/d2/1f8682ae50d5c6897a563cb96bc106da8c9cb5b7b6e81a52e4cc086679b9/orjson-3.11.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76070a76e9c5ae661e2d9848f216980d8d533e0f8143e6ed462807b242e3c5e8", size = 131946, upload-time = "2026-03-31T16:15:15.607Z" },
|
| 593 |
+
{ url = "https://files.pythonhosted.org/packages/52/4b/5500f76f0eece84226e0689cb48dcde081104c2fa6e2483d17ca13685ffb/orjson-3.11.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54153d21520a71a4c82a0dbb4523e468941d549d221dc173de0f019678cf3813", size = 130368, upload-time = "2026-03-31T16:15:17.066Z" },
|
| 594 |
+
{ url = "https://files.pythonhosted.org/packages/da/4e/58b927e08fbe9840e6c920d9e299b051ea667463b1f39a56e668669f8508/orjson-3.11.8-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:469ac2125611b7c5741a0b3798cd9e5786cbad6345f9f400c77212be89563bec", size = 135540, upload-time = "2026-03-31T16:15:18.404Z" },
|
| 595 |
+
{ url = "https://files.pythonhosted.org/packages/56/7c/ba7cb871cba1bcd5cd02ee34f98d894c6cea96353ad87466e5aef2429c60/orjson-3.11.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14778ffd0f6896aa613951a7fbf4690229aa7a543cb2bfbe9f358e08aafa9546", size = 146877, upload-time = "2026-03-31T16:15:19.833Z" },
|
| 596 |
+
{ url = "https://files.pythonhosted.org/packages/0b/5d/eb9c25fc1386696c6a342cd361c306452c75e0b55e86ad602dd4827a7fd7/orjson-3.11.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea56a955056a6d6c550cf18b3348656a9d9a4f02e2d0c02cabf3c73f1055d506", size = 132837, upload-time = "2026-03-31T16:15:21.282Z" },
|
| 597 |
+
{ url = "https://files.pythonhosted.org/packages/37/87/5ddeb7fc1fbd9004aeccab08426f34c81a5b4c25c7061281862b015fce2b/orjson-3.11.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a0f57e59a530d18a142f4d4ba6dfc708dc5fdedce45e98ff06b44930a2a48f", size = 133624, upload-time = "2026-03-31T16:15:22.641Z" },
|
| 598 |
+
{ url = "https://files.pythonhosted.org/packages/22/09/90048793db94ee4b2fcec4ac8e5ddb077367637d6650be896b3494b79bb7/orjson-3.11.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b48e274f8824567d74e2158199e269597edf00823a1b12b63d48462bbf5123e", size = 141904, upload-time = "2026-03-31T16:15:24.435Z" },
|
| 599 |
+
{ url = "https://files.pythonhosted.org/packages/c0/cf/eb284847487821a5d415e54149a6449ba9bfc5872ce63ab7be41b8ec401c/orjson-3.11.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3f262401086a3960586af06c054609365e98407151f5ea24a62893a40d80dbbb", size = 423742, upload-time = "2026-03-31T16:15:26.155Z" },
|
| 600 |
+
{ url = "https://files.pythonhosted.org/packages/44/09/e12423d327071c851c13e76936f144a96adacfc037394dec35ac3fc8d1e8/orjson-3.11.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e8c6218b614badf8e229b697865df4301afa74b791b6c9ade01d19a9953a942", size = 147806, upload-time = "2026-03-31T16:15:27.909Z" },
|
| 601 |
+
{ url = "https://files.pythonhosted.org/packages/b3/6d/37c2589ba864e582ffe7611643314785c6afb1f83c701654ef05daa8fcc7/orjson-3.11.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093d489fa039ddade2db541097dbb484999fcc65fc2b0ff9819141e2ab364f25", size = 136485, upload-time = "2026-03-31T16:15:29.749Z" },
|
| 602 |
+
{ url = "https://files.pythonhosted.org/packages/be/c9/135194a02ab76b04ed9a10f68624b7ebd238bbe55548878b11ff15a0f352/orjson-3.11.8-cp312-cp312-win32.whl", hash = "sha256:e0950ed1bcb9893f4293fd5c5a7ee10934fbf82c4101c70be360db23ce24b7d2", size = 131966, upload-time = "2026-03-31T16:15:31.687Z" },
|
| 603 |
+
{ url = "https://files.pythonhosted.org/packages/ed/9a/9796f8fbe3cf30ce9cb696748dbb535e5c87be4bf4fe2e9ca498ef1fa8cf/orjson-3.11.8-cp312-cp312-win_amd64.whl", hash = "sha256:3cf17c141617b88ced4536b2135c552490f07799f6ad565948ea07bef0dcb9a6", size = 127441, upload-time = "2026-03-31T16:15:33.333Z" },
|
| 604 |
+
{ url = "https://files.pythonhosted.org/packages/cc/47/5aaf54524a7a4a0dd09dd778f3fa65dd2108290615b652e23d944152bc8e/orjson-3.11.8-cp312-cp312-win_arm64.whl", hash = "sha256:48854463b0572cc87dac7d981aa72ed8bf6deedc0511853dc76b8bbd5482d36d", size = 127364, upload-time = "2026-03-31T16:15:34.748Z" },
|
| 605 |
+
]
|
| 606 |
+
|
| 607 |
+
[[package]]
|
| 608 |
+
name = "packaging"
|
| 609 |
+
version = "26.1"
|
| 610 |
+
source = { registry = "https://pypi.org/simple" }
|
| 611 |
+
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" }
|
| 612 |
+
wheels = [
|
| 613 |
+
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" },
|
| 614 |
+
]
|
| 615 |
+
|
| 616 |
+
[[package]]
|
| 617 |
+
name = "pandas"
|
| 618 |
+
version = "3.0.2"
|
| 619 |
+
source = { registry = "https://pypi.org/simple" }
|
| 620 |
+
dependencies = [
|
| 621 |
+
{ name = "numpy" },
|
| 622 |
+
{ name = "python-dateutil" },
|
| 623 |
+
{ name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
|
| 624 |
+
]
|
| 625 |
+
sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" }
|
| 626 |
+
wheels = [
|
| 627 |
+
{ url = "https://files.pythonhosted.org/packages/f3/b0/c20bd4d6d3f736e6bd6b55794e9cd0a617b858eaad27c8f410ea05d953b7/pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18", size = 10347921, upload-time = "2026-03-31T06:46:33.36Z" },
|
| 628 |
+
{ url = "https://files.pythonhosted.org/packages/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14", size = 9888127, upload-time = "2026-03-31T06:46:36.253Z" },
|
| 629 |
+
{ url = "https://files.pythonhosted.org/packages/61/a9/16ea9346e1fc4a96e2896242d9bc674764fb9049b0044c0132502f7a771e/pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d", size = 10399577, upload-time = "2026-03-31T06:46:39.224Z" },
|
| 630 |
+
{ url = "https://files.pythonhosted.org/packages/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f", size = 10880030, upload-time = "2026-03-31T06:46:42.412Z" },
|
| 631 |
+
{ url = "https://files.pythonhosted.org/packages/da/65/7225c0ea4d6ce9cb2160a7fb7f39804871049f016e74782e5dade4d14109/pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab", size = 11409468, upload-time = "2026-03-31T06:46:45.2Z" },
|
| 632 |
+
{ url = "https://files.pythonhosted.org/packages/fa/5b/46e7c76032639f2132359b5cf4c785dd8cf9aea5ea64699eac752f02b9db/pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d", size = 11936381, upload-time = "2026-03-31T06:46:48.293Z" },
|
| 633 |
+
{ url = "https://files.pythonhosted.org/packages/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4", size = 9744993, upload-time = "2026-03-31T06:46:51.488Z" },
|
| 634 |
+
{ url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118, upload-time = "2026-03-31T06:46:54.548Z" },
|
| 635 |
+
]
|
| 636 |
+
|
| 637 |
+
[[package]]
|
| 638 |
+
name = "pillow"
|
| 639 |
+
version = "10.2.0"
|
| 640 |
+
source = { registry = "https://pypi.org/simple" }
|
| 641 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f8/3e/32cbd0129a28686621434cbf17bb64bf1458bfb838f1f668262fefce145c/pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e", size = 46212712, upload-time = "2024-01-02T09:16:59.702Z" }
|
| 642 |
+
wheels = [
|
| 643 |
+
{ url = "https://files.pythonhosted.org/packages/37/d5/2c00228ace73a7855a52053a92fdd6cea9b22393fbf3961125c11829dcd2/pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef", size = 3517780, upload-time = "2024-01-02T09:15:41.495Z" },
|
| 644 |
+
{ url = "https://files.pythonhosted.org/packages/9d/a0/28756da34d6b58c3c5f6c1d5589e4e8f4e73472b55875524ae9d6e7e98fe/pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac", size = 3317920, upload-time = "2024-01-02T09:15:44.116Z" },
|
| 645 |
+
{ url = "https://files.pythonhosted.org/packages/ab/72/e6a8887c0ce6c94cd0b74fef495a81f4ea4c742242de4bc1943abbd21f92/pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c", size = 4308358, upload-time = "2024-01-02T09:33:09.603Z" },
|
| 646 |
+
{ url = "https://files.pythonhosted.org/packages/a8/2f/86cf1dc4b0530e4c3e96edd0338dcc4809c2622d9d45460029a71a831473/pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa", size = 4422007, upload-time = "2024-01-02T09:15:46.355Z" },
|
| 647 |
+
{ url = "https://files.pythonhosted.org/packages/00/43/1ca3313b56ef623de0afebfe3d7a6e9c07e1a76c50ce191302018907b2b5/pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2", size = 4333841, upload-time = "2024-01-02T09:33:14.842Z" },
|
| 648 |
+
{ url = "https://files.pythonhosted.org/packages/5c/c6/5b6b1f7362267494a423b45af684d604491565e81436e3ebeefee68f78fd/pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04", size = 4502101, upload-time = "2024-01-02T09:15:48.416Z" },
|
| 649 |
+
{ url = "https://files.pythonhosted.org/packages/e6/c5/37e72d74c248adf133a2dd56890cf8632e2e46562e5fa70414445bbd3ae6/pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f", size = 4542122, upload-time = "2024-01-02T09:33:19.012Z" },
|
| 650 |
+
{ url = "https://files.pythonhosted.org/packages/fa/93/79979b8ab99da2958bf6fef1be745c344c4e727f07d1429c49c015e21db2/pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb", size = 4611042, upload-time = "2024-01-02T09:15:50.616Z" },
|
| 651 |
+
{ url = "https://files.pythonhosted.org/packages/ce/a7/11a539c1e12dfb9d67c35e5d3d99c7a6853face9083e6483360f4d9cd1d8/pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f", size = 2290438, upload-time = "2024-01-02T09:15:53.219Z" },
|
| 652 |
+
{ url = "https://files.pythonhosted.org/packages/51/07/7e9266a59bb267b56c1f432f6416653b9a78dda771c57740d064a8aa2a44/pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9", size = 2621845, upload-time = "2024-01-02T09:15:55.293Z" },
|
| 653 |
+
{ url = "https://files.pythonhosted.org/packages/a0/61/6cff8a8dbbac3d7fb7adb435b60737a7d0b0849f53e3af38f2c94d988da6/pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48", size = 2229322, upload-time = "2024-01-02T09:15:57.475Z" },
|
| 654 |
+
]
|
| 655 |
+
|
| 656 |
+
[[package]]
|
| 657 |
+
name = "pluggy"
|
| 658 |
+
version = "1.6.0"
|
| 659 |
+
source = { registry = "https://pypi.org/simple" }
|
| 660 |
+
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" }
|
| 661 |
+
wheels = [
|
| 662 |
+
{ 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" },
|
| 663 |
+
]
|
| 664 |
+
|
| 665 |
+
[[package]]
|
| 666 |
+
name = "pydantic"
|
| 667 |
+
version = "2.13.1"
|
| 668 |
+
source = { registry = "https://pypi.org/simple" }
|
| 669 |
+
dependencies = [
|
| 670 |
+
{ name = "annotated-types" },
|
| 671 |
+
{ name = "pydantic-core" },
|
| 672 |
+
{ name = "typing-extensions" },
|
| 673 |
+
{ name = "typing-inspection" },
|
| 674 |
+
]
|
| 675 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/6b/1353beb3d1cd5cf61cdec5b6f87a9872399de3bc5cae0b7ce07ff4de2ab0/pydantic-2.13.1.tar.gz", hash = "sha256:a0f829b279ddd1e39291133fe2539d2aa46cc6b150c1706a270ff0879e3774d2", size = 843746, upload-time = "2026-04-15T14:57:19.398Z" }
|
| 676 |
+
wheels = [
|
| 677 |
+
{ url = "https://files.pythonhosted.org/packages/81/5a/2225f4c176dbfed0d809e848b50ef08f70e61daa667b7fa14b0d311ae44d/pydantic-2.13.1-py3-none-any.whl", hash = "sha256:9557ecc2806faaf6037f85b1fbd963d01e30511c48085f0d573650fdeaad378a", size = 471917, upload-time = "2026-04-15T14:57:17.277Z" },
|
| 678 |
+
]
|
| 679 |
+
|
| 680 |
+
[[package]]
|
| 681 |
+
name = "pydantic-core"
|
| 682 |
+
version = "2.46.1"
|
| 683 |
+
source = { registry = "https://pypi.org/simple" }
|
| 684 |
+
dependencies = [
|
| 685 |
+
{ name = "typing-extensions" },
|
| 686 |
+
]
|
| 687 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/93/f97a86a7eb28faa1d038af2fd5d6166418b4433659108a4c311b57128b2d/pydantic_core-2.46.1.tar.gz", hash = "sha256:d408153772d9f298098fb5d620f045bdf0f017af0d5cb6e309ef8c205540caa4", size = 471230, upload-time = "2026-04-15T14:49:34.52Z" }
|
| 688 |
+
wheels = [
|
| 689 |
+
{ url = "https://files.pythonhosted.org/packages/ce/fb/caaa8ee23861c170f07dbd58fc2be3a2c02a32637693cbb23eef02e84808/pydantic_core-2.46.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae8c8c5eb4c796944f3166f2f0dab6c761c2c2cc5bd20e5f692128be8600b9a4", size = 2119472, upload-time = "2026-04-15T14:49:45.946Z" },
|
| 690 |
+
{ url = "https://files.pythonhosted.org/packages/fa/61/bcffaa52894489ff89e5e1cdde67429914bf083c0db7296bef153020f786/pydantic_core-2.46.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:daba6f5f5b986aa0682623a1a4f8d1ecb0ec00ce09cfa9ca71a3b742bc383e3a", size = 1951230, upload-time = "2026-04-15T14:52:27.646Z" },
|
| 691 |
+
{ url = "https://files.pythonhosted.org/packages/f8/95/80d2f43a2a1a1e3220fd329d614aa5a39e0a75d24353a3aaf226e605f1c2/pydantic_core-2.46.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0265f3a2460539ecc97817a80c7a23c458dd84191229b655522a2674f701f14e", size = 1976394, upload-time = "2026-04-15T14:50:32.742Z" },
|
| 692 |
+
{ url = "https://files.pythonhosted.org/packages/8d/31/2c5b1a207926b5fc1961a2d11da940129bc3841c36cc4df03014195b2966/pydantic_core-2.46.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb16c0156c4b4e94aa3719138cc43c53d30ff21126b6a3af63786dcc0757b56e", size = 2068455, upload-time = "2026-04-15T14:50:01.286Z" },
|
| 693 |
+
{ url = "https://files.pythonhosted.org/packages/7d/36/c6aa07274359a51ac62895895325ce90107e811c6cea39d2617a99ef10d7/pydantic_core-2.46.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b42d80fad8e4b283e1e4138f1142f0d038c46d137aad2f9824ad9086080dd41", size = 2239049, upload-time = "2026-04-15T14:53:02.216Z" },
|
| 694 |
+
{ url = "https://files.pythonhosted.org/packages/0a/3f/77cdd0db8bddc714842dfd93f737c863751cf02001c993341504f6b0cd53/pydantic_core-2.46.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cced85896d5b795293bc36b7e2fb0347a36c828551b50cbba510510d928548c", size = 2318681, upload-time = "2026-04-15T14:50:04.539Z" },
|
| 695 |
+
{ url = "https://files.pythonhosted.org/packages/a1/a3/09d929a40e6727274b0b500ad06e1b3f35d4f4665ae1c8ba65acbb17e9b5/pydantic_core-2.46.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a641cb1e74b44c418adaf9f5f450670dbec53511f030d8cde8d8accb66edc363", size = 2096527, upload-time = "2026-04-15T14:53:14.766Z" },
|
| 696 |
+
{ url = "https://files.pythonhosted.org/packages/89/ae/544c3a82456ebc254a9fcbe2715bab76c70acf9d291aaea24391147943e4/pydantic_core-2.46.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:191e7a122ab14eb12415fe3f92610fc06c7f1d2b4b9101d24d490d447ac92506", size = 2170407, upload-time = "2026-04-15T14:51:27.138Z" },
|
| 697 |
+
{ url = "https://files.pythonhosted.org/packages/9d/ce/0dfd881c7af4c522f47b325707bd9a2cdcf4f40e4f2fd30df0e9a3e8d393/pydantic_core-2.46.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fe4ff660f7938b5d92f21529ce331b011aa35e481ab64b7cd03f52384e544bb", size = 2188578, upload-time = "2026-04-15T14:50:39.655Z" },
|
| 698 |
+
{ url = "https://files.pythonhosted.org/packages/a1/e9/980ea2a6d5114dd1a62ecc5f56feb3d34555f33bd11043f042e5f7f0724a/pydantic_core-2.46.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:18fcea085b3adc3868d8d19606da52d7a52d8bccd8e28652b0778dbe5e6a6660", size = 2188959, upload-time = "2026-04-15T14:52:42.243Z" },
|
| 699 |
+
{ url = "https://files.pythonhosted.org/packages/e7/f1/595e0f50f4bfc56cde2fe558f2b0978f29f2865da894c6226231e17464a5/pydantic_core-2.46.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e8e589e7c9466e022d79e13c5764c2239b2e5a7993ba727822b021234f89b56b", size = 2339973, upload-time = "2026-04-15T14:52:10.642Z" },
|
| 700 |
+
{ url = "https://files.pythonhosted.org/packages/49/44/be9f979a6ab6b8c36865ccd92c3a38a760c66055e1f384665f35525134c4/pydantic_core-2.46.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f78eb3d4027963bdc9baccd177f02a98bf8714bc51fe17153d8b51218918b5bc", size = 2385228, upload-time = "2026-04-15T14:51:00.77Z" },
|
| 701 |
+
{ url = "https://files.pythonhosted.org/packages/5b/d4/c826cd711787d240219f01d0d3ca116cb55516b8b95277820aa9c85e1882/pydantic_core-2.46.1-cp312-cp312-win32.whl", hash = "sha256:54fe30c20cab03844dc63bdc6ddca67f74a2eb8482df69c1e5f68396856241be", size = 1978828, upload-time = "2026-04-15T14:50:29.362Z" },
|
| 702 |
+
{ url = "https://files.pythonhosted.org/packages/22/05/8a1fcf8181be4c7a9cfc34e5fbf2d9c3866edc9dfd3c48d5401806e0a523/pydantic_core-2.46.1-cp312-cp312-win_amd64.whl", hash = "sha256:aea4e22ed4c53f2774221435e39969a54d2e783f4aee902cdd6c8011415de893", size = 2070015, upload-time = "2026-04-15T14:49:47.301Z" },
|
| 703 |
+
{ url = "https://files.pythonhosted.org/packages/61/d5/fea36ad2882b99c174ef4ffbc7ea6523f6abe26060fbc1f77d6441670232/pydantic_core-2.46.1-cp312-cp312-win_arm64.whl", hash = "sha256:f76fb49c34b4d66aa6e552ce9e852ea97a3a06301a9f01ae82f23e449e3a55f8", size = 2030176, upload-time = "2026-04-15T14:50:47.307Z" },
|
| 704 |
+
{ url = "https://files.pythonhosted.org/packages/f4/97/95de673a1356a88b2efdaa120eb6af357a81555c35f6809a7a1423ff7aef/pydantic_core-2.46.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:5f9107a24a4bc00293434dfa95cf8968751ad0dd703b26ea83a75a56f7326041", size = 2107564, upload-time = "2026-04-15T14:50:49.14Z" },
|
| 705 |
+
{ url = "https://files.pythonhosted.org/packages/00/fc/a7c16d85211ea9accddc693b7d049f20b0c06440d9264d1e1c074394ee6c/pydantic_core-2.46.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:2b1801ba99876984d0a03362782819238141c4d0f3f67f69093663691332fc35", size = 1939925, upload-time = "2026-04-15T14:50:36.188Z" },
|
| 706 |
+
{ url = "https://files.pythonhosted.org/packages/2e/23/87841169d77820ddabeb81d82002c95dcb82163846666d74f5bdeeaec750/pydantic_core-2.46.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7fd82a91a20ed6d54fa8c91e7a98255b1ff45bf09b051bfe7fe04eb411e232e", size = 1995313, upload-time = "2026-04-15T14:50:22.538Z" },
|
| 707 |
+
{ url = "https://files.pythonhosted.org/packages/ea/96/b46609359a354fa9cd336fc5d93334f1c358b756cc81e4b397347a88fa6f/pydantic_core-2.46.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f135bf07c92c93def97008bc4496d16934da9efefd7204e5f22a2c92523cb1f", size = 2151197, upload-time = "2026-04-15T14:51:22.925Z" },
|
| 708 |
+
]
|
| 709 |
+
|
| 710 |
+
[[package]]
|
| 711 |
+
name = "pydub"
|
| 712 |
+
version = "0.25.1"
|
| 713 |
+
source = { registry = "https://pypi.org/simple" }
|
| 714 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" }
|
| 715 |
+
wheels = [
|
| 716 |
+
{ url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" },
|
| 717 |
+
]
|
| 718 |
+
|
| 719 |
+
[[package]]
|
| 720 |
+
name = "pygments"
|
| 721 |
+
version = "2.20.0"
|
| 722 |
+
source = { registry = "https://pypi.org/simple" }
|
| 723 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
| 724 |
+
wheels = [
|
| 725 |
+
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
| 726 |
+
]
|
| 727 |
+
|
| 728 |
+
[[package]]
|
| 729 |
+
name = "pytest"
|
| 730 |
+
version = "9.0.3"
|
| 731 |
+
source = { registry = "https://pypi.org/simple" }
|
| 732 |
+
dependencies = [
|
| 733 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 734 |
+
{ name = "iniconfig" },
|
| 735 |
+
{ name = "packaging" },
|
| 736 |
+
{ name = "pluggy" },
|
| 737 |
+
{ name = "pygments" },
|
| 738 |
+
]
|
| 739 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
| 740 |
+
wheels = [
|
| 741 |
+
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
| 742 |
+
]
|
| 743 |
+
|
| 744 |
+
[[package]]
|
| 745 |
+
name = "pytest-cov"
|
| 746 |
+
version = "7.1.0"
|
| 747 |
+
source = { registry = "https://pypi.org/simple" }
|
| 748 |
+
dependencies = [
|
| 749 |
+
{ name = "coverage" },
|
| 750 |
+
{ name = "pluggy" },
|
| 751 |
+
{ name = "pytest" },
|
| 752 |
+
]
|
| 753 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" }
|
| 754 |
+
wheels = [
|
| 755 |
+
{ url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" },
|
| 756 |
+
]
|
| 757 |
+
|
| 758 |
+
[[package]]
|
| 759 |
+
name = "python-dateutil"
|
| 760 |
+
version = "2.9.0.post0"
|
| 761 |
+
source = { registry = "https://pypi.org/simple" }
|
| 762 |
+
dependencies = [
|
| 763 |
+
{ name = "six" },
|
| 764 |
+
]
|
| 765 |
+
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
| 766 |
+
wheels = [
|
| 767 |
+
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
| 768 |
+
]
|
| 769 |
+
|
| 770 |
+
[[package]]
|
| 771 |
+
name = "python-multipart"
|
| 772 |
+
version = "0.0.26"
|
| 773 |
+
source = { registry = "https://pypi.org/simple" }
|
| 774 |
+
sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" }
|
| 775 |
+
wheels = [
|
| 776 |
+
{ url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" },
|
| 777 |
+
]
|
| 778 |
+
|
| 779 |
+
[[package]]
|
| 780 |
+
name = "pytz"
|
| 781 |
+
version = "2026.1.post1"
|
| 782 |
+
source = { registry = "https://pypi.org/simple" }
|
| 783 |
+
sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" }
|
| 784 |
+
wheels = [
|
| 785 |
+
{ url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" },
|
| 786 |
+
]
|
| 787 |
+
|
| 788 |
+
[[package]]
|
| 789 |
+
name = "pyyaml"
|
| 790 |
+
version = "6.0.3"
|
| 791 |
+
source = { registry = "https://pypi.org/simple" }
|
| 792 |
+
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" }
|
| 793 |
+
wheels = [
|
| 794 |
+
{ 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" },
|
| 795 |
+
{ 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" },
|
| 796 |
+
{ 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" },
|
| 797 |
+
{ 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" },
|
| 798 |
+
{ 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" },
|
| 799 |
+
{ 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" },
|
| 800 |
+
{ 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" },
|
| 801 |
+
{ 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" },
|
| 802 |
+
{ 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" },
|
| 803 |
+
{ 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" },
|
| 804 |
+
]
|
| 805 |
+
|
| 806 |
+
[[package]]
|
| 807 |
+
name = "requests"
|
| 808 |
+
version = "2.33.1"
|
| 809 |
+
source = { registry = "https://pypi.org/simple" }
|
| 810 |
+
dependencies = [
|
| 811 |
+
{ name = "certifi" },
|
| 812 |
+
{ name = "charset-normalizer" },
|
| 813 |
+
{ name = "idna" },
|
| 814 |
+
{ name = "urllib3" },
|
| 815 |
+
]
|
| 816 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
|
| 817 |
+
wheels = [
|
| 818 |
+
{ url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
|
| 819 |
+
]
|
| 820 |
+
|
| 821 |
+
[[package]]
|
| 822 |
+
name = "rich"
|
| 823 |
+
version = "15.0.0"
|
| 824 |
+
source = { registry = "https://pypi.org/simple" }
|
| 825 |
+
dependencies = [
|
| 826 |
+
{ name = "markdown-it-py" },
|
| 827 |
+
{ name = "pygments" },
|
| 828 |
+
]
|
| 829 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
|
| 830 |
+
wheels = [
|
| 831 |
+
{ url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
|
| 832 |
+
]
|
| 833 |
+
|
| 834 |
+
[[package]]
|
| 835 |
+
name = "safehttpx"
|
| 836 |
+
version = "0.1.7"
|
| 837 |
+
source = { registry = "https://pypi.org/simple" }
|
| 838 |
+
dependencies = [
|
| 839 |
+
{ name = "httpx" },
|
| 840 |
+
]
|
| 841 |
+
sdist = { url = "https://files.pythonhosted.org/packages/89/d1/4282284d9cf1ee873607a46442da977fc3c985059315ab23610be31d5885/safehttpx-0.1.7.tar.gz", hash = "sha256:db201c0978c41eddb8bb480f3eee59dd67304fdd91646035e9d9a720049a9d23", size = 10385, upload-time = "2025-10-24T18:30:09.783Z" }
|
| 842 |
+
wheels = [
|
| 843 |
+
{ url = "https://files.pythonhosted.org/packages/2e/a3/0f0b7d78e2f1eb9e8e1afbff1d2bff8d60144aee17aca51c065b516743dd/safehttpx-0.1.7-py3-none-any.whl", hash = "sha256:c4f4a162db6993464d7ca3d7cc4af0ffc6515a606dfd220b9f82c6945d869cde", size = 8959, upload-time = "2025-10-24T18:30:08.733Z" },
|
| 844 |
+
]
|
| 845 |
+
|
| 846 |
+
[[package]]
|
| 847 |
+
name = "semantic-version"
|
| 848 |
+
version = "2.10.0"
|
| 849 |
+
source = { registry = "https://pypi.org/simple" }
|
| 850 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" }
|
| 851 |
+
wheels = [
|
| 852 |
+
{ url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" },
|
| 853 |
+
]
|
| 854 |
+
|
| 855 |
+
[[package]]
|
| 856 |
+
name = "shellingham"
|
| 857 |
+
version = "1.5.4"
|
| 858 |
+
source = { registry = "https://pypi.org/simple" }
|
| 859 |
+
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
| 860 |
+
wheels = [
|
| 861 |
+
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
| 862 |
+
]
|
| 863 |
+
|
| 864 |
+
[[package]]
|
| 865 |
+
name = "six"
|
| 866 |
+
version = "1.17.0"
|
| 867 |
+
source = { registry = "https://pypi.org/simple" }
|
| 868 |
+
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
| 869 |
+
wheels = [
|
| 870 |
+
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
| 871 |
+
]
|
| 872 |
+
|
| 873 |
+
[[package]]
|
| 874 |
+
name = "starlette"
|
| 875 |
+
version = "1.0.0"
|
| 876 |
+
source = { registry = "https://pypi.org/simple" }
|
| 877 |
+
dependencies = [
|
| 878 |
+
{ name = "anyio" },
|
| 879 |
+
{ name = "typing-extensions" },
|
| 880 |
+
]
|
| 881 |
+
sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
|
| 882 |
+
wheels = [
|
| 883 |
+
{ url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
|
| 884 |
+
]
|
| 885 |
+
|
| 886 |
+
[[package]]
|
| 887 |
+
name = "sympy"
|
| 888 |
+
version = "1.14.0"
|
| 889 |
+
source = { registry = "https://pypi.org/simple" }
|
| 890 |
+
dependencies = [
|
| 891 |
+
{ name = "mpmath" },
|
| 892 |
+
]
|
| 893 |
+
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" }
|
| 894 |
+
wheels = [
|
| 895 |
+
{ 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" },
|
| 896 |
+
]
|
| 897 |
+
|
| 898 |
+
[[package]]
|
| 899 |
+
name = "tomlkit"
|
| 900 |
+
version = "0.14.0"
|
| 901 |
+
source = { registry = "https://pypi.org/simple" }
|
| 902 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" }
|
| 903 |
+
wheels = [
|
| 904 |
+
{ url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" },
|
| 905 |
+
]
|
| 906 |
+
|
| 907 |
+
[[package]]
|
| 908 |
+
name = "torch"
|
| 909 |
+
version = "2.2.2"
|
| 910 |
+
source = { registry = "https://pypi.org/simple" }
|
| 911 |
+
dependencies = [
|
| 912 |
+
{ name = "filelock" },
|
| 913 |
+
{ name = "fsspec" },
|
| 914 |
+
{ name = "jinja2" },
|
| 915 |
+
{ name = "networkx" },
|
| 916 |
+
{ name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 917 |
+
{ name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 918 |
+
{ name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 919 |
+
{ name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 920 |
+
{ name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 921 |
+
{ name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 922 |
+
{ name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 923 |
+
{ name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 924 |
+
{ name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 925 |
+
{ name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 926 |
+
{ name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 927 |
+
{ name = "sympy" },
|
| 928 |
+
{ name = "typing-extensions" },
|
| 929 |
+
]
|
| 930 |
+
wheels = [
|
| 931 |
+
{ url = "https://files.pythonhosted.org/packages/4c/0c/d8f77363a7a3350c96e6c9db4ffb101d1c0487cc0b8cdaae1e4bfb2800ad/torch-2.2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:cf12cdb66c9c940227ad647bc9cf5dba7e8640772ae10dfe7569a0c1e2a28aca", size = 755466713, upload-time = "2024-03-27T21:08:48.868Z" },
|
| 932 |
+
{ url = "https://files.pythonhosted.org/packages/05/9b/e5c0df26435f3d55b6699e1c61f07652b8c8a3ac5058a75d0e991f92c2b0/torch-2.2.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:89ddac2a8c1fb6569b90890955de0c34e1724f87431cacff4c1979b5f769203c", size = 86515814, upload-time = "2024-03-27T21:09:07.247Z" },
|
| 933 |
+
{ url = "https://files.pythonhosted.org/packages/72/ce/beca89dcdcf4323880d3b959ef457a4c61a95483af250e6892fec9174162/torch-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:451331406b760f4b1ab298ddd536486ab3cfb1312614cfe0532133535be60bea", size = 198528804, upload-time = "2024-03-27T21:09:14.691Z" },
|
| 934 |
+
{ url = "https://files.pythonhosted.org/packages/79/78/29dcab24a344ffd9ee9549ec0ab2c7885c13df61cde4c65836ee275efaeb/torch-2.2.2-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:eb4d6e9d3663e26cd27dc3ad266b34445a16b54908e74725adb241aa56987533", size = 150797270, upload-time = "2024-03-27T21:08:29.623Z" },
|
| 935 |
+
{ url = "https://files.pythonhosted.org/packages/4a/0e/e4e033371a7cba9da0db5ccb507a9174e41b9c29189a932d01f2f61ecfc0/torch-2.2.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:bf9558da7d2bf7463390b3b2a61a6a3dbb0b45b161ee1dd5ec640bf579d479fc", size = 59678388, upload-time = "2024-03-27T21:08:35.869Z" },
|
| 936 |
+
]
|
| 937 |
+
|
| 938 |
+
[[package]]
|
| 939 |
+
name = "torchvision"
|
| 940 |
+
version = "0.17.2"
|
| 941 |
+
source = { registry = "https://pypi.org/simple" }
|
| 942 |
+
dependencies = [
|
| 943 |
+
{ name = "numpy" },
|
| 944 |
+
{ name = "pillow" },
|
| 945 |
+
{ name = "torch" },
|
| 946 |
+
]
|
| 947 |
+
wheels = [
|
| 948 |
+
{ url = "https://files.pythonhosted.org/packages/ff/b6/a056fb68cae15e8aec4f854f78d4787086d77efa5468a29d5b744eee2a2b/torchvision-0.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14fd1d4a033c325bdba2d03a69c3450cab6d3a625f85cc375781d9237ca5d04d", size = 1666430, upload-time = "2024-03-27T21:11:29.158Z" },
|
| 949 |
+
{ url = "https://files.pythonhosted.org/packages/58/12/0be3c13b2694ce2d103d259a4c0692884d52b0b445387101d96965d5b060/torchvision-0.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9c3acbebbe379af112b62b535820174277b1f3eed30df264a4e458d58ee4e5b2", size = 1571152, upload-time = "2024-03-27T21:11:27.241Z" },
|
| 950 |
+
{ url = "https://files.pythonhosted.org/packages/1c/e9/830390c704f1471c33faebe964c3ca99113e43ffc3f6653d3188ca04077c/torchvision-0.17.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:77d680adf6ce367166a186d2c7fda3a73807ab9a03b2c31a03fa8812c8c5335b", size = 6915847, upload-time = "2024-03-27T21:11:14.359Z" },
|
| 951 |
+
{ url = "https://files.pythonhosted.org/packages/52/89/9af25236f7bc31fe74f88bde03bbd63c284d0aefa6d19bd92cc37433470c/torchvision-0.17.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f1c9ab3152cfb27f83aca072cac93a3a4c4e4ab0261cf0f2d516b9868a4e96f3", size = 14008843, upload-time = "2024-03-27T21:10:59.268Z" },
|
| 952 |
+
{ url = "https://files.pythonhosted.org/packages/fd/d1/8da7f30169f56764f0ef9ed961a32f300a2d782b6c1bc8b391c3014092f8/torchvision-0.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:3f784381419f3ed3f2ec2aa42fb4aeec5bf4135e298d1631e41c926e6f1a0dff", size = 1165531, upload-time = "2024-03-27T21:11:22.555Z" },
|
| 953 |
+
]
|
| 954 |
+
|
| 955 |
+
[[package]]
|
| 956 |
+
name = "tqdm"
|
| 957 |
+
version = "4.67.3"
|
| 958 |
+
source = { registry = "https://pypi.org/simple" }
|
| 959 |
+
dependencies = [
|
| 960 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 961 |
+
]
|
| 962 |
+
sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
|
| 963 |
+
wheels = [
|
| 964 |
+
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
|
| 965 |
+
]
|
| 966 |
+
|
| 967 |
+
[[package]]
|
| 968 |
+
name = "typer"
|
| 969 |
+
version = "0.24.1"
|
| 970 |
+
source = { registry = "https://pypi.org/simple" }
|
| 971 |
+
dependencies = [
|
| 972 |
+
{ name = "annotated-doc" },
|
| 973 |
+
{ name = "click" },
|
| 974 |
+
{ name = "rich" },
|
| 975 |
+
{ name = "shellingham" },
|
| 976 |
+
]
|
| 977 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" }
|
| 978 |
+
wheels = [
|
| 979 |
+
{ url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" },
|
| 980 |
+
]
|
| 981 |
+
|
| 982 |
+
[[package]]
|
| 983 |
+
name = "typing-extensions"
|
| 984 |
+
version = "4.15.0"
|
| 985 |
+
source = { registry = "https://pypi.org/simple" }
|
| 986 |
+
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" }
|
| 987 |
+
wheels = [
|
| 988 |
+
{ 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" },
|
| 989 |
+
]
|
| 990 |
+
|
| 991 |
+
[[package]]
|
| 992 |
+
name = "typing-inspection"
|
| 993 |
+
version = "0.4.2"
|
| 994 |
+
source = { registry = "https://pypi.org/simple" }
|
| 995 |
+
dependencies = [
|
| 996 |
+
{ name = "typing-extensions" },
|
| 997 |
+
]
|
| 998 |
+
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
| 999 |
+
wheels = [
|
| 1000 |
+
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
| 1001 |
+
]
|
| 1002 |
+
|
| 1003 |
+
[[package]]
|
| 1004 |
+
name = "tzdata"
|
| 1005 |
+
version = "2026.1"
|
| 1006 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1007 |
+
sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" }
|
| 1008 |
+
wheels = [
|
| 1009 |
+
{ url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" },
|
| 1010 |
+
]
|
| 1011 |
+
|
| 1012 |
+
[[package]]
|
| 1013 |
+
name = "urllib3"
|
| 1014 |
+
version = "2.6.3"
|
| 1015 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1016 |
+
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" }
|
| 1017 |
+
wheels = [
|
| 1018 |
+
{ 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" },
|
| 1019 |
+
]
|
| 1020 |
+
|
| 1021 |
+
[[package]]
|
| 1022 |
+
name = "uvicorn"
|
| 1023 |
+
version = "0.44.0"
|
| 1024 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1025 |
+
dependencies = [
|
| 1026 |
+
{ name = "click" },
|
| 1027 |
+
{ name = "h11" },
|
| 1028 |
+
]
|
| 1029 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" }
|
| 1030 |
+
wheels = [
|
| 1031 |
+
{ url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" },
|
| 1032 |
+
]
|