From 2059d9727981e6ca4fa721a0e33915f2867eabe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 7 Nov 2025 15:07:18 -0800 Subject: [PATCH 01/26] feat(resources): Support for Serverless.type QB|LB This activates the support for deploying Load-balancer -based endpoints. We formerly covered Queue-based only. --- src/tetra_rp/__init__.py | 2 ++ src/tetra_rp/core/resources/__init__.py | 2 ++ src/tetra_rp/core/resources/serverless.py | 23 +++++++++++++++++++++++ uv.lock | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/tetra_rp/__init__.py b/src/tetra_rp/__init__.py index 0849a71c..8ae3f3f8 100644 --- a/src/tetra_rp/__init__.py +++ b/src/tetra_rp/__init__.py @@ -21,6 +21,7 @@ PodTemplate, ResourceManager, ServerlessEndpoint, + ServerlessType, runpod, NetworkVolume, ) @@ -38,6 +39,7 @@ "PodTemplate", "ResourceManager", "ServerlessEndpoint", + "ServerlessType", "runpod", "NetworkVolume", ] diff --git a/src/tetra_rp/core/resources/__init__.py b/src/tetra_rp/core/resources/__init__.py index 10aa5bcb..682c190c 100644 --- a/src/tetra_rp/core/resources/__init__.py +++ b/src/tetra_rp/core/resources/__init__.py @@ -9,6 +9,7 @@ ServerlessEndpoint, JobOutput, CudaVersion, + ServerlessType, ) from .serverless_cpu import CpuServerlessEndpoint from .template import PodTemplate @@ -32,6 +33,7 @@ "ResourceManager", "ServerlessResource", "ServerlessEndpoint", + "ServerlessType", "PodTemplate", "NetworkVolume", ] diff --git a/src/tetra_rp/core/resources/serverless.py b/src/tetra_rp/core/resources/serverless.py index 8409f46f..3d5e0a3d 100644 --- a/src/tetra_rp/core/resources/serverless.py +++ b/src/tetra_rp/core/resources/serverless.py @@ -43,6 +43,23 @@ class ServerlessScalerType(Enum): REQUEST_COUNT = "REQUEST_COUNT" +class ServerlessType(Enum): + """ + Serverless endpoint execution model. + + QB (Queue-based): Traditional queue processing with automatic retries. + Requests are placed in queue and processed sequentially. + JSON input/output only. Higher latency but built-in error recovery. + + LB (Load-balancer): Direct HTTP routing to healthy workers. + Supports custom HTTP endpoints and any data format. + Lower latency but no automatic retries. + """ + + QB = "QB" + LB = "LB" + + class CudaVersion(Enum): V11_8 = "11.8" V12_0 = "12.0" @@ -91,6 +108,7 @@ class ServerlessResource(DeployableResource): scalerType: Optional[ServerlessScalerType] = ServerlessScalerType.QUEUE_DELAY scalerValue: Optional[int] = 4 templateId: Optional[str] = None + type: Optional[ServerlessType] = ServerlessType.QB workersMax: Optional[int] = 3 workersMin: Optional[int] = 0 workersPFBTarget: Optional[int] = None @@ -132,6 +150,11 @@ def serialize_scaler_type( """Convert ServerlessScalerType enum to string.""" return value.value if value is not None else None + @field_serializer("type") + def serialize_type(self, value: Optional[ServerlessType]) -> Optional[str]: + """Convert ServerlessType enum to string.""" + return value.value if value is not None else None + @field_validator("gpus") @classmethod def validate_gpus(cls, value: List[GpuGroup]) -> List[GpuGroup]: diff --git a/uv.lock b/uv.lock index 3d7040dd..7f70b873 100644 --- a/uv.lock +++ b/uv.lock @@ -2866,7 +2866,7 @@ wheels = [ [[package]] name = "tetra-rp" -version = "0.13.0" +version = "0.14.0" source = { editable = "." } dependencies = [ { name = "cloudpickle" }, From f21bc82d8c05a2c470f9e3fd72db5fc073ea3f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sun, 9 Nov 2025 21:31:24 -0800 Subject: [PATCH 02/26] feat(cli): add flash init project skeleton template - Add comprehensive README.md with complete documentation - Add example GPU worker with @remote decorator - Add example CPU interface with todo list functions - Add FastAPI main.py entry point - Add .env template with commented values - Add .flashignore and .gitignore - Add requirements.txt with dependencies - Add skeleton.py with conflict detection and template creation --- src/tetra_rp/cli/utils/skeleton.py | 363 ++--------- src/tetra_rp/cli/utils/skeleton_template/.env | 3 + .../cli/utils/skeleton_template/.flashignore | 40 ++ .../cli/utils/skeleton_template/.gitignore | 44 ++ .../cli/utils/skeleton_template/README.md | 585 ++++++++++++++++++ .../cli/utils/skeleton_template/main.py | 51 ++ .../utils/skeleton_template/requirements.txt | 1 + .../workers/example/__init__.py | 47 ++ .../workers/example/endpoint.py | 56 ++ .../workers/interface/__init__.py | 54 ++ .../workers/interface/endpoint.py | 28 + 11 files changed, 959 insertions(+), 313 deletions(-) create mode 100644 src/tetra_rp/cli/utils/skeleton_template/.env create mode 100644 src/tetra_rp/cli/utils/skeleton_template/.flashignore create mode 100644 src/tetra_rp/cli/utils/skeleton_template/.gitignore create mode 100644 src/tetra_rp/cli/utils/skeleton_template/README.md create mode 100644 src/tetra_rp/cli/utils/skeleton_template/main.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/requirements.txt create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py diff --git a/src/tetra_rp/cli/utils/skeleton.py b/src/tetra_rp/cli/utils/skeleton.py index 30b58046..f3e9c32c 100644 --- a/src/tetra_rp/cli/utils/skeleton.py +++ b/src/tetra_rp/cli/utils/skeleton.py @@ -3,310 +3,40 @@ from pathlib import Path from typing import List -# Template files content -MAIN_PY_TEMPLATE = '''""" -Flash Application - Flash Server -This is the main entry point for your Flash application. -It runs a FastAPI server that coordinates GPU workers. -""" - -import asyncio -from fastapi import FastAPI -from pydantic import BaseModel -from dotenv import load_dotenv - -from workers import ExampleWorker - -# Load environment variables -load_dotenv() - -# Create FastAPI app -app = FastAPI(title="Flash Application") - - -class ProcessRequest(BaseModel): - """Request model for processing endpoint.""" - data: str - - -@app.get("/") -def home(): - """Health check endpoint.""" - return {"status": "ok", "message": "Flash application running"} - - -@app.get("/health") -def health(): - """Health check endpoint.""" - return {"healthy": True} - - -@app.post("/process") -async def process(request: ProcessRequest): - """ - Process data using GPU worker. - - Example request: - { - "data": "test input" - } +def detect_file_conflicts(project_dir: Path) -> List[Path]: """ - # Instantiate worker - worker = ExampleWorker() - - # Call worker's process method - result = await worker.process({"input": request.data}) - - return result - - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="localhost", port=8888) -''' - -WORKER_EXAMPLE_PY_TEMPLATE = '''""" -Example GPU Worker - -This is an example of a GPU worker class that can be deployed -to RunPod serverless endpoints. -""" - -from tetra_rp import remote, LiveServerless - - -# Configure GPU resource -config = LiveServerless( - name="example_worker", - workersMax=3, -) - - -@remote(config) -class ExampleWorker: - """Example GPU worker for processing tasks.""" - - def __init__(self): - """Initialize the worker.""" - print("ExampleWorker initialized") - - def process(self, input_data: dict) -> dict: - """ - Process input data and return result. - - Args: - input_data: Dictionary with input parameters - - Returns: - Dictionary with processing results - """ - # Your GPU processing logic here - result = { - "status": "success", - "input": input_data, - "output": f"Processed: {input_data}" - } - - return result -''' - -WORKERS_INIT_PY_TEMPLATE = '''"""GPU Workers package.""" - -from .example_worker import ExampleWorker - -__all__ = ["ExampleWorker"] -''' - -ENV_EXAMPLE_TEMPLATE = """# RunPod API Configuration -RUNPOD_API_KEY=your_runpod_api_key_here - -# Development settings -DEBUG=false -LOG_LEVEL=INFO -""" + Detect files that would be overwritten when creating project skeleton. -REQUIREMENTS_TXT_TEMPLATE = """# Core dependencies for Flash -tetra-rp>=0.12.0 -fastapi>=0.104.0 -uvicorn[standard]>=0.24.0 -python-dotenv>=1.0.0 -pydantic>=2.0.0 -aiohttp>=3.9.0 -""" - -GITIGNORE_TEMPLATE = """# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -env/ -venv/ -ENV/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# Environment -.env -.venv - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# Tetra -.tetra/ - -# OS -.DS_Store -Thumbs.db -""" - -FLASHIGNORE_TEMPLATE = """# Flash build ignores -# Similar to .gitignore but specifically for flash build command - -# Version control -.git/ -.gitignore - -# Python artifacts -__pycache__/ -*.pyc -*.pyo -*.pyd -.Python - -# Virtual environments -.env -.venv/ -env/ -venv/ -ENV/ - -# Build artifacts -.build/ -.tetra/ -*.tar.gz -*.egg-info/ -dist/ -build/ - -# IDE files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS files -.DS_Store -Thumbs.db - -# Test files -tests/ -test_*.py -*_test.py - -# Documentation (optional - comment out to include) -# docs/ -# *.md - -# Logs -*.log -""" - -README_TEMPLATE = """# {{project_name}} - -Flash application with Flash Server and GPU workers. - -## Setup - -1. Activate the conda environment: -```bash -conda activate {{project_name}} -``` - -2. Configure your RunPod API key: -```bash -cp .env.example .env -# Edit .env and add your RUNPOD_API_KEY -``` - -3. Run the development server: -```bash -flash run -``` - -## Project Structure - -``` -{{project_name}}/ -├── main.py # Flash Server (FastAPI) -├── workers/ # GPU workers -│ ├── __init__.py -│ └── example_worker.py -├── .env.example # Environment variables template -├── requirements.txt # Python dependencies -└── README.md # This file -``` - -## Development - -The Flash Server runs on `localhost:8888` and coordinates GPU workers. - -### Adding New Workers - -1. Create a new file in `workers/` directory -2. Define a class with `@remote` decorator -3. Import it in `workers/__init__.py` -4. Use it in `main.py` - -Example: -```python -from tetra_rp import remote, LiveServerless + Args: + project_dir: Project directory path to check -config = LiveServerless(name="my_worker", workersMax=3) + Returns: + List of file paths that already exist and would be overwritten + """ + conflicts = [] -@remote(config) -class MyWorker: - def process(self, data): - return {{"result": f"Processed: {{data}}"}} -``` + # Get template directory path + template_dir = Path(__file__).parent / "skeleton_template" -## Deployment + if not template_dir.exists(): + return conflicts -Deploy to production: -```bash -flash deploy send production -``` + # Check each template file against target directory + for template_file in template_dir.rglob("*"): + if template_file.is_file(): + relative_path = template_file.relative_to(template_dir) + target_file = project_dir / relative_path -## Documentation + if target_file.exists(): + conflicts.append(relative_path) -- [Flash CLI Docs](./docs/) -- [Tetra Documentation](https://docs.tetra.dev) -""" + return conflicts def create_project_skeleton(project_dir: Path, force: bool = False) -> List[str]: """ - Create Flash project skeleton. + Create Flash project skeleton from template directory. Args: project_dir: Project directory path @@ -317,31 +47,38 @@ def create_project_skeleton(project_dir: Path, force: bool = False) -> List[str] """ created_files = [] - # Define project structure - files_to_create = { - "main.py": MAIN_PY_TEMPLATE, - "workers/__init__.py": WORKERS_INIT_PY_TEMPLATE, - "workers/example_worker.py": WORKER_EXAMPLE_PY_TEMPLATE, - ".env.example": ENV_EXAMPLE_TEMPLATE, - "requirements.txt": REQUIREMENTS_TXT_TEMPLATE, - ".gitignore": GITIGNORE_TEMPLATE, - ".flashignore": FLASHIGNORE_TEMPLATE, - "README.md": README_TEMPLATE.format(project_name=project_dir.name), - } + # Get template directory path + template_dir = Path(__file__).parent / "skeleton_template" + + if not template_dir.exists(): + raise FileNotFoundError(f"Template directory not found: {template_dir}") + + # Create project directory + project_dir.mkdir(parents=True, exist_ok=True) + + # Walk through template directory and copy files + for template_file in template_dir.rglob("*"): + if template_file.is_file(): + # Get relative path from template dir + relative_path = template_file.relative_to(template_dir) + target_file = project_dir / relative_path + + # Skip existing files unless force is True + if target_file.exists() and not force: + continue - # Create files - for relative_path, content in files_to_create.items(): - file_path = project_dir / relative_path + # Create parent directories if needed + target_file.parent.mkdir(parents=True, exist_ok=True) - # Create parent directories if needed - file_path.parent.mkdir(parents=True, exist_ok=True) + # Read content and handle template substitutions + content = template_file.read_text() - # Skip existing files unless force is True - if file_path.exists() and not force: - continue + # Replace {{project_name}} placeholder + if "{{project_name}}" in content: + content = content.replace("{{project_name}}", project_dir.name) - # Write file - file_path.write_text(content) - created_files.append(str(file_path.relative_to(project_dir))) + # Write file + target_file.write_text(content) + created_files.append(str(relative_path)) return created_files diff --git a/src/tetra_rp/cli/utils/skeleton_template/.env b/src/tetra_rp/cli/utils/skeleton_template/.env new file mode 100644 index 00000000..4c6ec357 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/.env @@ -0,0 +1,3 @@ +# RUNPOD_API_KEY=your_api_key_here +# PORT=80 +# LOG_LEVEL=INFO diff --git a/src/tetra_rp/cli/utils/skeleton_template/.flashignore b/src/tetra_rp/cli/utils/skeleton_template/.flashignore new file mode 100644 index 00000000..ea5988c4 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/.flashignore @@ -0,0 +1,40 @@ +# Flash Build Ignore Patterns + +# Python cache +__pycache__/ +*.pyc + +# Virtual environments +venv/ +.venv/ +env/ + +# IDE +.vscode/ +.idea/ + +# Environment files +.env +.env.local + +# Git +.git/ +.gitignore + +# Build artifacts +dist/ +build/ +*.egg-info/ + +# Flash resources +.tetra_resources.pkl + +# Tests +tests/ +test_*.py +*_test.py + +# Documentation +docs/ +*.md +!README.md diff --git a/src/tetra_rp/cli/utils/skeleton_template/.gitignore b/src/tetra_rp/cli/utils/skeleton_template/.gitignore new file mode 100644 index 00000000..9e84778e --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/.gitignore @@ -0,0 +1,44 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +.venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Environment +.env +.env.local + +# Flash +.tetra_resources.pkl +dist/ + +# OS +.DS_Store +Thumbs.db diff --git a/src/tetra_rp/cli/utils/skeleton_template/README.md b/src/tetra_rp/cli/utils/skeleton_template/README.md new file mode 100644 index 00000000..50c4a811 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/README.md @@ -0,0 +1,585 @@ +# {{project_name}} + +A Flash application for distributed inference and serving on Runpod's serverless infrastructure. + +## Quick Start (60 seconds) + +### 1. Install Dependencies + +**Using pip:** +```bash +pip install -r requirements.txt +``` + +**Using uv (faster):** +```bash +uv pip install -r requirements.txt +``` + +**Using conda (if environment was created):** +```bash +conda activate {{project_name}} +pip install -r requirements.txt +``` + +### 2. Configure Environment + +Add your Runpod API key to `.env`: + +```bash +RUNPOD_API_KEY=your_api_key_here +``` + +Get your API key from [Runpod Settings](https://www.runpod.io/console/user/settings). + +### 3. Run Locally + +```bash +flash run +``` + +The server starts at **http://localhost:8000** + +### 4. Test the API + +```bash +# Health check +curl http://localhost:8000/ping + +# Test GPU worker endpoint +curl -X POST http://localhost:8000/example/process \ + -H "Content-Type: application/json" \ + -d '{"task": "example"}' + +# Test CPU interface endpoint +curl http://localhost:8000/interface/todo/list +``` + +Test using the OpenAPI (Swagger) UI at **http://localhost:8000/docs** + +## Understanding the Architecture + +Flash applications consist of three components: + +### 1. **Flash Server** (`main.py`) +- FastAPI application that coordinates all workers +- Runs locally during development +- Serves as the entry point for all API requests + +### 2. **GPU Workers** (`workers/example/`) +- **Queue-based handlers** for GPU-intensive tasks +- Each request is queued and processed by available workers +- Auto-scales based on queue depth +- Best for: ML inference, image processing, video encoding + +Example configuration: +```python +from tetra_rp import remote, LiveServerless, GpuGroup + +config = LiveServerless( + name="example_worker", + gpus=[GpuGroup.ADA_24], # RTX 4090 + workersMin=0, # Scale to zero when idle + workersMax=3 # Max concurrent workers +) + +@remote(config) +class ExampleWorker: + def process(self, input_data: dict) -> dict: + # Your GPU processing logic here + return {"result": "processed"} +``` + +### 3. **CPU Interfaces** (`workers/interface/`) +- **Load-balancer handlers** for CPU-based API endpoints +- Direct HTTP requests, no queue +- Lower latency, better for APIs +- Best for: REST APIs, data transformations, business logic + +Example configuration: +```python +from tetra_rp import remote, CpuLiveServerless, CpuInstanceType + +config = CpuLiveServerless( + name="interface_worker", + instanceIds=[CpuInstanceType.CPU5G_2_8], # 2 vCPU, 8GB RAM + workersMin=0, + workersMax=5 +) + +@remote(config) +def todo_get_list(): + return {"items": [...]} +``` + +## Project Structure + +``` +{{project_name}}/ +├── main.py # FastAPI application entry point +├── workers/ +│ ├── example/ # GPU worker (queue-based) +│ │ ├── __init__.py # FastAPI router +│ │ └── endpoint.py # Worker implementation with @remote decorator +│ └── interface/ # CPU interface (load-balancer) +│ ├── __init__.py # FastAPI router +│ └── endpoint.py # Functions with @remote decorator +├── .env # Environment variables (RUNPOD_API_KEY) +├── .flashignore # Files to exclude from build +├── requirements.txt # Python dependencies +└── README.md # This file +``` + +## Deployment + +### Build and Deploy + +```bash +# Build the application (discovers all handlers) +flash build + +# Create deployment environment +flash deploy new production + +# Deploy to Runpod serverless +flash deploy send production + +# Check deployment status and get endpoint URLs +flash deploy report production +``` + +### What Happens During Build? + +1. **Handler Discovery**: Flash scans your code for `@remote` decorators +2. **Classification**: Determines if handler is queue-based (GPU) or load-balancer (CPU) +3. **Resource Configuration**: Creates endpoint configuration for each handler +4. **Packaging**: Bundles code and dependencies for deployment + +### Monitor Deployments + +```bash +# View all deployed resources +flash report + +# Check specific environment +flash deploy report production + +# View logs in Runpod Console +# Links are shown in the report output +``` + +## API Reference + +### Health Endpoints + +#### `GET /` +Root endpoint returning application status. + +**Response:** +```json +{ + "status": "ok", + "message": "Flash application running" +} +``` + +#### `GET /ping` +Health check endpoint. + +**Response:** +```json +{ + "healthy": true +} +``` + +### Example GPU Worker + +#### `POST /example/process` +Queue-based GPU processing endpoint. + +**Request:** +```json +{ + "input_data": {"task": "example"} +} +``` + +**Response:** +```json +{ + "status": "success", + "input": {"task": "example"}, + "output": "Processed: {'task': 'example'}" +} +``` + +### Example CPU Interface + +#### `GET /interface/todo/list` +Get list of todo items. + +**Response:** +```json +{ + "item_1": "Make a list", + "item_2": "Show a list", + "item_3": "Delete a list" +} +``` + +#### `POST /interface/todo/add` +Add a new todo item. + +**Request:** +```json +{ + "item": "New task" +} +``` + +**Response:** +```json +"added item: New task" +``` + +#### `DELETE /interface/todo/delete` +Delete a todo item. + +**Request:** +```json +{ + "item": "Old task" +} +``` + +**Response:** +```json +"deleted item: Old task" +``` + +## Configuration + +### Environment Variables (`.env`) + +```bash +# Required: Runpod API Key +RUNPOD_API_KEY=your_api_key_here + +# Optional: Server port (default: 80) +PORT=8000 + +# Optional: Log level (default: INFO) +LOG_LEVEL=DEBUG +``` + +### GPU Worker Configuration + +```python +from tetra_rp import LiveServerless, GpuGroup + +config = LiveServerless( + name="my_worker", + gpus=[GpuGroup.RTX4090], # GPU type + gpuCount=1, # GPUs per worker + workersMin=0, # Minimum workers (0 = scale to zero) + workersMax=3, # Maximum workers + idleTimeout=5, # Minutes before scaling down + env={"MODEL_PATH": "/models"} # Environment variables +) +``` + +**Available GPU Types:** +- `GpuGroup.RTX4090` - RTX 4090 (24GB) +- `GpuGroup.RTX3090` - RTX 3090 (24GB) +- `GpuGroup.A100_80GB` - A100 (80GB) +- `GpuGroup.H100` - H100 (80GB) + +### CPU Interface Configuration + +```python +from tetra_rp import CpuLiveServerless, CpuInstanceType + +config = CpuLiveServerless( + name="my_interface", + instanceIds=[CpuInstanceType.CPU5G_2_8], # 2 vCPU, 8GB RAM + workersMin=0, + workersMax=5, + idleTimeout=5, + env={"API_TIMEOUT": "30"} +) +``` + +**Available CPU Types:** +- `CPU3G_1_2` - 1 vCPU, 2GB RAM (General Purpose, Gen 3) +- `CPU3G_2_4` - 2 vCPU, 4GB RAM +- `CPU3G_4_8` - 4 vCPU, 8GB RAM +- `CPU5G_2_8` - 2 vCPU, 8GB RAM (General Purpose, Gen 5) +- `CPU5G_4_16` - 4 vCPU, 16GB RAM +- `CPU5C_4_16` - 4 vCPU, 16GB RAM (Compute Optimized, Gen 5) + +## Adding New Workers + +### Add a GPU Worker + +1. Create new directory in `workers/`: +```bash +mkdir -p workers/my_gpu_worker +``` + +2. Create `endpoint.py`: +```python +from tetra_rp import remote, LiveServerless + +config = LiveServerless(name="my_gpu_worker") + +@remote(config) +class MyWorker: + def __init__(self): + # Load models, initialize resources + pass + + def process(self, data: dict) -> dict: + # Your GPU processing logic + return {"result": "processed"} +``` + +3. Create `__init__.py`: +```python +from fastapi import APIRouter +from .endpoint import MyWorker + +router = APIRouter() + +@router.post("/process") +async def process(data: dict): + worker = MyWorker() + return await worker.process(data) +``` + +4. Register in `main.py`: +```python +from workers.my_gpu_worker import router as my_gpu_router +app.include_router(my_gpu_router, prefix="/my_gpu_worker", tags=["workers"]) +``` + +### Add a CPU Interface + +1. Create new directory in `workers/`: +```bash +mkdir -p workers/my_api +``` + +2. Create `endpoint.py`: +```python +from tetra_rp import remote, CpuLiveServerless + +config = CpuLiveServerless(name="my_api") + +@remote(config) +def get_data(id: str): + return {"id": id, "data": "..."} + +@remote(config) +def process_data(data: dict): + return {"processed": data} +``` + +3. Create `__init__.py`: +```python +from fastapi import APIRouter +from .endpoint import get_data, process_data + +router = APIRouter() + +@router.get("/data/{id}") +async def get_data_endpoint(id: str): + return await get_data(id) + +@router.post("/process") +async def process_endpoint(data: dict): + return await process_data(data) +``` + +4. Register in `main.py`: +```python +from workers.my_api import router as my_api_router +app.include_router(my_api_router, prefix="/my_api", tags=["api"]) +``` + +## Common Patterns + +### Pattern 1: ML Model Inference (GPU) + +```python +from tetra_rp import remote, LiveServerless + +config = LiveServerless( + name="model_inference", + gpus=["RTX4090"], + workersMax=3 +) + +@remote(config) +class ModelInference: + def __init__(self): + # Imports must be inside the function/class + import torch + from transformers import AutoModel + + self.model = AutoModel.from_pretrained("model-name") + self.model.cuda() + + def predict(self, text: str) -> dict: + import torch + + with torch.no_grad(): + result = self.model(text) + + return {"prediction": result} +``` + +### Pattern 2: API Gateway (CPU) + +```python +from tetra_rp import remote, CpuLiveServerless + +config = CpuLiveServerless( + name="api_gateway", + instanceIds=["cpu5c-4-16"], # Compute optimized + workersMax=10 +) + +@remote(config) +def fetch_user(user_id: str): + # Call external APIs, databases, etc. + import requests + response = requests.get(f"https://api.example.com/users/{user_id}") + return response.json() + +@remote(config) +def process_webhook(data: dict): + # Process webhook events + return {"status": "processed", "data": data} +``` + +### Pattern 3: Batch Processing (GPU) + +```python +from tetra_rp import remote, LiveServerless + +config = LiveServerless( + name="batch_processor", + gpus=["A100_80GB"], + gpuCount=2, # Multi-GPU + workersMax=5 +) + +@remote(config) +class BatchProcessor: + def process_batch(self, items: list[dict]) -> list[dict]: + import torch + + results = [] + for item in items: + # Process each item + result = self.process_single(item) + results.append(result) + + return results +``` + +## Troubleshooting + +### Local Development + +**Issue: `ModuleNotFoundError` when running `flash run`** +```bash +# Solution: Install dependencies +pip install -r requirements.txt +``` + +**Issue: Port already in use** +```bash +# Solution: Change port in .env +PORT=8001 + +# Or kill the process using port 8000 +lsof -ti:8000 | xargs kill -9 +``` + +**Issue: API key not found** +```bash +# Solution: Make sure .env file exists with RUNPOD_API_KEY +cp .env.example .env +# Edit .env and add your API key +``` + +### Deployment Issues + +**Issue: Build fails with import errors** +```bash +# Solution: Add missing dependencies to requirements.txt +# Then rebuild: +flash build +``` + +**Issue: Handler not discovered during build** +```bash +# Solution: Make sure you're using @remote decorator +# And the function/class is imported in __init__.py +``` + +**Issue: Deployment stuck or failing** +```bash +# Solution: Check logs in Runpod console +# Get console link from: +flash deploy report production +``` + +**Issue: Worker not scaling** +```bash +# Solution: Check worker configuration +# Make sure workersMax > 0 +# Check idleTimeout isn't too aggressive +``` + +### Performance Issues + +**Issue: High latency on GPU workers** +```bash +# Queue-based handlers have higher latency +# Solution: Consider using CPU interface if no GPU needed +# Or increase workersMin to keep workers warm +``` + +**Issue: Cold starts are slow** +```bash +# Solution: Set workersMin > 0 to keep workers running +# Or optimize model loading in __init__ +``` + +## Available Commands + +| Command | Description | +|---------|-------------| +| `flash run` | Start local development server | +| `flash build` | Build application for deployment | +| `flash deploy new ` | Create deployment environment | +| `flash deploy send ` | Deploy to environment | +| `flash deploy report ` | View environment status and URLs | +| `flash deploy list` | List all environments | +| `flash report` | Show all deployed resources | +| `flash clean` | Remove all deployed resources | + +## Learn More + +- **Runpod Console**: [https://www.runpod.io/console](https://www.runpod.io/console) +- **Runpod Documentation**: [https://docs.runpod.io](https://docs.runpod.io) +- **Get API Key**: [https://www.runpod.io/console/user/settings](https://www.runpod.io/console/user/settings) + +## Support + +- **Issues**: Report bugs at [GitHub Issues](https://github.com/runpod/flash/issues) +- **Discord**: Join the [Runpod Discord](https://discord.gg/runpod) +- **Email**: support@runpod.io diff --git a/src/tetra_rp/cli/utils/skeleton_template/main.py b/src/tetra_rp/cli/utils/skeleton_template/main.py new file mode 100644 index 00000000..4a8cde8a --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/main.py @@ -0,0 +1,51 @@ +""" +Flash Application - Flash Server + +This is the main entry point for your Flash application. +It runs a FastAPI server that coordinates with GPU and CPU workers. + +Each APIRouter is discovered as a handler during build and can be deployed +as a separate serverless endpoint (queue-based or load-balancer based). +""" +import os +import logging +from fastapi import FastAPI + +from workers.example import example_worker_router +from workers.interface import example_interface_router + + +log = logging.getLogger(__name__) + + +# Create FastAPI app +app = FastAPI(title="Flash Application") + + +@app.get("/") +def home(): + """Root endpoint.""" + return {"status": "ok", "message": "Flash application running"} + + +@app.get("/ping") +def ping(): + """Health check endpoint.""" + return {"healthy": True} + + +# Include worker routers +# Each router is discovered as a handler during build +app.include_router(example_worker_router, prefix="/example", tags=["workers"]) +app.include_router(example_interface_router, prefix="/interface", tags=["workers"]) + + +# Run the app when the script is executed +if __name__ == "__main__": + import uvicorn + + port = int(os.getenv("PORT", 80)) + logger.info(f"Starting Flash server on port {port}") + + # Start the server + uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/src/tetra_rp/cli/utils/skeleton_template/requirements.txt b/src/tetra_rp/cli/utils/skeleton_template/requirements.txt new file mode 100644 index 00000000..1666687a --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/requirements.txt @@ -0,0 +1 @@ +tetra_rp>=0.14.0 diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py new file mode 100644 index 00000000..067f8b91 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py @@ -0,0 +1,47 @@ +""" +Example GPU Worker Router + +This worker demonstrates the APIRouter pattern for Flash applications. + +Each router is discovered as a handler during build: +- Queue-based: Single POST endpoint (async processing) +- Load-balancer: Multiple endpoints or any GET (synchronous API) + +This router will be deployed as a Queue-based endpoint. +""" + +from fastapi import APIRouter +from pydantic import BaseModel +from .endpoint import ExampleWorker + + +# Create APIRouter for this worker +# This router will be discovered as a handler during build +example_worker_router = APIRouter() + + +class ProcessRequest(BaseModel): + """Request model for processing endpoint.""" + data: str + + +class ProcessResponse(BaseModel): + """Response model for processing endpoint.""" + status: str + input: dict + output: str + + +# Define routes for this worker +# Single POST endpoint → Queue-based handler (async processing) +@example_worker_router.post("/", response_model=ProcessResponse) +async def process_data(request: ProcessRequest): + """ + Process data using GPU worker. + + This is a queue-based handler (single POST endpoint). + Requests are queued and processed asynchronously. + """ + worker = ExampleWorker() + result = await worker.process({"input": request.data}) + return result diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py new file mode 100644 index 00000000..ed04134b --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py @@ -0,0 +1,56 @@ +""" +Example GPU Worker +""" + +from tetra_rp import remote, LiveServerless, GpuGroup + + +# Configure GPU resource for this worker +config = LiveServerless( + name="example_worker", + gpus=[GpuGroup.ADA_24], # RTX 4090 +) + + +@remote(config) +class ExampleWorker: + """Example GPU worker for processing tasks.""" + + def __init__(self): + """Initialize the worker.""" + print("ExampleWorker initialized") + + def process(self, input_data: dict) -> dict: + """ + Process input data and return result. + + Args: + input_data: Dictionary with input parameters + + Returns: + Dictionary with processing results + """ + # Your GPU processing logic here + result = { + "status": "success", + "input": input_data, + "output": f"Processed: {input_data}" + } + + return result + + +# Run this script by itself to test in Live Serverless +if __name__ == "__main__": + import asyncio + + worker = ExampleWorker() + payload = { + "input": { + "foo": "bar" + } + } + + result = asyncio.run(worker.process(payload)) + + print(result) diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py new file mode 100644 index 00000000..5c20e049 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py @@ -0,0 +1,54 @@ +""" +Example CPU Interface Router + +This router demonstrates CPU-based API endpoints using @remote decorated functions. + +Each @remote function is deployed as a separate serverless CPU endpoint. +The router provides RESTful access to these functions. + +This router will be deployed as a Load-balancer endpoint (multiple GET/POST). +""" + +from fastapi import APIRouter +from pydantic import BaseModel + +# Import remote functions +from .endpoint import todo_get_list, todo_add_item, todo_delete_item + + +# Create APIRouter for this interface +# This router will be discovered as a handler during build +example_interface_router = APIRouter() + + +class TodoItem(BaseModel): + """Request model for todo item operations.""" + data: str + + +# Define routes for this interface +# Multiple endpoints → Load-balancer handler (synchronous API) +@example_interface_router.get("/list") +async def get_todo_list(): + """ + Get the todo list. + + This is a load-balancer handler (multiple endpoints). + Requests are routed directly to available workers. + """ + result = await todo_get_list() + return result + + +@example_interface_router.post("/add") +async def add_todo_item(item: TodoItem): + """Add a new todo item.""" + result = await todo_add_item(item.data) + return {"result": result} + + +@example_interface_router.post("/delete") +async def delete_todo_item(item: TodoItem): + """Delete a todo item.""" + result = await todo_delete_item(item.data) + return {"result": result} diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py new file mode 100644 index 00000000..ce2d2a37 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py @@ -0,0 +1,28 @@ +""" +Example CPU Interface +""" + +from tetra_rp import remote, CpuLiveServerless + + +# Configure CPU resource for this interface +config = CpuLiveServerless(name="interface_worker") + + +@remote(config) +def todo_get_list(): + return { + "item_1": "Make a list", + "item_2": "Show a list", + "item_3": "Delete a list", + } + + +@remote(config) +def todo_add_item(item): + return f"added item: {item}" + + +@remote(config) +def todo_delete_item(item): + return f"deleted item: {item}" From b9db5fe9a4123420ddd2c8a6a794235e872f7548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sun, 9 Nov 2025 21:40:40 -0800 Subject: [PATCH 03/26] feat(cli): add flash init in-place with conflict warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables flash init to initialize projects in the current directory with interactive overwrite warnings. Usage: - `flash init` → Initialize in current directory - `flash init .` → Initialize in current directory - `flash init ` → Create new directory (existing behavior) Features: - Automatic conflict detection for existing files - Interactive confirmation prompt with file list - `--force` flag to skip prompts - Smart project naming (uses current dir name when in-place) - Conditional "Next steps" (omits cd when in current dir) - Separate success messages for each mode --- src/tetra_rp/cli/commands/init.py | 132 +++++++++++++++++++++--------- src/tetra_rp/cli/main.py | 110 ++++++++++++++++++++----- 2 files changed, 182 insertions(+), 60 deletions(-) diff --git a/src/tetra_rp/cli/commands/init.py b/src/tetra_rp/cli/commands/init.py index ac78959c..0bb10fea 100644 --- a/src/tetra_rp/cli/commands/init.py +++ b/src/tetra_rp/cli/commands/init.py @@ -7,7 +7,7 @@ from rich.panel import Panel from rich.table import Table -from ..utils.skeleton import create_project_skeleton +from ..utils.skeleton import create_project_skeleton, detect_file_conflicts from ..utils.conda import ( check_conda_available, create_conda_environment, @@ -29,28 +29,63 @@ def init_command( - project_name: str = typer.Argument(..., help="Project name"), - no_env: bool = typer.Option( - False, "--no-env", help="Skip conda environment creation" - ), - force: bool = typer.Option( - False, "--force", "-f", help="Overwrite existing directory" - ), + project_name: str = None, + no_env: bool = False, + force: bool = False, ): """Create new Flash project with Flash Server and GPU workers.""" - # Create project directory - project_dir = Path(project_name) - - if project_dir.exists() and not force: - console.print(f"Directory '{project_name}' already exists") - console.print("Use --force to overwrite") - raise typer.Exit(1) - - # Create project directory - project_dir.mkdir(parents=True, exist_ok=True) + # Determine target directory and initialization mode + if project_name is None or project_name == ".": + # Initialize in current directory + project_dir = Path.cwd() + is_current_dir = True + # Use current directory name as project name + actual_project_name = project_dir.name + else: + # Create new directory + project_dir = Path(project_name) + is_current_dir = False + actual_project_name = project_name + + # Handle directory existence and conflicts + if is_current_dir: + # Initializing in current directory - check for conflicts + conflicts = detect_file_conflicts(project_dir) + + if conflicts and not force: + # Show warning and prompt user + console.print( + Panel( + "[yellow]Warning: The following files will be overwritten:[/yellow]\n\n" + + "\n".join(f" • {conflict}" for conflict in conflicts), + title="File Conflicts Detected", + expand=False, + ) + ) - with console.status(f"Creating Flash project '{project_name}'..."): + # Prompt user for confirmation + proceed = typer.confirm("Continue and overwrite these files?", default=False) + if not proceed: + console.print("[yellow]Initialization aborted.[/yellow]") + raise typer.Exit(0) + else: + # Creating new directory - check if it exists + if project_dir.exists() and not force: + console.print(f"[red]Directory '{project_name}' already exists[/red]") + console.print("Use --force to overwrite") + raise typer.Exit(1) + + # Create project directory + project_dir.mkdir(parents=True, exist_ok=True) + + # Create project skeleton + status_msg = ( + f"Initializing Flash project in current directory..." + if is_current_dir + else f"Creating Flash project '{project_name}'..." + ) + with console.status(status_msg): create_project_skeleton(project_dir, force) # Create conda environment if requested @@ -65,15 +100,15 @@ def init_command( ) else: # Check if environment already exists - if environment_exists(project_name): + if environment_exists(actual_project_name): console.print( - f"[yellow]Conda environment '{project_name}' already exists. Skipping creation.[/yellow]" + f"[yellow]Conda environment '{actual_project_name}' already exists. Skipping creation.[/yellow]" ) env_created = True else: # Create conda environment - with console.status(f"Creating conda environment '{project_name}'..."): - success, message = create_conda_environment(project_name) + with console.status(f"Creating conda environment '{actual_project_name}'..."): + success, message = create_conda_environment(actual_project_name) if not success: console.print(f"[yellow]Warning: {message}[/yellow]") @@ -86,7 +121,7 @@ def init_command( # Install required packages with console.status("Installing dependencies..."): success, message = install_packages_in_env( - project_name, REQUIRED_PACKAGES, use_pip=True + actual_project_name, REQUIRED_PACKAGES, use_pip=True ) if not success: @@ -96,24 +131,34 @@ def init_command( ) # Success output - panel_content = ( - f"Flash project '[bold]{project_name}[/bold]' created successfully!\n\n" - ) - panel_content += "Project structure:\n" - panel_content += f" {project_name}/\n" + if is_current_dir: + panel_content = ( + f"Flash project '[bold]{actual_project_name}[/bold]' initialized in current directory!\n\n" + ) + panel_content += "Project structure:\n" + panel_content += " ./\n" + else: + panel_content = ( + f"Flash project '[bold]{actual_project_name}[/bold]' created successfully!\n\n" + ) + panel_content += "Project structure:\n" + panel_content += f" {actual_project_name}/\n" + panel_content += " ├── main.py # Flash Server (FastAPI)\n" panel_content += " ├── workers/ # GPU workers\n" - panel_content += " │ └── example_worker.py\n" - panel_content += " ├── .env.example\n" + panel_content += " │ └── example/\n" + panel_content += " │ └── interface/\n" + panel_content += " ├── .env\n" panel_content += " ├── requirements.txt\n" panel_content += " └── README.md\n" if env_created: panel_content += ( - f"\nConda environment '[bold]{project_name}[/bold]' created and configured" + f"\nConda environment '[bold]{actual_project_name}[/bold]' created and configured" ) - console.print(Panel(panel_content, title="Project Created", expand=False)) + title = "Project Initialized" if is_current_dir else "Project Created" + console.print(Panel(panel_content, title=title, expand=False)) # Next steps console.print("\n[bold]Next steps:[/bold]") @@ -121,15 +166,22 @@ def init_command( steps_table.add_column("Step", style="bold cyan") steps_table.add_column("Description") - steps_table.add_row("1.", f"cd {project_name}") + step_num = 1 + if not is_current_dir: + steps_table.add_row(f"{step_num}.", f"cd {actual_project_name}") + step_num += 1 if env_created: - steps_table.add_row("2.", f"{get_activation_command(project_name)}") - steps_table.add_row("3.", "cp .env.example .env # Add your RUNPOD_API_KEY") - steps_table.add_row("4.", "flash run") + steps_table.add_row(f"{step_num}.", f"{get_activation_command(actual_project_name)}") + step_num += 1 + steps_table.add_row(f"{step_num}.", "Add your RUNPOD_API_KEY to .env") + step_num += 1 + steps_table.add_row(f"{step_num}.", "flash run") else: - steps_table.add_row("2.", "pip install -r requirements.txt") - steps_table.add_row("3.", "cp .env.example .env # Add your RUNPOD_API_KEY") - steps_table.add_row("4.", "flash run") + steps_table.add_row(f"{step_num}.", "pip install -r requirements.txt") + step_num += 1 + steps_table.add_row(f"{step_num}.", "Add your RUNPOD_API_KEY to .env") + step_num += 1 + steps_table.add_row(f"{step_num}.", "flash run") console.print(steps_table) diff --git a/src/tetra_rp/cli/main.py b/src/tetra_rp/cli/main.py index 5fbf22d0..10613b8d 100644 --- a/src/tetra_rp/cli/main.py +++ b/src/tetra_rp/cli/main.py @@ -5,15 +5,6 @@ from rich.console import Console from rich.panel import Panel -from .commands import ( - init, - run, - build, - resource, - deploy, -) - - def get_version() -> str: """Get the package version from metadata.""" try: @@ -33,11 +24,54 @@ def get_version() -> str: ) # command: flash -app.command("init")(init.init_command) -app.command("run")(run.run_command) -app.command("build")(build.build_command) -app.command("report")(resource.report_command) -app.command("clean")(resource.clean_command) + + +@app.command("init") +def init_cmd( + project_name: str = typer.Argument(None, help="Project name (defaults to current directory)"), + no_env: bool = typer.Option(False, "--no-env", help="Skip conda environment creation"), + force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"), +): + """Create new Flash project with Flash Server and GPU workers.""" + from .commands.init import init_command + return init_command(project_name, no_env, force) + + +@app.command("run") +def run_cmd( + host: str = typer.Option("0.0.0.0", help="Host to bind to"), + port: int = typer.Option(8000, help="Port to bind to"), + reload: bool = typer.Option(False, help="Enable auto-reload on code changes"), +): + """Start local Flash development server.""" + from .commands.run import run_command + return run_command(host, port, reload) + + +@app.command("build") +def build_cmd( + output_dir: str = typer.Option("dist", help="Output directory for build artifacts"), + clean: bool = typer.Option(False, help="Clean output directory before building"), +): + """Build Flash application for deployment.""" + from .commands.build import build_command + return build_command(output_dir, clean) + + +@app.command("report") +def report_cmd(): + """Show status of all deployed resources.""" + from .commands.resource import report_command + return report_command() + + +@app.command("clean") +def clean_cmd( + force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"), +): + """Remove all tracked resources.""" + from .commands.resource import clean_command + return clean_command(force) # command: flash deploy deploy_app = typer.Typer( @@ -47,12 +81,48 @@ def get_version() -> str: ) # command: flash deploy * -deploy_app.command("list")(deploy.list_command) -deploy_app.command("new")(deploy.new_command) -deploy_app.command("send")(deploy.send_command) -deploy_app.command("report")(deploy.report_command) -deploy_app.command("rollback")(deploy.rollback_command) -deploy_app.command("remove")(deploy.remove_command) + + +@deploy_app.command("list") +def deploy_list_cmd(): + """Show available deployment environments.""" + from .commands.deploy import list_command + return list_command() + + +@deploy_app.command("new") +def deploy_new_cmd(name: str = typer.Argument(..., help="Environment name")): + """Create a new deployment environment.""" + from .commands.deploy import new_command + return new_command(name) + + +@deploy_app.command("send") +def deploy_send_cmd(name: str = typer.Argument(..., help="Environment name")): + """Deploy project to deployment environment.""" + from .commands.deploy import send_command + return send_command(name) + + +@deploy_app.command("report") +def deploy_report_cmd(name: str = typer.Argument(..., help="Environment name")): + """Show detailed environment status and metrics.""" + from .commands.deploy import report_command + return report_command(name) + + +@deploy_app.command("rollback") +def deploy_rollback_cmd(name: str = typer.Argument(..., help="Environment name")): + """Rollback deployment to previous version.""" + from .commands.deploy import rollback_command + return rollback_command(name) + + +@deploy_app.command("remove") +def deploy_remove_cmd(name: str = typer.Argument(..., help="Environment name")): + """Remove deployment environment.""" + from .commands.deploy import remove_command + return remove_command(name) app.add_typer(deploy_app, name="deploy") From dce72e9fa4a21a4d68522f93ba601bb9a0e53b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sun, 9 Nov 2025 21:50:17 -0800 Subject: [PATCH 04/26] fix(cli): correct GPU and CPU types in skeleton README Fixed critical documentation errors in code examples: - Correct GPU Types - Correct CPU Types --- .../cli/utils/skeleton_template/README.md | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/tetra_rp/cli/utils/skeleton_template/README.md b/src/tetra_rp/cli/utils/skeleton_template/README.md index 50c4a811..9d1920ef 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/README.md +++ b/src/tetra_rp/cli/utils/skeleton_template/README.md @@ -102,7 +102,7 @@ from tetra_rp import remote, CpuLiveServerless, CpuInstanceType config = CpuLiveServerless( name="interface_worker", - instanceIds=[CpuInstanceType.CPU5G_2_8], # 2 vCPU, 8GB RAM + instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM workersMin=0, workersMax=5 ) @@ -280,7 +280,7 @@ from tetra_rp import LiveServerless, GpuGroup config = LiveServerless( name="my_worker", - gpus=[GpuGroup.RTX4090], # GPU type + gpus=[GpuGroup.ADA_24], # GPU type gpuCount=1, # GPUs per worker workersMin=0, # Minimum workers (0 = scale to zero) workersMax=3, # Maximum workers @@ -290,10 +290,13 @@ config = LiveServerless( ``` **Available GPU Types:** -- `GpuGroup.RTX4090` - RTX 4090 (24GB) -- `GpuGroup.RTX3090` - RTX 3090 (24GB) -- `GpuGroup.A100_80GB` - A100 (80GB) -- `GpuGroup.H100` - H100 (80GB) +- `GpuGroup.ADA_24` - NVIDIA GeForce RTX 4090 (24GB) +- `GpuGroup.ADA_48_PRO` - NVIDIA RTX 6000 Ada, L40, L40S (48GB) +- `GpuGroup.ADA_80_PRO` - NVIDIA H100 PCIe, H100 80GB HBM3 (80GB) +- `GpuGroup.AMPERE_24` - NVIDIA RTX A5000, L4, GeForce RTX 3090 (24GB) +- `GpuGroup.AMPERE_48` - NVIDIA A40, RTX A6000 (48GB) +- `GpuGroup.AMPERE_80` - NVIDIA A100 80GB PCIe, A100-SXM4-80GB (80GB) +- `GpuGroup.HOPPER_141` - NVIDIA H200 (141GB) ### CPU Interface Configuration @@ -302,7 +305,7 @@ from tetra_rp import CpuLiveServerless, CpuInstanceType config = CpuLiveServerless( name="my_interface", - instanceIds=[CpuInstanceType.CPU5G_2_8], # 2 vCPU, 8GB RAM + instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM workersMin=0, workersMax=5, idleTimeout=5, @@ -311,12 +314,18 @@ config = CpuLiveServerless( ``` **Available CPU Types:** -- `CPU3G_1_2` - 1 vCPU, 2GB RAM (General Purpose, Gen 3) -- `CPU3G_2_4` - 2 vCPU, 4GB RAM -- `CPU3G_4_8` - 4 vCPU, 8GB RAM -- `CPU5G_2_8` - 2 vCPU, 8GB RAM (General Purpose, Gen 5) -- `CPU5G_4_16` - 4 vCPU, 16GB RAM -- `CPU5C_4_16` - 4 vCPU, 16GB RAM (Compute Optimized, Gen 5) +- `CPU3G_1_4` - 1 vCPU, 4GB RAM (3rd Gen General Purpose) +- `CPU3G_2_8` - 2 vCPU, 8GB RAM (3rd Gen General Purpose) +- `CPU3G_4_16` - 4 vCPU, 16GB RAM (3rd Gen General Purpose) +- `CPU3G_8_32` - 8 vCPU, 32GB RAM (3rd Gen General Purpose) +- `CPU3C_1_2` - 1 vCPU, 2GB RAM (3rd Gen Compute-Optimized) +- `CPU3C_2_4` - 2 vCPU, 4GB RAM (3rd Gen Compute-Optimized) +- `CPU3C_4_8` - 4 vCPU, 8GB RAM (3rd Gen Compute-Optimized) +- `CPU3C_8_16` - 8 vCPU, 16GB RAM (3rd Gen Compute-Optimized) +- `CPU5C_1_2` - 1 vCPU, 2GB RAM (5th Gen Compute-Optimized) +- `CPU5C_2_4` - 2 vCPU, 4GB RAM (5th Gen Compute-Optimized) +- `CPU5C_4_8` - 4 vCPU, 8GB RAM (5th Gen Compute-Optimized) +- `CPU5C_8_16` - 8 vCPU, 16GB RAM (5th Gen Compute-Optimized) ## Adding New Workers @@ -412,11 +421,11 @@ app.include_router(my_api_router, prefix="/my_api", tags=["api"]) ### Pattern 1: ML Model Inference (GPU) ```python -from tetra_rp import remote, LiveServerless +from tetra_rp import remote, LiveServerless, GpuGroup config = LiveServerless( name="model_inference", - gpus=["RTX4090"], + gpus=[GpuGroup.ADA_24], workersMax=3 ) @@ -442,11 +451,11 @@ class ModelInference: ### Pattern 2: API Gateway (CPU) ```python -from tetra_rp import remote, CpuLiveServerless +from tetra_rp import remote, CpuLiveServerless, CpuInstanceType config = CpuLiveServerless( name="api_gateway", - instanceIds=["cpu5c-4-16"], # Compute optimized + instanceIds=[CpuInstanceType.CPU5C_4_8], # Compute optimized workersMax=10 ) @@ -466,11 +475,11 @@ def process_webhook(data: dict): ### Pattern 3: Batch Processing (GPU) ```python -from tetra_rp import remote, LiveServerless +from tetra_rp import remote, LiveServerless, GpuGroup config = LiveServerless( name="batch_processor", - gpus=["A100_80GB"], + gpus=[GpuGroup.AMPERE_80], gpuCount=2, # Multi-GPU workersMax=5 ) From 5d62eb3db7382919ce30c349559a0a7b1e78b51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sun, 9 Nov 2025 21:56:38 -0800 Subject: [PATCH 05/26] chore(cli): lint and format fixes --- src/tetra_rp/cli/commands/init.py | 26 +++++++++---------- src/tetra_rp/cli/main.py | 22 ++++++++++++++-- .../cli/utils/skeleton_template/main.py | 3 ++- .../workers/example/__init__.py | 2 ++ .../workers/example/endpoint.py | 8 ++---- .../workers/interface/__init__.py | 3 ++- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/tetra_rp/cli/commands/init.py b/src/tetra_rp/cli/commands/init.py index 0bb10fea..b23e84d2 100644 --- a/src/tetra_rp/cli/commands/init.py +++ b/src/tetra_rp/cli/commands/init.py @@ -65,7 +65,9 @@ def init_command( ) # Prompt user for confirmation - proceed = typer.confirm("Continue and overwrite these files?", default=False) + proceed = typer.confirm( + "Continue and overwrite these files?", default=False + ) if not proceed: console.print("[yellow]Initialization aborted.[/yellow]") raise typer.Exit(0) @@ -81,7 +83,7 @@ def init_command( # Create project skeleton status_msg = ( - f"Initializing Flash project in current directory..." + "Initializing Flash project in current directory..." if is_current_dir else f"Creating Flash project '{project_name}'..." ) @@ -107,7 +109,9 @@ def init_command( env_created = True else: # Create conda environment - with console.status(f"Creating conda environment '{actual_project_name}'..."): + with console.status( + f"Creating conda environment '{actual_project_name}'..." + ): success, message = create_conda_environment(actual_project_name) if not success: @@ -132,15 +136,11 @@ def init_command( # Success output if is_current_dir: - panel_content = ( - f"Flash project '[bold]{actual_project_name}[/bold]' initialized in current directory!\n\n" - ) + panel_content = f"Flash project '[bold]{actual_project_name}[/bold]' initialized in current directory!\n\n" panel_content += "Project structure:\n" panel_content += " ./\n" else: - panel_content = ( - f"Flash project '[bold]{actual_project_name}[/bold]' created successfully!\n\n" - ) + panel_content = f"Flash project '[bold]{actual_project_name}[/bold]' created successfully!\n\n" panel_content += "Project structure:\n" panel_content += f" {actual_project_name}/\n" @@ -153,9 +153,7 @@ def init_command( panel_content += " └── README.md\n" if env_created: - panel_content += ( - f"\nConda environment '[bold]{actual_project_name}[/bold]' created and configured" - ) + panel_content += f"\nConda environment '[bold]{actual_project_name}[/bold]' created and configured" title = "Project Initialized" if is_current_dir else "Project Created" console.print(Panel(panel_content, title=title, expand=False)) @@ -172,7 +170,9 @@ def init_command( step_num += 1 if env_created: - steps_table.add_row(f"{step_num}.", f"{get_activation_command(actual_project_name)}") + steps_table.add_row( + f"{step_num}.", f"{get_activation_command(actual_project_name)}" + ) step_num += 1 steps_table.add_row(f"{step_num}.", "Add your RUNPOD_API_KEY to .env") step_num += 1 diff --git a/src/tetra_rp/cli/main.py b/src/tetra_rp/cli/main.py index 10613b8d..fcda922c 100644 --- a/src/tetra_rp/cli/main.py +++ b/src/tetra_rp/cli/main.py @@ -5,6 +5,7 @@ from rich.console import Console from rich.panel import Panel + def get_version() -> str: """Get the package version from metadata.""" try: @@ -28,12 +29,17 @@ def get_version() -> str: @app.command("init") def init_cmd( - project_name: str = typer.Argument(None, help="Project name (defaults to current directory)"), - no_env: bool = typer.Option(False, "--no-env", help="Skip conda environment creation"), + project_name: str = typer.Argument( + None, help="Project name (defaults to current directory)" + ), + no_env: bool = typer.Option( + False, "--no-env", help="Skip conda environment creation" + ), force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"), ): """Create new Flash project with Flash Server and GPU workers.""" from .commands.init import init_command + return init_command(project_name, no_env, force) @@ -45,6 +51,7 @@ def run_cmd( ): """Start local Flash development server.""" from .commands.run import run_command + return run_command(host, port, reload) @@ -55,6 +62,7 @@ def build_cmd( ): """Build Flash application for deployment.""" from .commands.build import build_command + return build_command(output_dir, clean) @@ -62,6 +70,7 @@ def build_cmd( def report_cmd(): """Show status of all deployed resources.""" from .commands.resource import report_command + return report_command() @@ -71,8 +80,10 @@ def clean_cmd( ): """Remove all tracked resources.""" from .commands.resource import clean_command + return clean_command(force) + # command: flash deploy deploy_app = typer.Typer( name="deploy", @@ -87,6 +98,7 @@ def clean_cmd( def deploy_list_cmd(): """Show available deployment environments.""" from .commands.deploy import list_command + return list_command() @@ -94,6 +106,7 @@ def deploy_list_cmd(): def deploy_new_cmd(name: str = typer.Argument(..., help="Environment name")): """Create a new deployment environment.""" from .commands.deploy import new_command + return new_command(name) @@ -101,6 +114,7 @@ def deploy_new_cmd(name: str = typer.Argument(..., help="Environment name")): def deploy_send_cmd(name: str = typer.Argument(..., help="Environment name")): """Deploy project to deployment environment.""" from .commands.deploy import send_command + return send_command(name) @@ -108,6 +122,7 @@ def deploy_send_cmd(name: str = typer.Argument(..., help="Environment name")): def deploy_report_cmd(name: str = typer.Argument(..., help="Environment name")): """Show detailed environment status and metrics.""" from .commands.deploy import report_command + return report_command(name) @@ -115,6 +130,7 @@ def deploy_report_cmd(name: str = typer.Argument(..., help="Environment name")): def deploy_rollback_cmd(name: str = typer.Argument(..., help="Environment name")): """Rollback deployment to previous version.""" from .commands.deploy import rollback_command + return rollback_command(name) @@ -122,8 +138,10 @@ def deploy_rollback_cmd(name: str = typer.Argument(..., help="Environment name") def deploy_remove_cmd(name: str = typer.Argument(..., help="Environment name")): """Remove deployment environment.""" from .commands.deploy import remove_command + return remove_command(name) + app.add_typer(deploy_app, name="deploy") diff --git a/src/tetra_rp/cli/utils/skeleton_template/main.py b/src/tetra_rp/cli/utils/skeleton_template/main.py index 4a8cde8a..ee45999b 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/main.py +++ b/src/tetra_rp/cli/utils/skeleton_template/main.py @@ -7,6 +7,7 @@ Each APIRouter is discovered as a handler during build and can be deployed as a separate serverless endpoint (queue-based or load-balancer based). """ + import os import logging from fastapi import FastAPI @@ -15,7 +16,7 @@ from workers.interface import example_interface_router -log = logging.getLogger(__name__) +logger = logging.getLogger(__name__) # Create FastAPI app diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py index 067f8b91..326a0f9b 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py @@ -22,11 +22,13 @@ class ProcessRequest(BaseModel): """Request model for processing endpoint.""" + data: str class ProcessResponse(BaseModel): """Response model for processing endpoint.""" + status: str input: dict output: str diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py index ed04134b..a2f111aa 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py @@ -34,7 +34,7 @@ def process(self, input_data: dict) -> dict: result = { "status": "success", "input": input_data, - "output": f"Processed: {input_data}" + "output": f"Processed: {input_data}", } return result @@ -45,11 +45,7 @@ def process(self, input_data: dict) -> dict: import asyncio worker = ExampleWorker() - payload = { - "input": { - "foo": "bar" - } - } + payload = {"input": {"foo": "bar"}} result = asyncio.run(worker.process(payload)) diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py index 5c20e049..5878c654 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py @@ -23,6 +23,7 @@ class TodoItem(BaseModel): """Request model for todo item operations.""" + data: str @@ -32,7 +33,7 @@ class TodoItem(BaseModel): async def get_todo_list(): """ Get the todo list. - + This is a load-balancer handler (multiple endpoints). Requests are routed directly to available workers. """ From f34165cde4e6bc7f844da8be505ee740892d1aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sun, 9 Nov 2025 22:06:06 -0800 Subject: [PATCH 06/26] fix(cli): correct API endpoints and request/response models in skeleton README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address Copilot review feedback on PR 110: **Quick Start fixes:** - GPU endpoint: /example/process → /example/ - CPU endpoint: /interface/todo/list → /interface/list - Fix request model: {"task": "..."} → {"data": "..."} **API Reference fixes:** - GPU Worker: /example/process → /example/ - GPU request: {"input_data": {...}} → {"data": "string"} - CPU list: /interface/todo/list → /interface/list - CPU add: /interface/todo/add → /interface/add - CPU delete: DELETE /interface/todo/delete → POST /interface/delete - Request bodies: {"item": "..."} → {"data": "..."} - Response format: direct strings → {"result": "string"} All endpoints now match actual FastAPI router implementation. --- .../cli/utils/skeleton_template/README.md | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/tetra_rp/cli/utils/skeleton_template/README.md b/src/tetra_rp/cli/utils/skeleton_template/README.md index 9d1920ef..0c3ce29b 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/README.md +++ b/src/tetra_rp/cli/utils/skeleton_template/README.md @@ -47,12 +47,12 @@ The server starts at **http://localhost:8000** curl http://localhost:8000/ping # Test GPU worker endpoint -curl -X POST http://localhost:8000/example/process \ +curl -X POST http://localhost:8000/example/ \ -H "Content-Type: application/json" \ - -d '{"task": "example"}' + -d '{"data": "example task"}' # Test CPU interface endpoint -curl http://localhost:8000/interface/todo/list +curl http://localhost:8000/interface/list ``` Test using the OpenAPI (Swagger) UI at **http://localhost:8000/docs** @@ -195,13 +195,13 @@ Health check endpoint. ### Example GPU Worker -#### `POST /example/process` +#### `POST /example/` Queue-based GPU processing endpoint. **Request:** ```json { - "input_data": {"task": "example"} + "data": "example task" } ``` @@ -209,14 +209,14 @@ Queue-based GPU processing endpoint. ```json { "status": "success", - "input": {"task": "example"}, - "output": "Processed: {'task': 'example'}" + "input": {"input": "example task"}, + "output": "Processed: {'input': 'example task'}" } ``` ### Example CPU Interface -#### `GET /interface/todo/list` +#### `GET /interface/list` Get list of todo items. **Response:** @@ -228,34 +228,38 @@ Get list of todo items. } ``` -#### `POST /interface/todo/add` +#### `POST /interface/add` Add a new todo item. **Request:** ```json { - "item": "New task" + "data": "New task" } ``` **Response:** ```json -"added item: New task" +{ + "result": "added item: New task" +} ``` -#### `DELETE /interface/todo/delete` +#### `POST /interface/delete` Delete a todo item. **Request:** ```json { - "item": "Old task" + "data": "Old task" } ``` **Response:** ```json -"deleted item: Old task" +{ + "result": "deleted item: Old task" +} ``` ## Configuration From 84792355585c5ff6cdde23a3bd9b04c48da63bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Sun, 9 Nov 2025 22:09:19 -0800 Subject: [PATCH 07/26] feat(cli): add parallel test block to skeleton interface endpoint Add `if __name__ == "__main__"` test block to CPU interface endpoint to match the pattern in GPU worker endpoint. Features: - Test all three interface functions in parallel using asyncio.gather() - Demonstrates concurrent execution of remote CPU functions - Provides example of running endpoint directly for testing Users can now test CPU interface by running: python workers/interface/endpoint.py --- .../workers/interface/endpoint.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py index ce2d2a37..fd534e18 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py @@ -26,3 +26,23 @@ def todo_add_item(item): @remote(config) def todo_delete_item(item): return f"deleted item: {item}" + + +# Run this script by itself to test the CPU interface +if __name__ == "__main__": + import asyncio + + async def test_interface(): + """Test all interface functions in parallel.""" + # Run all functions concurrently + list_result, add_result, delete_result = await asyncio.gather( + todo_get_list(), + todo_add_item("Test task"), + todo_delete_item("Old task"), + ) + + print("List result:", list_result) + print("Add result:", add_result) + print("Delete result:", delete_result) + + asyncio.run(test_interface()) From 53795b9d0e0c23de8315ec612027708e823fd157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 16:38:36 -0800 Subject: [PATCH 08/26] perf: reduce the load footprint from runpod library --- src/tetra_rp/__init__.py | 2 -- src/tetra_rp/core/resources/__init__.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/tetra_rp/__init__.py b/src/tetra_rp/__init__.py index 8ae3f3f8..6adb1a5b 100644 --- a/src/tetra_rp/__init__.py +++ b/src/tetra_rp/__init__.py @@ -22,7 +22,6 @@ ResourceManager, ServerlessEndpoint, ServerlessType, - runpod, NetworkVolume, ) @@ -40,6 +39,5 @@ "ResourceManager", "ServerlessEndpoint", "ServerlessType", - "runpod", "NetworkVolume", ] diff --git a/src/tetra_rp/core/resources/__init__.py b/src/tetra_rp/core/resources/__init__.py index 682c190c..60ab6b71 100644 --- a/src/tetra_rp/core/resources/__init__.py +++ b/src/tetra_rp/core/resources/__init__.py @@ -1,5 +1,4 @@ from .base import BaseResource, DeployableResource -from .cloud import runpod from .cpu import CpuInstanceType from .gpu import GpuGroup, GpuType, GpuTypeDetail from .resource_manager import ResourceManager @@ -17,7 +16,6 @@ __all__ = [ - "runpod", "BaseResource", "CpuInstanceType", "CpuLiveServerless", From b8741544ac42056456465b83a76f5bc5bd761370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 16:41:16 -0800 Subject: [PATCH 09/26] refactor(cli): restore IDE functionality and fix command signatures Reverted aggressive lazy-loading patterns that broke IDE reference linking and type checking. Performance is maintained through upstream boto3 lazy-loading in runpod-python. Changes: - Restored direct imports in __init__.py (removed __getattr__ pattern) - Removed get_runpod() lazy-loading function - Fixed ResourceManager to load resources at init - Added Typer decorators to command functions for proper CLI signatures - Improved flash init overwrite behavior with user prompts - Updated tests to handle new initialization pattern Breaking: None - all CLI commands work identically Performance: Maintained with upstream boto3 fix (0.6s cold start) Developer Experience: IDE navigation, autocomplete, and type checking restored --- src/tetra_rp/cli/commands/deploy.py | 20 ++- src/tetra_rp/cli/commands/init.py | 64 ++++----- src/tetra_rp/cli/main.py | 126 +++--------------- .../core/resources/resource_manager.py | 7 +- src/tetra_rp/core/resources/serverless.py | 7 +- tests/integration/test_remote_concurrency.py | 2 + tests/unit/test_dotenv_loading.py | 8 +- 7 files changed, 81 insertions(+), 153 deletions(-) diff --git a/src/tetra_rp/cli/commands/deploy.py b/src/tetra_rp/cli/commands/deploy.py index 5a56b65a..737767d6 100644 --- a/src/tetra_rp/cli/commands/deploy.py +++ b/src/tetra_rp/cli/commands/deploy.py @@ -78,7 +78,9 @@ def list_command(): console.print(f"\n{summary}") -def new_command(name: str): +def new_command( + name: str = typer.Argument(..., help="Name of the deployment environment to create") +): """Create a new deployment environment.""" environments = get_deployment_environments() @@ -125,7 +127,9 @@ def new_command(name: str): console.print(f"\nNext: [bold]flash deploy send {name}[/bold]") -def send_command(name: str): +def send_command( + name: str = typer.Argument(..., help="Name of the deployment environment") +): """Deploy project to deployment environment.""" environments = get_deployment_environments() @@ -157,7 +161,9 @@ def send_command(name: str): raise typer.Exit(1) -def report_command(name: str): +def report_command( + name: str = typer.Argument(..., help="Name of the deployment environment") +): """Show detailed environment status and metrics.""" environments = get_deployment_environments() @@ -223,7 +229,9 @@ def report_command(name: str): console.print(f" {metric}") -def rollback_command(name: str): +def rollback_command( + name: str = typer.Argument(..., help="Name of the deployment environment") +): """Rollback deployment to previous version.""" environments = get_deployment_environments() @@ -283,7 +291,9 @@ def rollback_command(name: str): console.print(f"Environment '{name}' is now running the previous version.") -def remove_command(name: str): +def remove_command( + name: str = typer.Argument(..., help="Name of the deployment environment to remove") +): """Remove deployment environment.""" environments = get_deployment_environments() diff --git a/src/tetra_rp/cli/commands/init.py b/src/tetra_rp/cli/commands/init.py index b23e84d2..fd07617e 100644 --- a/src/tetra_rp/cli/commands/init.py +++ b/src/tetra_rp/cli/commands/init.py @@ -1,6 +1,7 @@ """Project initialization command.""" from pathlib import Path +from typing import Optional import typer from rich.console import Console @@ -29,9 +30,11 @@ def init_command( - project_name: str = None, - no_env: bool = False, - force: bool = False, + project_name: Optional[str] = typer.Argument( + None, help="Project name or '.' for current directory" + ), + no_env: bool = typer.Option(False, "--no-env", help="Skip conda environment creation"), + force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"), ): """Create new Flash project with Flash Server and GPU workers.""" @@ -48,38 +51,35 @@ def init_command( is_current_dir = False actual_project_name = project_name - # Handle directory existence and conflicts - if is_current_dir: - # Initializing in current directory - check for conflicts - conflicts = detect_file_conflicts(project_dir) + # Create project directory if needed + if not is_current_dir: + project_dir.mkdir(parents=True, exist_ok=True) - if conflicts and not force: - # Show warning and prompt user - console.print( - Panel( - "[yellow]Warning: The following files will be overwritten:[/yellow]\n\n" - + "\n".join(f" • {conflict}" for conflict in conflicts), - title="File Conflicts Detected", - expand=False, - ) + # Check for file conflicts in target directory + conflicts = detect_file_conflicts(project_dir) + should_overwrite = force # Start with force flag value + + if conflicts and not force: + # Show warning and prompt user + console.print( + Panel( + "[yellow]Warning: The following files will be overwritten:[/yellow]\n\n" + + "\n".join(f" • {conflict}" for conflict in conflicts), + title="File Conflicts Detected", + expand=False, ) + ) - # Prompt user for confirmation - proceed = typer.confirm( - "Continue and overwrite these files?", default=False - ) - if not proceed: - console.print("[yellow]Initialization aborted.[/yellow]") - raise typer.Exit(0) - else: - # Creating new directory - check if it exists - if project_dir.exists() and not force: - console.print(f"[red]Directory '{project_name}' already exists[/red]") - console.print("Use --force to overwrite") - raise typer.Exit(1) + # Prompt user for confirmation + proceed = typer.confirm( + "Continue and overwrite these files?", default=False + ) + if not proceed: + console.print("[yellow]Initialization aborted.[/yellow]") + raise typer.Exit(0) - # Create project directory - project_dir.mkdir(parents=True, exist_ok=True) + # User confirmed, so we should overwrite + should_overwrite = True # Create project skeleton status_msg = ( @@ -88,7 +88,7 @@ def init_command( else f"Creating Flash project '{project_name}'..." ) with console.status(status_msg): - create_project_skeleton(project_dir, force) + create_project_skeleton(project_dir, should_overwrite) # Create conda environment if requested env_created = False diff --git a/src/tetra_rp/cli/main.py b/src/tetra_rp/cli/main.py index fcda922c..c7b317d3 100644 --- a/src/tetra_rp/cli/main.py +++ b/src/tetra_rp/cli/main.py @@ -5,6 +5,14 @@ from rich.console import Console from rich.panel import Panel +from .commands import ( + init, + run, + build, + # resource, + deploy, +) + def get_version() -> str: """Get the package version from metadata.""" @@ -25,63 +33,11 @@ def get_version() -> str: ) # command: flash - - -@app.command("init") -def init_cmd( - project_name: str = typer.Argument( - None, help="Project name (defaults to current directory)" - ), - no_env: bool = typer.Option( - False, "--no-env", help="Skip conda environment creation" - ), - force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"), -): - """Create new Flash project with Flash Server and GPU workers.""" - from .commands.init import init_command - - return init_command(project_name, no_env, force) - - -@app.command("run") -def run_cmd( - host: str = typer.Option("0.0.0.0", help="Host to bind to"), - port: int = typer.Option(8000, help="Port to bind to"), - reload: bool = typer.Option(False, help="Enable auto-reload on code changes"), -): - """Start local Flash development server.""" - from .commands.run import run_command - - return run_command(host, port, reload) - - -@app.command("build") -def build_cmd( - output_dir: str = typer.Option("dist", help="Output directory for build artifacts"), - clean: bool = typer.Option(False, help="Clean output directory before building"), -): - """Build Flash application for deployment.""" - from .commands.build import build_command - - return build_command(output_dir, clean) - - -@app.command("report") -def report_cmd(): - """Show status of all deployed resources.""" - from .commands.resource import report_command - - return report_command() - - -@app.command("clean") -def clean_cmd( - force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"), -): - """Remove all tracked resources.""" - from .commands.resource import clean_command - - return clean_command(force) +app.command("init")(init.init_command) +app.command("run")(run.run_command) +app.command("build")(build.build_command) +# app.command("report")(resource.report_command) +# app.command("clean")(resource.clean_command) # command: flash deploy @@ -91,57 +47,15 @@ def clean_cmd( no_args_is_help=True, ) -# command: flash deploy * - - -@deploy_app.command("list") -def deploy_list_cmd(): - """Show available deployment environments.""" - from .commands.deploy import list_command - - return list_command() - - -@deploy_app.command("new") -def deploy_new_cmd(name: str = typer.Argument(..., help="Environment name")): - """Create a new deployment environment.""" - from .commands.deploy import new_command - - return new_command(name) - - -@deploy_app.command("send") -def deploy_send_cmd(name: str = typer.Argument(..., help="Environment name")): - """Deploy project to deployment environment.""" - from .commands.deploy import send_command - - return send_command(name) - - -@deploy_app.command("report") -def deploy_report_cmd(name: str = typer.Argument(..., help="Environment name")): - """Show detailed environment status and metrics.""" - from .commands.deploy import report_command - - return report_command(name) - - -@deploy_app.command("rollback") -def deploy_rollback_cmd(name: str = typer.Argument(..., help="Environment name")): - """Rollback deployment to previous version.""" - from .commands.deploy import rollback_command - - return rollback_command(name) - - -@deploy_app.command("remove") -def deploy_remove_cmd(name: str = typer.Argument(..., help="Environment name")): - """Remove deployment environment.""" - from .commands.deploy import remove_command - - return remove_command(name) +deploy_app.command("list")(deploy.list_command) +deploy_app.command("new")(deploy.new_command) +# deploy_app.command("send")(deploy.send_command) +# deploy_app.command("report")(deploy.report_command) +# deploy_app.command("rollback")(deploy.rollback_command) +# deploy_app.command("remove")(deploy.remove_command) +# command: flash deploy * app.add_typer(deploy_app, name="deploy") diff --git a/src/tetra_rp/core/resources/resource_manager.py b/src/tetra_rp/core/resources/resource_manager.py index 93204456..00fbab9d 100644 --- a/src/tetra_rp/core/resources/resource_manager.py +++ b/src/tetra_rp/core/resources/resource_manager.py @@ -22,8 +22,9 @@ class ResourceManager(SingletonMixin): # Class variables shared across all instances (singleton) _resources: Dict[str, DeployableResource] = {} _deployment_locks: Dict[str, asyncio.Lock] = {} - _global_lock: Optional[asyncio.Lock] = None # Will be initialized lazily + _global_lock: Optional[asyncio.Lock] = None _lock_initialized = False + _resources_initialized = False def __init__(self): # Ensure async locks are initialized properly for the singleton instance @@ -31,8 +32,10 @@ def __init__(self): ResourceManager._global_lock = asyncio.Lock() ResourceManager._lock_initialized = True - if not self._resources: + # Load resources immediately on initialization (only once) + if not ResourceManager._resources_initialized: self._load_resources() + ResourceManager._resources_initialized = True def _load_resources(self) -> Dict[str, DeployableResource]: """Load persisted resource information using cross-platform file locking.""" diff --git a/src/tetra_rp/core/resources/serverless.py b/src/tetra_rp/core/resources/serverless.py index 3d5e0a3d..8ec70523 100644 --- a/src/tetra_rp/core/resources/serverless.py +++ b/src/tetra_rp/core/resources/serverless.py @@ -108,7 +108,7 @@ class ServerlessResource(DeployableResource): scalerType: Optional[ServerlessScalerType] = ServerlessScalerType.QUEUE_DELAY scalerValue: Optional[int] = 4 templateId: Optional[str] = None - type: Optional[ServerlessType] = ServerlessType.QB + type: Optional[ServerlessType] = None workersMax: Optional[int] = 3 workersMin: Optional[int] = 0 workersPFBTarget: Optional[int] = None @@ -143,6 +143,11 @@ def endpoint(self) -> runpod.Endpoint: raise ValueError("Missing self.id") return runpod.Endpoint(self.id) + @property + def endpoint_url(self) -> str: + base_url = self.endpoint.rp_client.endpoint_url_base + return f"{base_url}/{self.id}" + @field_serializer("scalerType") def serialize_scaler_type( self, value: Optional[ServerlessScalerType] diff --git a/tests/integration/test_remote_concurrency.py b/tests/integration/test_remote_concurrency.py index 58cdb296..2634ab62 100644 --- a/tests/integration/test_remote_concurrency.py +++ b/tests/integration/test_remote_concurrency.py @@ -39,6 +39,7 @@ def setup_method(self): ResourceManager._resources.clear() ResourceManager._deployment_locks.clear() ResourceManager._lock_initialized = False + ResourceManager._resources_initialized = False # Use temporary state file self.temp_dir = tempfile.mkdtemp() @@ -64,6 +65,7 @@ def teardown_method(self): ResourceManager._resources.clear() ResourceManager._deployment_locks.clear() ResourceManager._lock_initialized = False + ResourceManager._resources_initialized = False async def test_concurrent_remote_function_calls_single_deployment(self): """Test that concurrent calls to same @remote function create only one deployment.""" diff --git a/tests/unit/test_dotenv_loading.py b/tests/unit/test_dotenv_loading.py index af48c084..e587b75a 100644 --- a/tests/unit/test_dotenv_loading.py +++ b/tests/unit/test_dotenv_loading.py @@ -18,6 +18,7 @@ def test_dotenv_loads_before_imports(self): # This test verifies the order of operations in __init__.py # by checking that dotenv import/call happens before other imports + # Note: Resources are imported directly at module initialization init_file = ( Path(__file__).parent.parent.parent / "src" / "tetra_rp" / "__init__.py" @@ -29,7 +30,6 @@ def test_dotenv_loads_before_imports(self): dotenv_import_line = None dotenv_call_line = None logger_import_line = None - resources_import_line = None for i, line in enumerate(lines): if "from dotenv import load_dotenv" in line: @@ -38,14 +38,11 @@ def test_dotenv_loads_before_imports(self): dotenv_call_line = i elif "from .logger import setup_logging" in line: logger_import_line = i - elif "from .core.resources import" in line: - resources_import_line = i # Verify order of operations assert dotenv_import_line is not None, "dotenv import not found" assert dotenv_call_line is not None, "load_dotenv() call not found" assert logger_import_line is not None, "logger import not found" - assert resources_import_line is not None, "resources import not found" # load_dotenv() should be called before any other imports assert dotenv_import_line < dotenv_call_line, ( @@ -54,9 +51,6 @@ def test_dotenv_loads_before_imports(self): assert dotenv_call_line < logger_import_line, ( "load_dotenv() should be called before logger import" ) - assert dotenv_call_line < resources_import_line, ( - "load_dotenv() should be called before resources import" - ) def test_env_file_loading_with_temp_file(self): """Test that .env file variables are loaded correctly.""" From aed00e3172c9b834ddd8ac2102bfe5937ef3e5aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 16:42:29 -0800 Subject: [PATCH 10/26] build: pegged latest main branch of runpod-python for now --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2cd1f204..a8919954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,3 +114,6 @@ module = [ "cloudpickle.*", ] ignore_missing_imports = true + +[tool.uv.sources] +runpod = { git = "https://github.com/runpod/runpod-python", rev = "main" } From 8cbb166ccfe86fe315a377ad388d815c7281feaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 16:43:07 -0800 Subject: [PATCH 11/26] test: fix test for lazy-loaded runpod --- tests/unit/resources/test_serverless.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/resources/test_serverless.py b/tests/unit/resources/test_serverless.py index 5721147d..18ed0f1f 100644 --- a/tests/unit/resources/test_serverless.py +++ b/tests/unit/resources/test_serverless.py @@ -82,9 +82,11 @@ def test_endpoint_property_with_id(self, basic_serverless_config): serverless = ServerlessResource(**basic_serverless_config) serverless.id = "test-id-123" - with patch("tetra_rp.core.resources.cloud.runpod.api_key", "test-api-key"): + # Patch runpod.Endpoint since runpod is now lazy-loaded + with patch("runpod.Endpoint") as mock_endpoint: endpoint = serverless.endpoint assert endpoint is not None + mock_endpoint.assert_called_once_with("test-id-123") def test_endpoint_property_without_id_raises_error(self, basic_serverless_config): """Test endpoint property raises error when ID is not set.""" From c76a9cf2c0560a36dd7e2707642ae8e2fee46d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 21:24:31 -0800 Subject: [PATCH 12/26] feat(client): add async function support to @remote decorator Support both `def` and `async def` function definitions in @remote decorator. Changes: - Modified AST parser to recognize both FunctionDef and AsyncFunctionDef nodes - Added textwrap.dedent() to handle indented function definitions - Updated client.py docstring with async/sync examples - Added comprehensive test suite for async function support Fixes ValueError when wrapping async functions with @remote decorator. --- src/tetra_rp/client.py | 29 +++-- src/tetra_rp/stubs/live_serverless.py | 7 +- tests/unit/test_async_function_support.py | 132 ++++++++++++++++++++++ 3 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 tests/unit/test_async_function_support.py diff --git a/src/tetra_rp/client.py b/src/tetra_rp/client.py index e1045a8d..0fa1826f 100644 --- a/src/tetra_rp/client.py +++ b/src/tetra_rp/client.py @@ -26,6 +26,12 @@ def remote( dynamic resource provisioning and installation of required dependencies. It can also bypass remote execution entirely for local testing. + Supports both sync and async function definitions: + - `def my_function(...)` - Regular synchronous function + - `async def my_function(...)` - Asynchronous function + + In both cases, the decorated function returns an awaitable that must be called with `await`. + Args: resource_config (ServerlessResource): Configuration object specifying the serverless resource to be provisioned or used. Not used when local=True. @@ -46,18 +52,27 @@ def remote( Example: ```python - # Remote execution (production) + # Async function (recommended style) @remote( resource_config=my_resource_config, - dependencies=["numpy", "pandas"], - accelerate_downloads=True, + dependencies=["torch>=2.0.0"], ) - async def my_function(data): - # Function logic here - pass + async def gpu_task(data: dict) -> dict: + import torch + # GPU processing here + return {"result": "processed"} + + # Sync function (also supported) + @remote( + resource_config=my_resource_config, + dependencies=["pandas>=2.0.0"], + ) + def cpu_task(data: dict) -> dict: + import pandas as pd + # CPU processing here + return {"result": "processed"} # Local execution (testing/development) - # Note: Ensure numpy and pandas are installed locally first @remote( resource_config=my_resource_config, dependencies=["numpy", "pandas"], # Only used for remote execution diff --git a/src/tetra_rp/stubs/live_serverless.py b/src/tetra_rp/stubs/live_serverless.py index 04205c6b..65b70444 100644 --- a/src/tetra_rp/stubs/live_serverless.py +++ b/src/tetra_rp/stubs/live_serverless.py @@ -27,13 +27,16 @@ def get_function_source(func): # Get the source code of the decorated function source = inspect.getsource(func) + # Dedent the source to handle functions defined in classes or indented contexts + source = textwrap.dedent(source) + # Parse the source code module = ast.parse(source) - # Find the function definition node + # Find the function definition node (both sync and async) function_def = None for node in ast.walk(module): - if isinstance(node, ast.FunctionDef) and node.name == func.__name__: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == func.__name__: function_def = node break diff --git a/tests/unit/test_async_function_support.py b/tests/unit/test_async_function_support.py new file mode 100644 index 00000000..dda76c0e --- /dev/null +++ b/tests/unit/test_async_function_support.py @@ -0,0 +1,132 @@ +""" +Test that @remote decorator supports both sync and async function definitions. +""" + +import pytest +from tetra_rp.stubs.live_serverless import get_function_source + + +class TestAsyncFunctionSupport: + """Test async function support in @remote decorator.""" + + def test_sync_function_source_extraction(self): + """Test that sync functions can be extracted.""" + + def my_sync_function(x: int) -> int: + return x * 2 + + source, src_hash = get_function_source(my_sync_function) + + assert "def my_sync_function" in source + assert "return x * 2" in source + assert src_hash is not None + assert len(src_hash) == 64 # SHA256 hash length + + def test_async_function_source_extraction(self): + """Test that async functions can be extracted.""" + + async def my_async_function(x: int) -> int: + return x * 2 + + source, src_hash = get_function_source(my_async_function) + + assert "async def my_async_function" in source + assert "return x * 2" in source + assert src_hash is not None + assert len(src_hash) == 64 # SHA256 hash length + + def test_async_function_with_await(self): + """Test async function with await statements.""" + + async def async_with_await(x: int) -> int: + import asyncio + + await asyncio.sleep(0.1) + return x * 3 + + source, src_hash = get_function_source(async_with_await) + + assert "async def async_with_await" in source + assert "await asyncio.sleep" in source + assert "return x * 3" in source + + def test_async_function_with_imports(self): + """Test async function with internal imports.""" + + async def gpu_matrix_multiply(input_data: dict) -> dict: + import torch + + size = input_data.get("matrix_size", 1000) + device = "cuda" if torch.cuda.is_available() else "cpu" + matrix = torch.randn(size, size, device=device) + result = torch.matmul(matrix, matrix.T) + return {"device": device, "result": result.shape} + + source, src_hash = get_function_source(gpu_matrix_multiply) + + assert "async def gpu_matrix_multiply" in source + assert "import torch" in source + assert "torch.cuda.is_available()" in source + assert "torch.matmul" in source + + def test_sync_and_async_have_different_hashes(self): + """Test that sync and async versions of same function have different hashes.""" + + def sync_version(): + return "hello" + + async def async_version(): + return "hello" + + sync_source, sync_hash = get_function_source(sync_version) + async_source, async_hash = get_function_source(async_version) + + assert sync_hash != async_hash + assert "def sync_version" in sync_source + assert "async def async_version" in async_source + + def test_async_function_with_type_hints(self): + """Test async function with complex type hints.""" + + async def process_csv_data(input_data: dict) -> dict: + import pandas as pd + import numpy as np + + rows = input_data.get("rows", 1000) + data = {f"col_{i}": np.random.randn(rows) for i in range(5)} + df = pd.DataFrame(data) + + return { + "status": "success", + "shape": list(df.shape), + "mean": df.mean().to_dict(), + } + + source, src_hash = get_function_source(process_csv_data) + + assert "async def process_csv_data" in source + assert "input_data: dict" in source + assert "-> dict:" in source + assert "import pandas as pd" in source + + def test_lambda_not_supported(self): + """Test that lambda functions are not supported.""" + + # This should raise because lambdas are not FunctionDef nodes + # Note: We can't use lambda directly due to ruff E731 rule + with pytest.raises((ValueError, OSError)): + # Create lambda inline to test error handling + # OSError can occur from inspect.getsource on lambda + get_function_source((lambda x: x * 2)) + + def test_source_hash_consistency(self): + """Test that same function source produces same hash.""" + + def test_func(x: int) -> int: + return x + 1 + + source1, hash1 = get_function_source(test_func) + source2, hash2 = get_function_source(test_func) + + assert source1 == source2 + assert hash1 == hash2 From 62715d8a3e0dfe8d6ba673cd89e2755b4a397e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 21:26:02 -0800 Subject: [PATCH 13/26] fix: ruff format --- src/tetra_rp/stubs/live_serverless.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tetra_rp/stubs/live_serverless.py b/src/tetra_rp/stubs/live_serverless.py index 65b70444..a2495e21 100644 --- a/src/tetra_rp/stubs/live_serverless.py +++ b/src/tetra_rp/stubs/live_serverless.py @@ -36,7 +36,10 @@ def get_function_source(func): # Find the function definition node (both sync and async) function_def = None for node in ast.walk(module): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == func.__name__: + if ( + isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == func.__name__ + ): function_def = node break From 3517d0fa2b02de9bdc3b54c188fcb361f4627c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Thu, 13 Nov 2025 21:26:02 -0800 Subject: [PATCH 14/26] feat(client): add async function support to remote decorator Support both `def` and `async def` function definitions in the `@remote` decorator. Changes: - Modified AST parser to recognize both FunctionDef and AsyncFunctionDef nodes - Added textwrap.dedent() to handle indented function definitions - Updated client.py docstring with async/sync examples - Added comprehensive test suite for async function support Fixes ValueError when wrapping async functions with the remote decorator. --- src/tetra_rp/stubs/live_serverless.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tetra_rp/stubs/live_serverless.py b/src/tetra_rp/stubs/live_serverless.py index 65b70444..a2495e21 100644 --- a/src/tetra_rp/stubs/live_serverless.py +++ b/src/tetra_rp/stubs/live_serverless.py @@ -36,7 +36,10 @@ def get_function_source(func): # Find the function definition node (both sync and async) function_def = None for node in ast.walk(module): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == func.__name__: + if ( + isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == func.__name__ + ): function_def = node break From b77bbec98f06af089b1f4865635feaed8b45fe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 00:54:06 -0800 Subject: [PATCH 15/26] fix(stubs): support async functions and decorator unwrapping Fixes ValueError when using @remote decorator with async functions by: - Adding inspect.unwrap() to get original function from wrapper - Supporting ast.AsyncFunctionDef in addition to ast.FunctionDef - Adding early dedenting to handle functions defined in classes/methods This enables proper source extraction for both sync and async decorated functions, resolving the "Could not find function definition" error. --- src/tetra_rp/stubs/live_serverless.py | 3 + tests/unit/test_live_serverless_stub.py | 135 ++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 tests/unit/test_live_serverless_stub.py diff --git a/src/tetra_rp/stubs/live_serverless.py b/src/tetra_rp/stubs/live_serverless.py index a2495e21..933c6c92 100644 --- a/src/tetra_rp/stubs/live_serverless.py +++ b/src/tetra_rp/stubs/live_serverless.py @@ -24,6 +24,9 @@ def get_function_source(func): """Extract the function source code without the decorator.""" + # Unwrap any decorators to get the original function + func = inspect.unwrap(func) + # Get the source code of the decorated function source = inspect.getsource(func) diff --git a/tests/unit/test_live_serverless_stub.py b/tests/unit/test_live_serverless_stub.py new file mode 100644 index 00000000..398db02e --- /dev/null +++ b/tests/unit/test_live_serverless_stub.py @@ -0,0 +1,135 @@ +"""Unit tests for live_serverless stub functionality.""" + +import ast +from tetra_rp.stubs.live_serverless import get_function_source +from tetra_rp import remote, LiveServerless + + +# Create a dummy config for testing +dummy_config = LiveServerless(name="test-endpoint") + + +class TestGetFunctionSource: + """Test suite for get_function_source function.""" + + def test_sync_function_without_decorator(self): + """Test extraction of synchronous function source without decorator.""" + + def simple_function(x: int) -> int: + return x * 2 + + source, src_hash = get_function_source(simple_function) + + assert "def simple_function" in source + assert "return x * 2" in source + assert len(src_hash) == 64 # SHA256 hash length + + def test_async_function_without_decorator(self): + """Test extraction of async function source without decorator.""" + + async def async_function(x: int) -> int: + return x * 2 + + source, src_hash = get_function_source(async_function) + + assert "async def async_function" in source + assert "return x * 2" in source + assert len(src_hash) == 64 + + def test_sync_function_with_remote_decorator(self): + """Test extraction of decorated synchronous function source.""" + + # Create a real decorated function for testing + def real_sync_function(x: int) -> int: + """A real sync function.""" + return x * 3 + + decorated_real = remote(resource_config=dummy_config)(real_sync_function) + + source, src_hash = get_function_source(decorated_real) + + # Should find the function definition + assert "def real_sync_function" in source + assert "return x * 3" in source + + def test_async_function_with_remote_decorator(self): + """Test extraction of decorated async function source.""" + + @remote(resource_config=dummy_config) + async def decorated_async(x: int) -> int: + """A decorated async function.""" + return x * 4 + + source, src_hash = get_function_source(decorated_async) + + # Should find the async function definition + assert "async def decorated_async" in source + assert "return x * 4" in source + + def test_function_source_excludes_decorator_line(self): + """Test that source extraction correctly excludes decorator lines.""" + + @remote(resource_config=dummy_config) + async def function_with_decorator(x: int) -> int: + """Function with decorator.""" + return x * 5 + + source, src_hash = get_function_source(function_with_decorator) + + # Source should NOT include the decorator line (it's stripped) + assert "@remote" not in source + # But should include the function definition + assert "async def function_with_decorator" in source + assert "return x * 5" in source + + def test_ast_parsing_handles_async_function(self): + """Test that AST parsing correctly identifies async functions.""" + + @remote(resource_config=dummy_config) + async def async_test_function(x: int) -> int: + """Async function for AST test.""" + result = x * 6 + return result + + source, src_hash = get_function_source(async_test_function) + + # Parse the source to verify it's valid Python with async function + module = ast.parse(source) + + # Find the async function definition + async_func_found = False + for node in ast.walk(module): + if ( + isinstance(node, ast.AsyncFunctionDef) + and node.name == "async_test_function" + ): + async_func_found = True + break + + assert async_func_found, "AsyncFunctionDef node should be found in AST" + + def test_hash_consistency(self): + """Test that same function produces same hash.""" + + def consistent_function(x: int) -> int: + return x * 7 + + source1, hash1 = get_function_source(consistent_function) + source2, hash2 = get_function_source(consistent_function) + + assert hash1 == hash2 + assert source1 == source2 + + def test_different_functions_different_hashes(self): + """Test that different functions produce different hashes.""" + + def function_one(x: int) -> int: + return x * 8 + + def function_two(x: int) -> int: + return x * 9 + + _, hash1 = get_function_source(function_one) + _, hash2 = get_function_source(function_two) + + assert hash1 != hash2 From a089d426e31eb168e90bde251c65138226267c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:14:40 -0800 Subject: [PATCH 16/26] chore: quality check --- src/tetra_rp/cli/commands/deploy.py | 14 +- src/tetra_rp/cli/commands/init.py | 8 +- uv.lock | 948 +++++++++++++++------------- 3 files changed, 508 insertions(+), 462 deletions(-) diff --git a/src/tetra_rp/cli/commands/deploy.py b/src/tetra_rp/cli/commands/deploy.py index 737767d6..325ab1eb 100644 --- a/src/tetra_rp/cli/commands/deploy.py +++ b/src/tetra_rp/cli/commands/deploy.py @@ -79,7 +79,9 @@ def list_command(): def new_command( - name: str = typer.Argument(..., help="Name of the deployment environment to create") + name: str = typer.Argument( + ..., help="Name of the deployment environment to create" + ), ): """Create a new deployment environment.""" @@ -128,7 +130,7 @@ def new_command( def send_command( - name: str = typer.Argument(..., help="Name of the deployment environment") + name: str = typer.Argument(..., help="Name of the deployment environment"), ): """Deploy project to deployment environment.""" @@ -162,7 +164,7 @@ def send_command( def report_command( - name: str = typer.Argument(..., help="Name of the deployment environment") + name: str = typer.Argument(..., help="Name of the deployment environment"), ): """Show detailed environment status and metrics.""" @@ -230,7 +232,7 @@ def report_command( def rollback_command( - name: str = typer.Argument(..., help="Name of the deployment environment") + name: str = typer.Argument(..., help="Name of the deployment environment"), ): """Rollback deployment to previous version.""" @@ -292,7 +294,9 @@ def rollback_command( def remove_command( - name: str = typer.Argument(..., help="Name of the deployment environment to remove") + name: str = typer.Argument( + ..., help="Name of the deployment environment to remove" + ), ): """Remove deployment environment.""" diff --git a/src/tetra_rp/cli/commands/init.py b/src/tetra_rp/cli/commands/init.py index fd07617e..575a8037 100644 --- a/src/tetra_rp/cli/commands/init.py +++ b/src/tetra_rp/cli/commands/init.py @@ -33,7 +33,9 @@ def init_command( project_name: Optional[str] = typer.Argument( None, help="Project name or '.' for current directory" ), - no_env: bool = typer.Option(False, "--no-env", help="Skip conda environment creation"), + no_env: bool = typer.Option( + False, "--no-env", help="Skip conda environment creation" + ), force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"), ): """Create new Flash project with Flash Server and GPU workers.""" @@ -71,9 +73,7 @@ def init_command( ) # Prompt user for confirmation - proceed = typer.confirm( - "Continue and overwrite these files?", default=False - ) + proceed = typer.confirm("Continue and overwrite these files?", default=False) if not proceed: console.print("[yellow]Initialization aborted.[/yellow]") raise typer.Exit(0) diff --git a/uv.lock b/uv.lock index 7f70b873..24bed1b2 100644 --- a/uv.lock +++ b/uv.lock @@ -165,11 +165,11 @@ wheels = [ [[package]] name = "annotated-doc" -version = "0.0.3" +version = "0.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/a6/dc46877b911e40c00d395771ea710d5e77b6de7bacd5fdcd78d70cc5a48f/annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda", size = 5535 } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/b7/cf592cb5de5cb3bade3357f8d2cf42bf103bbe39f459824b4939fd212911/annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580", size = 5488 }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, ] [[package]] @@ -410,21 +410,21 @@ wheels = [ [[package]] name = "boto3" -version = "1.40.63" +version = "1.40.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/fb/db063c9600cbe2c52009edd436262c696b18bafaf49835a7f17ba1679a84/boto3-1.40.63.tar.gz", hash = "sha256:3bf4b034900c87a6a9b3b3b44c4aec26e96fc73bff2505f0766224b7295178ce", size = 111541 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/d1/3be32bf855200fb2defb4ab2b251aba4d05e787235adbf3ae1ba2099545b/boto3-1.40.73.tar.gz", hash = "sha256:3716703cb8b126607533853d7e2a85f0bb23b0b9d4805c69170abead33d725ef", size = 111607 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/d4/d977f678c60e05c19c857ad896f838152dc68e0cc28f0f026e224879d8ca/boto3-1.40.63-py3-none-any.whl", hash = "sha256:f15d4abf1a6283887c336f660cdfc2162a210d2d8f4d98dbcbcef983371c284d", size = 139322 }, + { url = "https://files.pythonhosted.org/packages/88/b7/350cf5d2ed824c75a596499e6a77c5ec6d35e9437b8c10a936900461c6e3/boto3-1.40.73-py3-none-any.whl", hash = "sha256:85172e11e3b8d5a09504bc532b6589730ac68845410403ca3793d037b8a5d445", size = 139360 }, ] [[package]] name = "botocore" -version = "1.40.63" +version = "1.40.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, @@ -432,95 +432,67 @@ dependencies = [ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/08/62f4d332dd729d14190073eaf6db63803a5bc2d9b8f1248ae3cbc6c9cb64/botocore-1.40.63.tar.gz", hash = "sha256:0324552c3c800e258cbcb8c22b495a2e2e0260a7408d08016196e46fa0d1b587", size = 14400022 } +sdist = { url = "https://files.pythonhosted.org/packages/51/83/4afe8a1fdd4b5200ceff986b1e72be16c55010980bf337360535733d85c3/botocore-1.40.73.tar.gz", hash = "sha256:0650ceada268824282da9af8615f3e4cf2453be8bf85b820f9207eff958d56d0", size = 14452167 } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/b0/17c1e8fa8617c588da33f6724909eef56e1745ddfe2f87972d9a8e9e6ca2/botocore-1.40.63-py3-none-any.whl", hash = "sha256:83657b3ee487268fccc9ba022cba572ba657b9ece8cddd1fa241e2c6a49c8c14", size = 14061984 }, + { url = "https://files.pythonhosted.org/packages/7a/9c/f5085b99a4e0ec8454ab2d1b9492f7e10c0d008578cb26a856d7a8240b40/botocore-1.40.73-py3-none-any.whl", hash = "sha256:87524c5fe552ecceaea72f51163b37ab35eb82aaa6a64eb80489ade7340c1d23", size = 14118004 }, ] [[package]] name = "brotli" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045 }, - { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218 }, - { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872 }, - { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254 }, - { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293 }, - { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385 }, - { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104 }, - { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981 }, - { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297 }, - { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735 }, - { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107 }, - { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400 }, - { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985 }, - { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099 }, - { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172 }, - { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255 }, - { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 }, - { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 }, - { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 }, - { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 }, - { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 }, - { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 }, - { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 }, - { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 }, - { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 }, - { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 }, - { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 }, - { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 }, - { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 }, - { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 }, - { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 }, - { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 }, - { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 }, - { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 }, - { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 }, - { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 }, - { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 }, - { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 }, - { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 }, - { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 }, - { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 }, - { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 }, - { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 }, - { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 }, - { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 }, - { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 }, - { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 }, - { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 }, - { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 }, - { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681 }, - { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475 }, - { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173 }, - { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803 }, - { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946 }, - { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707 }, - { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231 }, - { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157 }, - { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122 }, - { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206 }, - { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804 }, - { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517 }, - { url = "https://files.pythonhosted.org/packages/1b/aa/aa6e0c9848ee4375514af0b27abf470904992939b7363ae78fc8aca8a9a8/Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a", size = 873048 }, - { url = "https://files.pythonhosted.org/packages/ae/32/38bba1a8bef9ecb1cda08439fd28d7e9c51aff13b4783a4f1610da90b6c2/Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f", size = 446207 }, - { url = "https://files.pythonhosted.org/packages/3c/6a/14cc20ddc53efc274601c8195791a27cfb7acc5e5134e0f8c493a8b8821a/Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9", size = 2903803 }, - { url = "https://files.pythonhosted.org/packages/9a/26/62b2d894d4e82d7a7f4e0bb9007a42bbc765697a5679b43186acd68d7a79/Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf", size = 2941149 }, - { url = "https://files.pythonhosted.org/packages/a9/ca/00d55bbdd8631236c61777742d8a8454cf6a87eb4125cad675912c68bec7/Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac", size = 2672253 }, - { url = "https://files.pythonhosted.org/packages/e2/e6/4a730f6e5b5d538e92d09bc51bf69119914f29a222f9e1d65ae4abb27a4e/Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578", size = 2757005 }, - { url = "https://files.pythonhosted.org/packages/cb/6b/8cf297987fe3c1bf1c87f0c0b714af2ce47092b8d307b9f6ecbc65f98968/Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474", size = 2910658 }, - { url = "https://files.pythonhosted.org/packages/2c/1f/be9443995821c933aad7159803f84ef4923c6f5b72c2affd001192b310fc/Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c", size = 2809728 }, - { url = "https://files.pythonhosted.org/packages/76/2f/213bab6efa902658c80a1247142d42b138a27ccdd6bade49ca9cd74e714a/Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d", size = 2935043 }, - { url = "https://files.pythonhosted.org/packages/27/89/bbb14fa98e895d1e601491fba54a5feec167d262f0d3d537a3b0d4cd0029/Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59", size = 2930639 }, - { url = "https://files.pythonhosted.org/packages/14/87/03a6d6e1866eddf9f58cc57e35befbeb5514da87a416befe820150cae63f/Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419", size = 2932834 }, - { url = "https://files.pythonhosted.org/packages/a4/d5/e5f85e04f75144d1a89421ba432def6bdffc8f28b04f5b7d540bbd03362c/Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2", size = 2845213 }, - { url = "https://files.pythonhosted.org/packages/99/bf/25ef07add7afbb1aacd4460726a1a40370dfd60c0810b6f242a6d3871d7e/Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f", size = 3031573 }, - { url = "https://files.pythonhosted.org/packages/55/22/948a97bda5c9dc9968d56b9ed722d9727778db43739cf12ef26ff69be94d/Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb", size = 2926885 }, - { url = "https://files.pythonhosted.org/packages/31/ba/e53d107399b535ef89deb6977dd8eae468e2dde7b1b74c6cbe2c0e31fda2/Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64", size = 333171 }, - { url = "https://files.pythonhosted.org/packages/99/b3/f7b3af539f74b82e1c64d28685a5200c631cc14ae751d37d6ed819655627/Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467", size = 357258 }, +sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/10/a090475284fc4a71aed40a96f32e44a7fe5bda39687353dd977720b211b6/brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e", size = 863089 }, + { url = "https://files.pythonhosted.org/packages/03/41/17416630e46c07ac21e378c3464815dd2e120b441e641bc516ac32cc51d2/brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984", size = 445442 }, + { url = "https://files.pythonhosted.org/packages/24/31/90cc06584deb5d4fcafc0985e37741fc6b9717926a78674bbb3ce018957e/brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de", size = 1532658 }, + { url = "https://files.pythonhosted.org/packages/62/17/33bf0c83bcbc96756dfd712201d87342732fad70bb3472c27e833a44a4f9/brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947", size = 1631241 }, + { url = "https://files.pythonhosted.org/packages/48/10/f47854a1917b62efe29bc98ac18e5d4f71df03f629184575b862ef2e743b/brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2", size = 1424307 }, + { url = "https://files.pythonhosted.org/packages/e4/b7/f88eb461719259c17483484ea8456925ee057897f8e64487d76e24e5e38d/brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84", size = 1488208 }, + { url = "https://files.pythonhosted.org/packages/26/59/41bbcb983a0c48b0b8004203e74706c6b6e99a04f3c7ca6f4f41f364db50/brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d", size = 1597574 }, + { url = "https://files.pythonhosted.org/packages/8e/e6/8c89c3bdabbe802febb4c5c6ca224a395e97913b5df0dff11b54f23c1788/brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1", size = 1492109 }, + { url = "https://files.pythonhosted.org/packages/ed/9a/4b19d4310b2dbd545c0c33f176b0528fa68c3cd0754e34b2f2bcf56548ae/brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997", size = 334461 }, + { url = "https://files.pythonhosted.org/packages/ac/39/70981d9f47705e3c2b95c0847dfa3e7a37aa3b7c6030aedc4873081ed005/brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196", size = 369035 }, + { url = "https://files.pythonhosted.org/packages/7a/ef/f285668811a9e1ddb47a18cb0b437d5fc2760d537a2fe8a57875ad6f8448/brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744", size = 863110 }, + { url = "https://files.pythonhosted.org/packages/50/62/a3b77593587010c789a9d6eaa527c79e0848b7b860402cc64bc0bc28a86c/brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f", size = 445438 }, + { url = "https://files.pythonhosted.org/packages/cd/e1/7fadd47f40ce5549dc44493877db40292277db373da5053aff181656e16e/brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd", size = 1534420 }, + { url = "https://files.pythonhosted.org/packages/12/8b/1ed2f64054a5a008a4ccd2f271dbba7a5fb1a3067a99f5ceadedd4c1d5a7/brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe", size = 1632619 }, + { url = "https://files.pythonhosted.org/packages/89/5a/7071a621eb2d052d64efd5da2ef55ecdac7c3b0c6e4f9d519e9c66d987ef/brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a", size = 1426014 }, + { url = "https://files.pythonhosted.org/packages/26/6d/0971a8ea435af5156acaaccec1a505f981c9c80227633851f2810abd252a/brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b", size = 1489661 }, + { url = "https://files.pythonhosted.org/packages/f3/75/c1baca8b4ec6c96a03ef8230fab2a785e35297632f402ebb1e78a1e39116/brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3", size = 1599150 }, + { url = "https://files.pythonhosted.org/packages/0d/1a/23fcfee1c324fd48a63d7ebf4bac3a4115bdb1b00e600f80f727d850b1ae/brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae", size = 1493505 }, + { url = "https://files.pythonhosted.org/packages/36/e5/12904bbd36afeef53d45a84881a4810ae8810ad7e328a971ebbfd760a0b3/brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03", size = 334451 }, + { url = "https://files.pythonhosted.org/packages/02/8b/ecb5761b989629a4758c394b9301607a5880de61ee2ee5fe104b87149ebc/brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24", size = 369035 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { 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 }, + { url = "https://files.pythonhosted.org/packages/62/28/4d00cb9bd76a6357a66fcd54b4b6d70288385584063f4b07884c1e7286ac/brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161", size = 334362 }, + { url = "https://files.pythonhosted.org/packages/1c/4e/bc1dcac9498859d5e353c9b153627a3752868a9d5f05ce8dedd81a2354ab/brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44", size = 369115 }, + { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523 }, + { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289 }, + { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076 }, + { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880 }, + { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737 }, + { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440 }, + { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313 }, + { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945 }, + { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368 }, + { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116 }, + { url = "https://files.pythonhosted.org/packages/0f/1d/7787912f3fd30845d2927241bcd5aa2a9fde45b3e866394ee8155e49f612/brotli-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8d4f47f284bdd28629481c97b5f29ad67544fa258d9091a6ed1fda47c7347cd1", size = 862928 }, + { url = "https://files.pythonhosted.org/packages/d8/29/663fd4195dbbd90aa118874dd67ca438ba0ac039d67902ff46c7105196f3/brotli-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2881416badd2a88a7a14d981c103a52a23a276a553a8aacc1346c2ff47c8dc17", size = 445365 }, + { url = "https://files.pythonhosted.org/packages/96/14/d57282ff7da3e9238899c1bebb5f1d94265a1b76002f8a984ef5826d8ae8/brotli-1.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d39b54b968f4b49b5e845758e202b1035f948b0561ff5e6385e855c96625971", size = 1531224 }, + { url = "https://files.pythonhosted.org/packages/25/1a/ea1b65a92e0e317306b8b207757c0e21376b14984cfd8d4c746a0efe7ed1/brotli-1.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95db242754c21a88a79e01504912e537808504465974ebb92931cfca2510469e", size = 1630502 }, + { url = "https://files.pythonhosted.org/packages/6a/a4/68cd62219295ab8844731ebf64a5c60ba84358c62b130a5077ea90e2a73a/brotli-1.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bba6e7e6cfe1e6cb6eb0b7c2736a6059461de1fa2c0ad26cf845de6c078d16c8", size = 1423310 }, + { url = "https://files.pythonhosted.org/packages/a1/1d/e0b2a429cbe50f673cb318debd42297525e08add574677cce78c99041747/brotli-1.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:88ef7d55b7bcf3331572634c3fd0ed327d237ceb9be6066810d39020a3ebac7a", size = 1487431 }, + { url = "https://files.pythonhosted.org/packages/af/28/b8ddaf1b719818c22344f03ff2add71e387223408ea0a95f56f6ef8b8f5d/brotli-1.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7fa18d65a213abcfbb2f6cafbb4c58863a8bd6f2103d65203c520ac117d1944b", size = 1596969 }, + { url = "https://files.pythonhosted.org/packages/b8/a6/c790ef38cd49a9e27798a4b12681175f8c06cc76440e9deac22592fa7cd8/brotli-1.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:09ac247501d1909e9ee47d309be760c89c990defbb2e0240845c892ea5ff0de4", size = 1491229 }, + { url = "https://files.pythonhosted.org/packages/3e/d3/c09cc2348d1c92845752967cedd881fa7865d270caeab9153453037a872b/brotli-1.2.0-cp39-cp39-win32.whl", hash = "sha256:c25332657dee6052ca470626f18349fc1fe8855a56218e19bd7a8c6ad4952c49", size = 334437 }, + { url = "https://files.pythonhosted.org/packages/1b/df/e7c780e463ee7bd7951770692bbea5a605f56b9809ec7f6ce751d7b2ee88/brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937", size = 369008 }, ] [[package]] @@ -552,11 +524,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286 }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438 }, ] [[package]] @@ -752,11 +724,11 @@ wheels = [ [[package]] name = "cloudpickle" -version = "3.1.1" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113 } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992 }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228 }, ] [[package]] @@ -863,78 +835,78 @@ toml = [ [[package]] name = "coverage" -version = "7.11.0" +version = "7.11.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/95/c49df0aceb5507a80b9fe5172d3d39bf23f05be40c23c8d77d556df96cec/coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31", size = 215800 }, - { url = "https://files.pythonhosted.org/packages/dc/c6/7bb46ce01ed634fff1d7bb53a54049f539971862cc388b304ff3c51b4f66/coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075", size = 216198 }, - { url = "https://files.pythonhosted.org/packages/94/b2/75d9d8fbf2900268aca5de29cd0a0fe671b0f69ef88be16767cc3c828b85/coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab", size = 242953 }, - { url = "https://files.pythonhosted.org/packages/65/ac/acaa984c18f440170525a8743eb4b6c960ace2dbad80dc22056a437fc3c6/coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0", size = 244766 }, - { url = "https://files.pythonhosted.org/packages/d8/0d/938d0bff76dfa4a6b228c3fc4b3e1c0e2ad4aa6200c141fcda2bd1170227/coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785", size = 246625 }, - { url = "https://files.pythonhosted.org/packages/38/54/8f5f5e84bfa268df98f46b2cb396b1009734cfb1e5d6adb663d284893b32/coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591", size = 243568 }, - { url = "https://files.pythonhosted.org/packages/68/30/8ba337c2877fe3f2e1af0ed7ff4be0c0c4aca44d6f4007040f3ca2255e99/coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088", size = 244665 }, - { url = "https://files.pythonhosted.org/packages/cc/fb/c6f1d6d9a665536b7dde2333346f0cc41dc6a60bd1ffc10cd5c33e7eb000/coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f", size = 242681 }, - { url = "https://files.pythonhosted.org/packages/be/38/1b532319af5f991fa153c20373291dc65c2bf532af7dbcffdeef745c8f79/coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866", size = 242912 }, - { url = "https://files.pythonhosted.org/packages/67/3d/f39331c60ef6050d2a861dc1b514fa78f85f792820b68e8c04196ad733d6/coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841", size = 243559 }, - { url = "https://files.pythonhosted.org/packages/4b/55/cb7c9df9d0495036ce582a8a2958d50c23cd73f84a23284bc23bd4711a6f/coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf", size = 218266 }, - { url = "https://files.pythonhosted.org/packages/68/a8/b79cb275fa7bd0208767f89d57a1b5f6ba830813875738599741b97c2e04/coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969", size = 219169 }, - { url = "https://files.pythonhosted.org/packages/49/3a/ee1074c15c408ddddddb1db7dd904f6b81bc524e01f5a1c5920e13dbde23/coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", size = 215912 }, - { url = "https://files.pythonhosted.org/packages/70/c4/9f44bebe5cb15f31608597b037d78799cc5f450044465bcd1ae8cb222fe1/coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", size = 216310 }, - { url = "https://files.pythonhosted.org/packages/42/01/5e06077cfef92d8af926bdd86b84fb28bf9bc6ad27343d68be9b501d89f2/coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", size = 246706 }, - { url = "https://files.pythonhosted.org/packages/40/b8/7a3f1f33b35cc4a6c37e759137533119560d06c0cc14753d1a803be0cd4a/coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", size = 248634 }, - { url = "https://files.pythonhosted.org/packages/7a/41/7f987eb33de386bc4c665ab0bf98d15fcf203369d6aacae74f5dd8ec489a/coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", size = 250741 }, - { url = "https://files.pythonhosted.org/packages/23/c1/a4e0ca6a4e83069fb8216b49b30a7352061ca0cb38654bd2dc96b7b3b7da/coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", size = 246837 }, - { url = "https://files.pythonhosted.org/packages/5d/03/ced062a17f7c38b4728ff76c3acb40d8465634b20b4833cdb3cc3a74e115/coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", size = 248429 }, - { url = "https://files.pythonhosted.org/packages/97/af/a7c6f194bb8c5a2705ae019036b8fe7f49ea818d638eedb15fdb7bed227c/coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", size = 246490 }, - { url = "https://files.pythonhosted.org/packages/ab/c3/aab4df02b04a8fde79068c3c41ad7a622b0ef2b12e1ed154da986a727c3f/coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", size = 246208 }, - { url = "https://files.pythonhosted.org/packages/30/d8/e282ec19cd658238d60ed404f99ef2e45eed52e81b866ab1518c0d4163cf/coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", size = 247126 }, - { url = "https://files.pythonhosted.org/packages/d1/17/a635fa07fac23adb1a5451ec756216768c2767efaed2e4331710342a3399/coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c", size = 218314 }, - { url = "https://files.pythonhosted.org/packages/2a/29/2ac1dfcdd4ab9a70026edc8d715ece9b4be9a1653075c658ee6f271f394d/coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", size = 219203 }, - { url = "https://files.pythonhosted.org/packages/03/21/5ce8b3a0133179115af4c041abf2ee652395837cb896614beb8ce8ddcfd9/coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", size = 217879 }, - { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098 }, - { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331 }, - { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825 }, - { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573 }, - { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706 }, - { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221 }, - { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624 }, - { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744 }, - { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325 }, - { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180 }, - { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479 }, - { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290 }, - { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924 }, - { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129 }, - { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380 }, - { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375 }, - { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978 }, - { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253 }, - { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591 }, - { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411 }, - { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303 }, - { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157 }, - { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921 }, - { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526 }, - { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317 }, - { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948 }, - { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837 }, - { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061 }, - { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398 }, - { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574 }, - { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797 }, - { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361 }, - { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349 }, - { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114 }, - { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723 }, - { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238 }, - { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180 }, - { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241 }, - { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510 }, - { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761 }, +sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/68/b53157115ef76d50d1d916d6240e5cd5b3c14dba8ba1b984632b8221fc2e/coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5", size = 216377 }, + { url = "https://files.pythonhosted.org/packages/14/c1/d2f9d8e37123fe6e7ab8afcaab8195f13bc84a8b2f449a533fd4812ac724/coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7", size = 216892 }, + { url = "https://files.pythonhosted.org/packages/83/73/18f05d8010149b650ed97ee5c9f7e4ae68c05c7d913391523281e41c2495/coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb", size = 243650 }, + { url = "https://files.pythonhosted.org/packages/63/3c/c0cbb296c0ecc6dcbd70f4b473fcd7fe4517bbef8b09f4326d78f38adb87/coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1", size = 245478 }, + { url = "https://files.pythonhosted.org/packages/b9/9a/dad288cf9faa142a14e75e39dc646d968b93d74e15c83e9b13fd628f2cb3/coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c", size = 247337 }, + { url = "https://files.pythonhosted.org/packages/e3/ba/f6148ebf5547b3502013175e41bf3107a4e34b7dd19f9793a6ce0e1cd61f/coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31", size = 244328 }, + { url = "https://files.pythonhosted.org/packages/e6/4d/b93784d0b593c5df89a0d48cbbd2d0963e0ca089eaf877405849792e46d3/coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2", size = 245381 }, + { url = "https://files.pythonhosted.org/packages/3a/8d/6735bfd4f0f736d457642ee056a570d704c9d57fdcd5c91ea5d6b15c944e/coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507", size = 243390 }, + { url = "https://files.pythonhosted.org/packages/db/3d/7ba68ed52d1873d450aefd8d2f5a353e67b421915cb6c174e4222c7b918c/coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832", size = 243654 }, + { url = "https://files.pythonhosted.org/packages/14/26/be2720c4c7bf73c6591ae4ab503a7b5a31c7a60ced6dba855cfcb4a5af7e/coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e", size = 244272 }, + { url = "https://files.pythonhosted.org/packages/90/20/086f5697780df146dbc0df4ae9b6db2b23ddf5aa550f977b2825137728e9/coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb", size = 218969 }, + { url = "https://files.pythonhosted.org/packages/98/5c/cc6faba945ede5088156da7770e30d06c38b8591785ac99bcfb2074f9ef6/coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8", size = 219903 }, + { url = "https://files.pythonhosted.org/packages/92/92/43a961c0f57b666d01c92bcd960c7f93677de5e4ee7ca722564ad6dee0fa/coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1", size = 216504 }, + { url = "https://files.pythonhosted.org/packages/5d/5c/dbfc73329726aef26dbf7fefef81b8a2afd1789343a579ea6d99bf15d26e/coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06", size = 217006 }, + { url = "https://files.pythonhosted.org/packages/a5/e0/878c84fb6661964bc435beb1e28c050650aa30e4c1cdc12341e298700bda/coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80", size = 247415 }, + { url = "https://files.pythonhosted.org/packages/56/9e/0677e78b1e6a13527f39c4b39c767b351e256b333050539861c63f98bd61/coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa", size = 249332 }, + { url = "https://files.pythonhosted.org/packages/54/90/25fc343e4ce35514262451456de0953bcae5b37dda248aed50ee51234cee/coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297", size = 251443 }, + { url = "https://files.pythonhosted.org/packages/13/56/bc02bbc890fd8b155a64285c93e2ab38647486701ac9c980d457cdae857a/coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362", size = 247554 }, + { url = "https://files.pythonhosted.org/packages/0f/ab/0318888d091d799a82d788c1e8d8bd280f1d5c41662bbb6e11187efe33e8/coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87", size = 249139 }, + { url = "https://files.pythonhosted.org/packages/79/d8/3ee50929c4cd36fcfcc0f45d753337001001116c8a5b8dd18d27ea645737/coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200", size = 247209 }, + { url = "https://files.pythonhosted.org/packages/94/7c/3cf06e327401c293e60c962b4b8a2ceb7167c1a428a02be3adbd1d7c7e4c/coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4", size = 246936 }, + { url = "https://files.pythonhosted.org/packages/99/0b/ffc03dc8f4083817900fd367110015ef4dd227b37284104a5eb5edc9c106/coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060", size = 247835 }, + { url = "https://files.pythonhosted.org/packages/17/4d/dbe54609ee066553d0bcdcdf108b177c78dab836292bee43f96d6a5674d1/coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7", size = 218994 }, + { url = "https://files.pythonhosted.org/packages/94/11/8e7155df53f99553ad8114054806c01a2c0b08f303ea7e38b9831652d83d/coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55", size = 219926 }, + { url = "https://files.pythonhosted.org/packages/1f/93/bea91b6a9e35d89c89a1cd5824bc72e45151a9c2a9ca0b50d9e9a85e3ae3/coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc", size = 218599 }, + { url = "https://files.pythonhosted.org/packages/c2/39/af056ec7a27c487e25c7f6b6e51d2ee9821dba1863173ddf4dc2eebef4f7/coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f", size = 216676 }, + { url = "https://files.pythonhosted.org/packages/3c/f8/21126d34b174d037b5d01bea39077725cbb9a0da94a95c5f96929c695433/coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e", size = 217034 }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0fd35f35658cdd11f7686303214bd5908225838f374db47f9e457c8d6df8/coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a", size = 248531 }, + { url = "https://files.pythonhosted.org/packages/8f/59/0bfc5900fc15ce4fd186e092451de776bef244565c840c9c026fd50857e1/coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1", size = 251290 }, + { url = "https://files.pythonhosted.org/packages/71/88/d5c184001fa2ac82edf1b8f2cd91894d2230d7c309e937c54c796176e35b/coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd", size = 252375 }, + { url = "https://files.pythonhosted.org/packages/5c/29/f60af9f823bf62c7a00ce1ac88441b9a9a467e499493e5cc65028c8b8dd2/coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5", size = 248946 }, + { url = "https://files.pythonhosted.org/packages/67/16/4662790f3b1e03fce5280cad93fd18711c35980beb3c6f28dca41b5230c6/coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e", size = 250310 }, + { url = "https://files.pythonhosted.org/packages/8f/75/dd6c2e28308a83e5fc1ee602f8204bd3aa5af685c104cb54499230cf56db/coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044", size = 248461 }, + { url = "https://files.pythonhosted.org/packages/16/fe/b71af12be9f59dc9eb060688fa19a95bf3223f56c5af1e9861dfa2275d2c/coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7", size = 248039 }, + { url = "https://files.pythonhosted.org/packages/11/b8/023b2003a2cd96bdf607afe03d9b96c763cab6d76e024abe4473707c4eb8/coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405", size = 249903 }, + { url = "https://files.pythonhosted.org/packages/d6/ee/5f1076311aa67b1fa4687a724cc044346380e90ce7d94fec09fd384aa5fd/coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e", size = 219201 }, + { url = "https://files.pythonhosted.org/packages/4f/24/d21688f48fe9fcc778956680fd5aaf69f4e23b245b7c7a4755cbd421d25b/coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055", size = 220012 }, + { url = "https://files.pythonhosted.org/packages/4f/9e/d5eb508065f291456378aa9b16698b8417d87cb084c2b597f3beb00a8084/coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f", size = 218652 }, + { url = "https://files.pythonhosted.org/packages/6d/f6/d8572c058211c7d976f24dab71999a565501fb5b3cdcb59cf782f19c4acb/coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36", size = 216694 }, + { url = "https://files.pythonhosted.org/packages/4a/f6/b6f9764d90c0ce1bce8d995649fa307fff21f4727b8d950fa2843b7b0de5/coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e", size = 217065 }, + { url = "https://files.pythonhosted.org/packages/a5/8d/a12cb424063019fd077b5be474258a0ed8369b92b6d0058e673f0a945982/coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2", size = 248062 }, + { url = "https://files.pythonhosted.org/packages/7f/9c/dab1a4e8e75ce053d14259d3d7485d68528a662e286e184685ea49e71156/coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63", size = 250657 }, + { url = "https://files.pythonhosted.org/packages/3f/89/a14f256438324f33bae36f9a1a7137729bf26b0a43f5eda60b147ec7c8c7/coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3", size = 251900 }, + { url = "https://files.pythonhosted.org/packages/04/07/75b0d476eb349f1296486b1418b44f2d8780cc8db47493de3755e5340076/coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5", size = 248254 }, + { url = "https://files.pythonhosted.org/packages/5a/4b/0c486581fa72873489ca092c52792d008a17954aa352809a7cbe6cf0bf07/coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5", size = 250041 }, + { url = "https://files.pythonhosted.org/packages/af/a3/0059dafb240ae3e3291f81b8de00e9c511d3dd41d687a227dd4b529be591/coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7", size = 248004 }, + { url = "https://files.pythonhosted.org/packages/83/93/967d9662b1eb8c7c46917dcc7e4c1875724ac3e73c3cb78e86d7a0ac719d/coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5", size = 247828 }, + { url = "https://files.pythonhosted.org/packages/4c/1c/5077493c03215701e212767e470b794548d817dfc6247a4718832cc71fac/coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094", size = 249588 }, + { url = "https://files.pythonhosted.org/packages/7f/a5/77f64de461016e7da3e05d7d07975c89756fe672753e4cf74417fc9b9052/coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c", size = 219223 }, + { url = "https://files.pythonhosted.org/packages/ed/1c/ec51a3c1a59d225b44bdd3a4d463135b3159a535c2686fac965b698524f4/coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2", size = 220033 }, + { url = "https://files.pythonhosted.org/packages/01/ec/e0ce39746ed558564c16f2cc25fa95ce6fc9fa8bfb3b9e62855d4386b886/coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944", size = 218661 }, + { url = "https://files.pythonhosted.org/packages/46/cb/483f130bc56cbbad2638248915d97b185374d58b19e3cc3107359715949f/coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428", size = 217389 }, + { url = "https://files.pythonhosted.org/packages/cb/ae/81f89bae3afef75553cf10e62feb57551535d16fd5859b9ee5a2a97ddd27/coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a", size = 217742 }, + { url = "https://files.pythonhosted.org/packages/db/6e/a0fb897041949888191a49c36afd5c6f5d9f5fd757e0b0cd99ec198a324b/coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655", size = 259049 }, + { url = "https://files.pythonhosted.org/packages/d9/b6/d13acc67eb402d91eb94b9bd60593411799aed09ce176ee8d8c0e39c94ca/coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7", size = 261113 }, + { url = "https://files.pythonhosted.org/packages/ea/07/a6868893c48191d60406df4356aa7f0f74e6de34ef1f03af0d49183e0fa1/coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d", size = 263546 }, + { url = "https://files.pythonhosted.org/packages/24/e5/28598f70b2c1098332bac47925806353b3313511d984841111e6e760c016/coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f", size = 258260 }, + { url = "https://files.pythonhosted.org/packages/0e/58/58e2d9e6455a4ed746a480c4b9cf96dc3cb2a6b8f3efbee5efd33ae24b06/coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0", size = 261121 }, + { url = "https://files.pythonhosted.org/packages/17/57/38803eefb9b0409934cbc5a14e3978f0c85cb251d2b6f6a369067a7105a0/coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739", size = 258736 }, + { url = "https://files.pythonhosted.org/packages/a8/f3/f94683167156e93677b3442be1d4ca70cb33718df32a2eea44a5898f04f6/coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71", size = 257625 }, + { url = "https://files.pythonhosted.org/packages/87/ed/42d0bf1bc6bfa7d65f52299a31daaa866b4c11000855d753857fe78260ac/coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76", size = 259827 }, + { url = "https://files.pythonhosted.org/packages/d3/76/5682719f5d5fbedb0c624c9851ef847407cae23362deb941f185f489c54e/coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c", size = 219897 }, + { url = "https://files.pythonhosted.org/packages/10/e0/1da511d0ac3d39e6676fa6cc5ec35320bbf1cebb9b24e9ee7548ee4e931a/coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac", size = 220959 }, + { url = "https://files.pythonhosted.org/packages/e5/9d/e255da6a04e9ec5f7b633c54c0fdfa221a9e03550b67a9c83217de12e96c/coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc", size = 219234 }, + { url = "https://files.pythonhosted.org/packages/19/8f/92bdd27b067204b99f396a1414d6342122f3e2663459baf787108a6b8b84/coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe", size = 208478 }, ] [package.optional-dependencies] @@ -1015,11 +987,11 @@ wheels = [ [[package]] name = "docutils" -version = "0.22.2" +version = "0.22.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/c0/89fe6215b443b919cb98a5002e107cb5026854ed1ccb6b5833e0768419d1/docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d", size = 2289092 } +sdist = { url = "https://files.pythonhosted.org/packages/d9/02/111134bfeb6e6c7ac4c74594e39a59f6c0195dc4846afbeac3cba60f1927/docutils-0.22.3.tar.gz", hash = "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", size = 2290153 } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667 }, + { url = "https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl", hash = "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb", size = 633032 }, ] [[package]] @@ -1050,7 +1022,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.120.2" +version = "0.121.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -1058,9 +1030,9 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/fb/79e556bc8f9d360e5cc2fa7364a7ad6bda6f1736938b43a2791fa8baee7b/fastapi-0.120.2.tar.gz", hash = "sha256:4c5ab43e2a90335bbd8326d1b659eac0f3dbcc015e2af573c4f5de406232c4ac", size = 338684 } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/f08f264da34cf160db82c62ffb335e838b1fc16cbcc905f474c7d4c815db/fastapi-0.121.2.tar.gz", hash = "sha256:ca8e932b2b823ec1721c641e3669472c855ad9564a2854c9899d904c2848b8b9", size = 342944 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/cc/1c33d05f62c9349bb80dfe789cc9a7409bdfb337a63fa347fd651d25294a/fastapi-0.120.2-py3-none-any.whl", hash = "sha256:bedcf2c14240e43d56cb9a339b32bcf15104fe6b5897c0222603cb7ec416c8eb", size = 108383 }, + { url = "https://files.pythonhosted.org/packages/eb/23/dfb161e91db7c92727db505dc72a384ee79681fe0603f706f9f9f52c2901/fastapi-0.121.2-py3-none-any.whl", hash = "sha256:f2d80b49a86a846b70cc3a03eb5ea6ad2939298bf6a7fe377aa9cd3dd079d358", size = 109201 }, ] [package.optional-dependencies] @@ -1072,7 +1044,8 @@ all = [ { name = "jinja2" }, { name = "orjson" }, { name = "pydantic-extra-types" }, - { name = "pydantic-settings" }, + { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "python-multipart" }, { name = "pyyaml" }, { name = "ujson" }, @@ -1081,16 +1054,17 @@ all = [ [[package]] name = "fastapi-cli" -version = "0.0.14" +version = "0.0.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich-toolkit" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typer" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/13/11e43d630be84e51ba5510a6da6a11eb93b44b72caa796137c5dddda937b/fastapi_cli-0.0.14.tar.gz", hash = "sha256:ddfb5de0a67f77a8b3271af1460489bd4d7f4add73d11fbfac613827b0275274", size = 17994 } +sdist = { url = "https://files.pythonhosted.org/packages/99/75/9407a6b452be4c988feacec9c9d2f58d8f315162a6c7258d5a649d933ebe/fastapi_cli-0.0.16.tar.gz", hash = "sha256:e8a2a1ecf7a4e062e3b2eec63ae34387d1e142d4849181d936b23c4bdfe29073", size = 19447 } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/e8/bc8bbfd93dcc8e347ce98a3e654fb0d2e5f2739afb46b98f41a30c339269/fastapi_cli-0.0.14-py3-none-any.whl", hash = "sha256:e66b9ad499ee77a4e6007545cde6de1459b7f21df199d7f29aad2adaab168eca", size = 11151 }, + { url = "https://files.pythonhosted.org/packages/55/43/678528c19318394320ee43757648d5e0a8070cf391b31f69d931e5c840d2/fastapi_cli-0.0.16-py3-none-any.whl", hash = "sha256:addcb6d130b5b9c91adbbf3f2947fe115991495fdb442fe3e51b5fc6327df9f4", size = 12312 }, ] [package.optional-dependencies] @@ -1492,7 +1466,7 @@ dependencies = [ { name = "jeepney", marker = "sys_platform == 'linux'" }, { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", version = "3.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and sys_platform == 'linux'" }, - { name = "secretstorage", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, + { name = "secretstorage", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750 } wheels = [ @@ -2167,7 +2141,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -2175,9 +2149,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383 } +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431 }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400 }, ] [package.optional-dependencies] @@ -2187,110 +2161,105 @@ email = [ [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197 }, - { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909 }, - { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905 }, - { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938 }, - { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710 }, - { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445 }, - { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875 }, - { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329 }, - { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658 }, - { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777 }, - { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705 }, - { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464 }, - { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497 }, - { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062 }, - { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301 }, - { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728 }, - { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238 }, - { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424 }, - { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047 }, - { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163 }, - { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585 }, - { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109 }, - { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078 }, - { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737 }, - { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160 }, - { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883 }, - { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026 }, - { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043 }, - { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699 }, - { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121 }, - { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590 }, - { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869 }, - { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169 }, - { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165 }, - { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067 }, - { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997 }, - { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187 }, - { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204 }, - { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536 }, - { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132 }, - { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483 }, - { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688 }, - { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807 }, - { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669 }, - { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629 }, - { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049 }, - { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409 }, - { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635 }, - { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284 }, - { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566 }, - { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809 }, - { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119 }, - { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398 }, - { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735 }, - { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209 }, - { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324 }, - { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515 }, - { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819 }, - { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866 }, - { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034 }, - { url = "https://files.pythonhosted.org/packages/2c/36/f86d582be5fb47d4014506cd9ddd10a3979b6d0f2d237aa6ad3e7033b3ea/pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062", size = 2112444 }, - { url = "https://files.pythonhosted.org/packages/ba/e5/63c521dc2dd106ba6b5941c080617ea9db252f8a7d5625231e9d761bc28c/pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338", size = 1938218 }, - { url = "https://files.pythonhosted.org/packages/30/56/c84b638a3e6e9f5a612b9f5abdad73182520423de43669d639ed4f14b011/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d", size = 1971449 }, - { url = "https://files.pythonhosted.org/packages/99/c6/e974aade34fc7a0248fdfd0a373d62693502a407c596ab3470165e38183c/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7", size = 2054023 }, - { url = "https://files.pythonhosted.org/packages/4f/91/2507dda801f50980a38d1353c313e8f51349a42b008e63a4e45bf4620562/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166", size = 2251614 }, - { url = "https://files.pythonhosted.org/packages/b2/ad/05d886bc96938f4d31bed24e8d3fc3496d9aea7e77bcff6e4b93127c6de7/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e", size = 2378807 }, - { url = "https://files.pythonhosted.org/packages/6a/0a/d26e1bb9a80b9fc12cc30d9288193fbc9e60a799e55843804ee37bd38a9c/pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891", size = 2076891 }, - { url = "https://files.pythonhosted.org/packages/d9/66/af014e3a294d9933ebfecf11a5d858709014bd2315fa9616195374dd82f0/pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb", size = 2192179 }, - { url = "https://files.pythonhosted.org/packages/e7/3e/79783f97024037d0ea6e1b3ebcd761463a925199e04ce2625727e9f27d06/pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514", size = 2153067 }, - { url = "https://files.pythonhosted.org/packages/b3/97/ea83b0f87d9e742405fb687d5682e7a26334eef2c82a2de06bfbdc305fab/pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005", size = 2319048 }, - { url = "https://files.pythonhosted.org/packages/64/4a/36d8c966a0b086362ac10a7ee75978ed15c5f2dfdfc02a1578d19d3802fb/pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8", size = 2321830 }, - { url = "https://files.pythonhosted.org/packages/a2/6e/d80cc4909dde5f6842861288aa1a7181e7afbfc50940c862ed2848df15bd/pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb", size = 1976706 }, - { url = "https://files.pythonhosted.org/packages/29/ee/5bda8d960d4a8b24a7eeb8a856efa9c865a7a6cab714ed387b29507dc278/pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332", size = 2027640 }, - { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139 }, - { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674 }, - { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398 }, - { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674 }, - { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087 }, - { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387 }, - { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495 }, - { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008 }, - { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739 }, - { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549 }, - { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093 }, - { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971 }, - { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939 }, - { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400 }, - { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840 }, - { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135 }, - { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721 }, - { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608 }, - { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986 }, - { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516 }, - { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146 }, - { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296 }, - { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386 }, - { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775 }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298 }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475 }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815 }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567 }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442 }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956 }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253 }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050 }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178 }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833 }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156 }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378 }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622 }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873 }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826 }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869 }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890 }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740 }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021 }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378 }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761 }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303 }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355 }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875 }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549 }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305 }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902 }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990 }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003 }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200 }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578 }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504 }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816 }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366 }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698 }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603 }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591 }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068 }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908 }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145 }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179 }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403 }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206 }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258 }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917 }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186 }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146 }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788 }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133 }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852 }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679 }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766 }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005 }, + { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999 }, + { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745 }, + { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220 }, + { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296 }, + { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548 }, + { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571 }, + { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175 }, + { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203 }, + { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191 }, + { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907 }, + { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174 }, + { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353 }, + { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698 }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441 }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291 }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632 }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905 }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495 }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388 }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879 }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017 }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351 }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363 }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615 }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369 }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218 }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951 }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428 }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009 }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980 }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865 }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256 }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762 }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141 }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317 }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992 }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302 }, ] [[package]] @@ -2310,16 +2279,36 @@ wheels = [ name = "pydantic-settings" version = "2.11.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, + { name = "pydantic", marker = "python_full_version < '3.10'" }, + { name = "python-dotenv", marker = "python_full_version < '3.10'" }, + { name = "typing-inspection", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394 } wheels = [ { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608 }, ] +[[package]] +name = "pydantic-settings" +version = "2.12.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "python-dotenv", marker = "python_full_version >= '3.10'" }, + { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880 }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -2331,71 +2320,115 @@ wheels = [ [[package]] name = "pynacl" -version = "1.6.0" +version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/c6/a3124dee667a423f2c637cfd262a54d67d8ccf3e160f3c50f622a85b7723/pynacl-1.6.0.tar.gz", hash = "sha256:cb36deafe6e2bce3b286e5d1f3e1c246e0ccdb8808ddb4550bb2792f2df298f2", size = 3505641 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/46/aeca065d227e2265125aea590c9c47fbf5786128c9400ee0eb7c88931f06/pynacl-1.6.1.tar.gz", hash = "sha256:8d361dac0309f2b6ad33b349a56cd163c98430d409fa503b10b70b3ad66eaa1d", size = 3506616 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/37/87c72df19857c5b3b47ace6f211a26eb862ada495cc96daa372d96048fca/pynacl-1.6.0-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:f4b3824920e206b4f52abd7de621ea7a44fd3cb5c8daceb7c3612345dfc54f2e", size = 382610 }, - { url = "https://files.pythonhosted.org/packages/0c/64/3ce958a5817fd3cc6df4ec14441c43fd9854405668d73babccf77f9597a3/pynacl-1.6.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16dd347cdc8ae0b0f6187a2608c0af1c8b7ecbbe6b4a06bff8253c192f696990", size = 798744 }, - { url = "https://files.pythonhosted.org/packages/e4/8a/3f0dd297a0a33fa3739c255feebd0206bb1df0b44c52fbe2caf8e8bc4425/pynacl-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16c60daceee88d04f8d41d0a4004a7ed8d9a5126b997efd2933e08e93a3bd850", size = 1397879 }, - { url = "https://files.pythonhosted.org/packages/41/94/028ff0434a69448f61348d50d2c147dda51aabdd4fbc93ec61343332174d/pynacl-1.6.0-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25720bad35dfac34a2bcdd61d9e08d6bfc6041bebc7751d9c9f2446cf1e77d64", size = 833907 }, - { url = "https://files.pythonhosted.org/packages/52/bc/a5cff7f8c30d5f4c26a07dfb0bcda1176ab8b2de86dda3106c00a02ad787/pynacl-1.6.0-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bfaa0a28a1ab718bad6239979a5a57a8d1506d0caf2fba17e524dbb409441cf", size = 1436649 }, - { url = "https://files.pythonhosted.org/packages/7a/20/c397be374fd5d84295046e398de4ba5f0722dc14450f65db76a43c121471/pynacl-1.6.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ef214b90556bb46a485b7da8258e59204c244b1b5b576fb71848819b468c44a7", size = 817142 }, - { url = "https://files.pythonhosted.org/packages/12/30/5efcef3406940cda75296c6d884090b8a9aad2dcc0c304daebb5ae99fb4a/pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:49c336dd80ea54780bcff6a03ee1a476be1612423010472e60af83452aa0f442", size = 1401794 }, - { url = "https://files.pythonhosted.org/packages/be/e1/a8fe1248cc17ccb03b676d80fa90763760a6d1247da434844ea388d0816c/pynacl-1.6.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f3482abf0f9815e7246d461fab597aa179b7524628a4bc36f86a7dc418d2608d", size = 772161 }, - { url = "https://files.pythonhosted.org/packages/a3/76/8a62702fb657d6d9104ce13449db221a345665d05e6a3fdefb5a7cafd2ad/pynacl-1.6.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:140373378e34a1f6977e573033d1dd1de88d2a5d90ec6958c9485b2fd9f3eb90", size = 1370720 }, - { url = "https://files.pythonhosted.org/packages/6d/38/9e9e9b777a1c4c8204053733e1a0269672c0bd40852908c9ad6b6eaba82c/pynacl-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6b393bc5e5a0eb86bb85b533deb2d2c815666665f840a09e0aa3362bb6088736", size = 791252 }, - { url = "https://files.pythonhosted.org/packages/63/ef/d972ce3d92ae05c9091363cf185e8646933f91c376e97b8be79ea6e96c22/pynacl-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a25cfede801f01e54179b8ff9514bd7b5944da560b7040939732d1804d25419", size = 1362910 }, - { url = "https://files.pythonhosted.org/packages/35/2c/ee0b373a1861f66a7ca8bdb999331525615061320dd628527a50ba8e8a60/pynacl-1.6.0-cp38-abi3-win32.whl", hash = "sha256:dcdeb41c22ff3c66eef5e63049abf7639e0db4edee57ba70531fc1b6b133185d", size = 226461 }, - { url = "https://files.pythonhosted.org/packages/75/f7/41b6c0b9dd9970173b6acc026bab7b4c187e4e5beef2756d419ad65482da/pynacl-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:cf831615cc16ba324240de79d925eacae8265b7691412ac6b24221db157f6bd1", size = 238802 }, - { url = "https://files.pythonhosted.org/packages/8e/0f/462326910c6172fa2c6ed07922b22ffc8e77432b3affffd9e18f444dbfbb/pynacl-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:84709cea8f888e618c21ed9a0efdb1a59cc63141c403db8bf56c469b71ad56f2", size = 183846 }, + { url = "https://files.pythonhosted.org/packages/49/41/3cfb3b4f3519f6ff62bf71bf1722547644bcfb1b05b8fdbdc300249ba113/pynacl-1.6.1-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:a6f9fd6d6639b1e81115c7f8ff16b8dedba1e8098d2756275d63d208b0e32021", size = 387591 }, + { url = "https://files.pythonhosted.org/packages/18/21/b8a6563637799f617a3960f659513eccb3fcc655d5fc2be6e9dc6416826f/pynacl-1.6.1-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e49a3f3d0da9f79c1bec2aa013261ab9fa651c7da045d376bd306cf7c1792993", size = 798866 }, + { url = "https://files.pythonhosted.org/packages/e8/6c/dc38033bc3ea461e05ae8f15a81e0e67ab9a01861d352ae971c99de23e7c/pynacl-1.6.1-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7713f8977b5d25f54a811ec9efa2738ac592e846dd6e8a4d3f7578346a841078", size = 1398001 }, + { url = "https://files.pythonhosted.org/packages/9f/05/3ec0796a9917100a62c5073b20c4bce7bf0fea49e99b7906d1699cc7b61b/pynacl-1.6.1-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a3becafc1ee2e5ea7f9abc642f56b82dcf5be69b961e782a96ea52b55d8a9fc", size = 834024 }, + { url = "https://files.pythonhosted.org/packages/f0/b7/ae9982be0f344f58d9c64a1c25d1f0125c79201634efe3c87305ac7cb3e3/pynacl-1.6.1-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ce50d19f1566c391fedc8dc2f2f5be265ae214112ebe55315e41d1f36a7f0a9", size = 1436766 }, + { url = "https://files.pythonhosted.org/packages/b4/51/b2ccbf89cf3025a02e044dd68a365cad593ebf70f532299f2c047d2b7714/pynacl-1.6.1-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:543f869140f67d42b9b8d47f922552d7a967e6c116aad028c9bfc5f3f3b3a7b7", size = 817275 }, + { url = "https://files.pythonhosted.org/packages/a8/6c/dd9ee8214edf63ac563b08a9b30f98d116942b621d39a751ac3256694536/pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a2bb472458c7ca959aeeff8401b8efef329b0fc44a89d3775cffe8fad3398ad8", size = 1401891 }, + { url = "https://files.pythonhosted.org/packages/0f/c1/97d3e1c83772d78ee1db3053fd674bc6c524afbace2bfe8d419fd55d7ed1/pynacl-1.6.1-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3206fa98737fdc66d59b8782cecc3d37d30aeec4593d1c8c145825a345bba0f0", size = 772291 }, + { url = "https://files.pythonhosted.org/packages/4d/ca/691ff2fe12f3bb3e43e8e8df4b806f6384593d427f635104d337b8e00291/pynacl-1.6.1-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:53543b4f3d8acb344f75fd4d49f75e6572fce139f4bfb4815a9282296ff9f4c0", size = 1370839 }, + { url = "https://files.pythonhosted.org/packages/30/27/06fe5389d30391fce006442246062cc35773c84fbcad0209fbbf5e173734/pynacl-1.6.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:319de653ef84c4f04e045eb250e6101d23132372b0a61a7acf91bac0fda8e58c", size = 791371 }, + { url = "https://files.pythonhosted.org/packages/2c/7a/e2bde8c9d39074a5aa046c7d7953401608d1f16f71e237f4bef3fb9d7e49/pynacl-1.6.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:262a8de6bba4aee8a66f5edf62c214b06647461c9b6b641f8cd0cb1e3b3196fe", size = 1363031 }, + { url = "https://files.pythonhosted.org/packages/dd/b6/63fd77264dae1087770a1bb414bc604470f58fbc21d83822fc9c76248076/pynacl-1.6.1-cp38-abi3-win32.whl", hash = "sha256:9fd1a4eb03caf8a2fe27b515a998d26923adb9ddb68db78e35ca2875a3830dde", size = 226585 }, + { url = "https://files.pythonhosted.org/packages/12/c8/b419180f3fdb72ab4d45e1d88580761c267c7ca6eda9a20dcbcba254efe6/pynacl-1.6.1-cp38-abi3-win_amd64.whl", hash = "sha256:a569a4069a7855f963940040f35e87d8bc084cb2d6347428d5ad20550a0a1a21", size = 238923 }, + { url = "https://files.pythonhosted.org/packages/35/76/c34426d532e4dce7ff36e4d92cb20f4cbbd94b619964b93d24e8f5b5510f/pynacl-1.6.1-cp38-abi3-win_arm64.whl", hash = "sha256:5953e8b8cfadb10889a6e7bd0f53041a745d1b3d30111386a1bb37af171e6daf", size = 183970 }, ] [[package]] name = "pytest" version = "8.4.2" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 } wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 }, ] +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668 }, +] + [[package]] name = "pytest-asyncio" version = "1.2.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, - { name = "pytest" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "backports-asyncio-runner", marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119 } wheels = [ { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095 }, ] +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075 }, +] + [[package]] name = "pytest-cov" version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, - { name = "coverage", version = "7.11.0", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", version = "7.11.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, { name = "pluggy" }, - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328 } wheels = [ @@ -2407,7 +2440,8 @@ name = "pytest-mock" version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036 } wheels = [ @@ -2602,141 +2636,151 @@ wheels = [ [[package]] name = "rignore" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/b5/1fe06acc517315fba13db19039e77a2b9689451e0b5b02e03f26f05f24ec/rignore-0.7.2.tar.gz", hash = "sha256:b343749a59b53db30be1180ffab6995a914a244860e31a5cbea25bb647c38a61", size = 15254 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/86/72c5e2315cbd0c86beea3f08642d4a0aa9dd52d9cd4f96efc82e366a522a/rignore-0.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:108495cdba5b5b857901a39f3b5bc3aede23f530539b268f90071e4733eca54f", size = 901209 }, - { url = "https://files.pythonhosted.org/packages/e9/6d/8075f3c59a7be423c02a61760684f91fdd5d05d8e225e2a1ce0d169121f3/rignore-0.7.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:503a2e2574f74270cae29aa2594db831f2fff600e172ec1d8a177e5ee5957f09", size = 874689 }, - { url = "https://files.pythonhosted.org/packages/a6/40/81ec784f6d17a48e0ea667e88a221a214890ea1fdadbcb93dfc196f2c225/rignore-0.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6059bbab894f3376e4ace5a25bcb357cbd7b232aa64418caf4ee95f9057f5970", size = 1177799 }, - { url = "https://files.pythonhosted.org/packages/d7/e8/b9140c7d0106a154ace2414f42d2db5ef0d6268ab39b0f6adbbbb612d214/rignore-0.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf0e26847d9f547bba421d23e809ae68b6db3daf92e1946fa62b70667c60f1f4", size = 943792 }, - { url = "https://files.pythonhosted.org/packages/59/7d/8b2f177f084cc7ef826ffa0444a263cba26ade9f309ab42400f65117c263/rignore-0.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc706824b5b9e6ea6dbb3b67fe2fcc9cc3afe793eb8c9a622e4ed4fa97f967de", size = 959589 }, - { url = "https://files.pythonhosted.org/packages/4b/21/0342ef5e5ace366dd4e3c9e78098cc9cd1e1fba3a63ce4f5ac08989aaccd/rignore-0.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:054e3656e447801e650be95c781c8e0b1a1f62fc7f798713035d466cde18b7fa", size = 985682 }, - { url = "https://files.pythonhosted.org/packages/0c/cd/ca05616d5cc0b05ac948c324dedc6b592d9df9946e67fd61f0c0504f5777/rignore-0.7.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4888621136c4a62522600373fe63cf30bdec6c9c29429ac5ebf54a8fab2b4b4", size = 1081589 }, - { url = "https://files.pythonhosted.org/packages/b1/dd/eba96d3cfa1da77cafddca242ae53e0ef5e5c5b76d16255029d8108433f6/rignore-0.7.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:365faa243acc5ae3db48aa5b50a2f7de2dcb7c059dae343bef345f614ad50035", size = 1138752 }, - { url = "https://files.pythonhosted.org/packages/6f/1d/98f706b67a46d64f66db046b56d8d1f152f1886bbcf8027e81939aec8fed/rignore-0.7.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7f2447482a91ecde9e785b1d43a0b620d1d80bbe3ae38738727c0e3a4dcf538c", size = 1118389 }, - { url = "https://files.pythonhosted.org/packages/73/d6/2fd68e797d3efc4de5d3299121b34d6902d7ec18927e13a2f8d46486411b/rignore-0.7.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7488f817a1723f7a98b077ffa53ea675610db4f804c10c910a5b7ec18a709491", size = 1125667 }, - { url = "https://files.pythonhosted.org/packages/7b/10/646e1cc7cb080cb0052aaa5c87e13c6820e1ef164d6b98d322d94f6beb7a/rignore-0.7.2-cp310-cp310-win32.whl", hash = "sha256:6d2805a0d6b6dd4b3fe044664e9a9ca74dcd04321f637a8a687d5db20cc8dabf", size = 646820 }, - { url = "https://files.pythonhosted.org/packages/fc/18/f015cd17a406d025e50f543b645551118f3fb03dab9137be9eafb7285ad7/rignore-0.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9aad65debb5efe2bd3ed7053f1ddfd7e830b05651c17d5095305849ecf6404f", size = 727029 }, - { url = "https://files.pythonhosted.org/packages/d2/1e/34f11b4ebc331fc8f137d2b65304667a58bd2b321ce6309ac1e6f7f1c9b2/rignore-0.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ed6ec2d50664865feea344b2e39eaad697f0f2b1676a26add1b458e416120a2b", size = 891771 }, - { url = "https://files.pythonhosted.org/packages/d0/a8/4240d08eb693908451bcb6efc27e1ae936dee8b1adfd5fbcc7f7668fb961/rignore-0.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9bad1790c32bf1f84bed6f2750933cfe67be056da074ed98a8808f7fb6d0aae0", size = 823881 }, - { url = "https://files.pythonhosted.org/packages/19/a7/162a821b67e3ef0444c8713ae28c6a66f3ffed29197b3927ae5513020591/rignore-0.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ac56757af3b224ffb20368f033007404676d4e211d85a0b95b6b57cd94898ec", size = 901542 }, - { url = "https://files.pythonhosted.org/packages/3c/b3/6d5ae8b7b2fb94f5a3962e281843c983c8ed0a57bc37ac8ef893f581e460/rignore-0.7.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11284ae105e5e80539a420b194d8624940f7836caafb9cde45e2b590ed957ebf", size = 874673 }, - { url = "https://files.pythonhosted.org/packages/4c/89/83063fb4d4b57d00cb9b6a04878e5971830518c001bb44ad1b93ace4f476/rignore-0.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5643cf2857a80744bd43752fac245de5f65effaafb0bac2736d62ded39b82d8", size = 1177702 }, - { url = "https://files.pythonhosted.org/packages/e9/53/6ac52ead4dbc99acb5a1de1794ef522f93dacf535d017469d64ff84f4262/rignore-0.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:193e6414107634546416fa80e0ca67c5e5899eb5fcd699f444138b8f25d557d1", size = 944091 }, - { url = "https://files.pythonhosted.org/packages/ec/7b/fcf87d8050f103e377357bd599d0d976d67623c1a1f87d454e9a97a7e605/rignore-0.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:059932ba1bf00130bce0ee8569e44b466fd4e249e3befffdca50843e0e63d7b5", size = 959575 }, - { url = "https://files.pythonhosted.org/packages/73/fa/d3aa50f33376c6ec44ba96af6d576aa5d5fb16087e39fb2646eca789ef05/rignore-0.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0de4bcf93471999260885c123ebac59ddab96257649b3a0cb7daefbfa7ed7714", size = 985688 }, - { url = "https://files.pythonhosted.org/packages/b2/58/761abdf261b4aefa0e41a5d52093fbc7e6a53ce52fdb6dc5403c9ad43558/rignore-0.7.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9049a42a9c34ee4f191038f653e14eb8bdaf3e913c7ebfc7cb0945896ab8cd2a", size = 1082297 }, - { url = "https://files.pythonhosted.org/packages/d2/01/d9a396d7535f3b0fce0f9e0f6317e498a70d801831dabf2c3648835e9d95/rignore-0.7.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f11e61dcaf60273661f233792a6fb144a998a43dd92f9f007e3fda57fbc2178a", size = 1138750 }, - { url = "https://files.pythonhosted.org/packages/a0/8b/821a446169d72280295b76e946d34e4a58aa2ae75e81e317816c9146071e/rignore-0.7.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:844af954b26af2ace2c333b78691acb939e55b3aa7ad5ca0c4cd27085a1088a2", size = 1118562 }, - { url = "https://files.pythonhosted.org/packages/b7/97/68ddb8f52efde41e0079e494eda0ac0168993e9a1a85470d2510575d566f/rignore-0.7.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:999d746d2c345f050b063be57481d3658a4bb561d27685db866c8ebb218d6199", size = 1125516 }, - { url = "https://files.pythonhosted.org/packages/6b/0e/d0de1198a246bd9418945d415f4cf66a4a325ecc5fa6832d3c8648bdf04c/rignore-0.7.2-cp311-cp311-win32.whl", hash = "sha256:b63c880029b3bfb7cfacc43312bae1b73763078ac29f249074fb86e60d2206ac", size = 646549 }, - { url = "https://files.pythonhosted.org/packages/ce/56/c3635d04c29edf7f56ff6b1e30c9d05c4b8a5982f3adea4fc87efe3af260/rignore-0.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:c8c1b971689fbd4cdb88ab63626c45ae22264f8927ab12013764fb8e242b3908", size = 727143 }, - { url = "https://files.pythonhosted.org/packages/19/63/7f16c23743cd7b3a31cf7f02ce9e8b8127873f75250cbdd451e87f1eab4e/rignore-0.7.2-cp311-cp311-win_arm64.whl", hash = "sha256:7de5ce5fd19009656712d70f9b9ab0eeeed2ca126c7080ae7cee87a9256803c0", size = 657622 }, - { url = "https://files.pythonhosted.org/packages/a2/aa/6f21e66910ec1745dec1d1b0ffb97977bfcf76b520c60079c0fca050c702/rignore-0.7.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2220958d76332fa16aff92b71754ba10601cb2ba66723f87fb64e09d7a8d121a", size = 889758 }, - { url = "https://files.pythonhosted.org/packages/c1/07/20fd5d14677bb34d6ee93f331f4b6bbb593806f8ebda3c0464074b3e8b20/rignore-0.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4dd674966c8219b82ea165e3717d629d5a08aad80697489a1bf750c8517f3b9", size = 820405 }, - { url = "https://files.pythonhosted.org/packages/a4/8b/412ab22fba7eeabf7cbd6d5098ee00989dcfcae8b68a8bf3f5c0c21812a0/rignore-0.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25323d7d93fa4d93fb2149dbedde911907de933a573b6c475e2d2b248384b42b", size = 901575 }, - { url = "https://files.pythonhosted.org/packages/32/3e/acc817bc5267ffc42ae65f80f12cfa804db6be6ba58c8f3af06a60f5ced7/rignore-0.7.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b429d97fbe3e2c8180dd10684668d2da624a2f9b2e8cb625dc12cb3c77d1f22", size = 874033 }, - { url = "https://files.pythonhosted.org/packages/05/f8/709846896df0ca1119409339fe9292bdf8f8b142eaee90fc52468844adc5/rignore-0.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbcac80502ca335f38f6bbbacb9b4818622649235d028b0eb01a931b77a38dc2", size = 1176041 }, - { url = "https://files.pythonhosted.org/packages/a4/2f/e4a92d18e4cfcf6f83b82d2928a8f8c5d3a6c8306f4791d89c88cdd80a5f/rignore-0.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35a8a220e7a38f672da8ec58126e52ffabfc0d5f833e2047e89f2fa0c2c0cc6a", size = 944453 }, - { url = "https://files.pythonhosted.org/packages/7a/12/ca2d6fc7b68c916fe6d68fc63230f4b60be7a5fc8cebc62962ab342b60c7/rignore-0.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e541c1d4717ce5e938748c5302b6ebe63f4eed08bfca68a1861a214a9c8316f2", size = 959266 }, - { url = "https://files.pythonhosted.org/packages/b4/68/97693bbc3fdd65401f44377097040d285f42e6013ba5eb2da8b97fcf2dd4/rignore-0.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a35db5bb7ab3f9a37e131a97bc3388ad3af0fc8a90b9d91799f33afde2a8e15", size = 985338 }, - { url = "https://files.pythonhosted.org/packages/e0/95/b580fdb7666ffe17c52258e48b8d1217be53eaf69f7ad5c2ecc680782836/rignore-0.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9cee5d75c8b1bc1450855dde82c6e3ac2d258238410eba710e2326c2e2cf4cce", size = 1081155 }, - { url = "https://files.pythonhosted.org/packages/a1/d7/08d067e0d0011bf18c32833b3f82a119bc191625fb4156db589bb5f3d826/rignore-0.7.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fd1062ecc16bb8cc32d7ad2e13c04fc3db74919ed8c4d79a9e55b32d50b40c0f", size = 1137958 }, - { url = "https://files.pythonhosted.org/packages/c0/d1/6f3b624e671d2bc24c2a8450d30036a4f387bdae2b556604e0eb67ba3975/rignore-0.7.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e03c758f81f4084d37cd6095c837f24f4fcdc4238d2a921ae880cd0f21c02850", size = 1117831 }, - { url = "https://files.pythonhosted.org/packages/9d/e5/ef8eed0a0f4bab93f470a58430cea4e4b69bb6864bdcb9237276f08a133c/rignore-0.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8d27901bf65db37778990db08cca809cbd4e0be0ea08aef6eb850e4a624b1c1", size = 1125163 }, - { url = "https://files.pythonhosted.org/packages/44/18/11f4af8d56e1941bb8c32749fb15f43b11a855ac4ea0091796c5b289abdf/rignore-0.7.2-cp312-cp312-win32.whl", hash = "sha256:85b511bcd85cc521bfda40ca60a08e35d82c2f4d87a33100f5308cc4e150708e", size = 646153 }, - { url = "https://files.pythonhosted.org/packages/a0/26/b1963edafa3ce974e70ca3fe914ea965c631e21a6cb63de7a87ba78deb89/rignore-0.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:bffd6b885c450ca6d7ab3bb9ff6b1d910a74aec45bb427580c2a91089d2127cb", size = 726157 }, - { url = "https://files.pythonhosted.org/packages/ef/d4/1119b58862bb6e7918c28dd90511f5743a462590eced505caa71f3c7384a/rignore-0.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:036bb1597fab0ebc5082abfaf6b13eaced7769703a492c5772844edc34bc8f76", size = 656322 }, - { url = "https://files.pythonhosted.org/packages/e5/ce/c77d73a611a47b021b1536f7b49fe5593fec0b5e43934166e0c1fcfd1d4c/rignore-0.7.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2b98b8396f856f302e983664b1e2a13aee8c82d8ce0b34c2548a0e09a7c30d3c", size = 889368 }, - { url = "https://files.pythonhosted.org/packages/11/dc/bbbe0d23051605cd2197626d3a5212f376d5e0143881cdbf6632c8ecb38b/rignore-0.7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bfdfb906ad0f8f22d2685fb2b6465a078d78ee32e437dab4ab35080a2790c87b", size = 820141 }, - { url = "https://files.pythonhosted.org/packages/5d/62/ee54bc98dc986de7bf8cfddbb62670cbcbbfc21b4c53821421be96d561d0/rignore-0.7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3eef7c19742af7d3d813917a81af65ed9d7050f49f90fd78986a0243170531a", size = 901513 }, - { url = "https://files.pythonhosted.org/packages/a0/e5/e87a724794d23e1aaf7f9a5b2108fefb64703784e88f1082df36631c424a/rignore-0.7.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a73c14e1a131b17235fac9b148d549e6bd90abb4e9950baeb2df1e09e467bf6d", size = 873815 }, - { url = "https://files.pythonhosted.org/packages/07/02/7a804c2491d9794aef7052a4cdb6343ff6fdee5d68adc6e859f4f46363e8/rignore-0.7.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2927a547bd6934882fc92f55d77b8c4d548655612db502b509e495cbe9ef39eb", size = 1177286 }, - { url = "https://files.pythonhosted.org/packages/4a/6b/0b84972c4442b60d6afb450607708aa74e2b416f403e12541c65a3e49c50/rignore-0.7.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fce3b899a3a891744264edde217a8d3a9fc4e9f542afe1c4b88bfa8544509cca", size = 944310 }, - { url = "https://files.pythonhosted.org/packages/c0/35/abb0816263aaaee399730a701636c81090455203af67601cc409adb8d431/rignore-0.7.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ea5364e7e0a188ee794be4335eaad1df089b8226279b460c98d8b95c11b73d", size = 958713 }, - { url = "https://files.pythonhosted.org/packages/14/70/0573d0bcf3fb27b3960c601027db9e31338c56e3a899e6d1c649b872bb62/rignore-0.7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a118788ce31693c02629851b4431043d5949c506e15f45d7ccd0cdc3d3e65765", size = 985183 }, - { url = "https://files.pythonhosted.org/packages/72/03/f25ff93e3ede74e8c7908c675ba643ec67fb4fee48a4d8bcc2c2880c53b5/rignore-0.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:80c978468562464be9c64de6e086707103a727fec0ec88961d88bca91111f1a9", size = 1080365 }, - { url = "https://files.pythonhosted.org/packages/fd/0c/9a273bf389e0651f118e35f2f4acbe2ed0ceecb570f1ea49475e59d8149e/rignore-0.7.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ea0a073a7b9639be68d8269732630d1ddf55fb72f5e4faa0e1b3d2f46d9e6b48", size = 1137639 }, - { url = "https://files.pythonhosted.org/packages/c9/d5/009ce164e2ef31bc0cf5506337cd5eca495c7b5ea526cb4ccbbbfe8b9928/rignore-0.7.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a15dfd14b5d9118e1e4afbc5e951b1a5ce43185d1605aac5f46ad72a6c53952a", size = 1117566 }, - { url = "https://files.pythonhosted.org/packages/a9/3a/c2aed0787572cc0a0c5afcafb9bbd8827fb676fe89ca3a78cdf62e656f14/rignore-0.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bde72ba6474cea23ca9091a66959caaaa915091e472fff95ced1b341d7541300", size = 1124968 }, - { url = "https://files.pythonhosted.org/packages/63/fa/4ab82713918e6a8fc1ef9a609a19baeeb9ddc37e7ba10620045f10689c56/rignore-0.7.2-cp313-cp313-win32.whl", hash = "sha256:011c6ede35ad03c4f84c72c6535033f31c56543830222586e9ef09274b22688a", size = 646108 }, - { url = "https://files.pythonhosted.org/packages/ea/46/c91aac0466158973c8c9deb00ab2bbb870dabc726261dd786246bb62201c/rignore-0.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:26fb0c20f77e24b9dd361cce8c78c7b581fbceab8b2a06e4374c54f5ce63c475", size = 726268 }, - { url = "https://files.pythonhosted.org/packages/3e/41/815c603dff6512ec35ff7ff2b5d8a10f0884203eb71e8d22d5ce3c49bc71/rignore-0.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:4d7d33e36a4f53f1765d3340e126758a1cf232cba9f27d2458f806dad434793e", size = 656198 }, - { url = "https://files.pythonhosted.org/packages/9b/8a/74a0fe1478f22f114acbf1d804c613ad4d5bafad5aa48ef6378768a9b21f/rignore-0.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca096869b942123cecd216373482087a23596d84e6749629a3a26259ca7b21b1", size = 903010 }, - { url = "https://files.pythonhosted.org/packages/c5/02/573215e86f78719c16abcb21b3c2bdc2a479abdd86daa2464badd5f5bf39/rignore-0.7.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aada211261d1a17e743dc822136b47fd957c35cc09b1a2532fa3809a72ad27e6", size = 876265 }, - { url = "https://files.pythonhosted.org/packages/0f/0d/c980e1dd8b1d29b9fab821fb7ffe4317f17c15da56e542d85b3bd2343867/rignore-0.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32847375239a969b8de5feda57155e5e23e2e4f07f9cf739533cf8f36825475d", size = 1180444 }, - { url = "https://files.pythonhosted.org/packages/c8/6f/aea78120680c1b416bbe6021ed4cc67c5a919b61e3e21f948489e83831ce/rignore-0.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75a07516b3977573cbf221c7aab2e6d0dfbdac16f06b6b4e103bcc4917d04c81", size = 944903 }, - { url = "https://files.pythonhosted.org/packages/fd/95/b0639945297b3574f8b39fcfe620e1bc7ee9fb6bd84b2cebffe4a2343cd5/rignore-0.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a4e2d6f6650ffa4c6f36ad5ce0bb917031c3a604246d35014cf18b9e5332fb8", size = 961293 }, - { url = "https://files.pythonhosted.org/packages/0b/1a/77a53d94e493eb90908b1c6f2cbc14a7f54c2f793eb6e90ca5b8aca535d5/rignore-0.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40e0b21fdeec139d01b2e5303e9d4e90e5be5c88691828b83415d654252cefd5", size = 987518 }, - { url = "https://files.pythonhosted.org/packages/3f/08/41c2a4bf2f87370957a3f99f96953fb8993cf717b1dc0eb52781950b5eaa/rignore-0.7.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:06eea83f769528999e44f47d034f37f749b0b71e1f46eac77464bf28d7c896fa", size = 1083348 }, - { url = "https://files.pythonhosted.org/packages/d6/69/db5aa8ac4ee643d1580b7bf16107a570bd1e01a43fa40a51554ebf3408a9/rignore-0.7.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0abd3773cd4cd463a7a30049b21f0709b66069e0038d33a56af0115d1cd4ae7a", size = 1139809 }, - { url = "https://files.pythonhosted.org/packages/e5/38/dd684882dabd8d5f5f61d6144bc8f163a1e8572403453c103418d5b39b80/rignore-0.7.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce6e00b77c3166de5284da675a13efa80d3dc1d3a1df67ed7489938438084f9c", size = 1119944 }, - { url = "https://files.pythonhosted.org/packages/e8/b4/137e078e461792a2f78789dd47bde985dd66175c96ac4aaeb1a2c3e53616/rignore-0.7.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8b167857f62420137fd796cff9337ca240e891a63efea33e23031dce88972d7a", size = 1126589 }, - { url = "https://files.pythonhosted.org/packages/4a/80/b2469aec4cc1d0133c2dfb944b297e567f9850e4b5f83f8ad167d00728fa/rignore-0.7.2-cp39-cp39-win32.whl", hash = "sha256:7615789752db646c36a130f9946dffac10a26c2784deccb892f0682eeeec5a74", size = 648268 }, - { url = "https://files.pythonhosted.org/packages/42/aa/9c16a363989e3cb908cb47574cd765d40fe86358bb29409560ce3dd4dd9b/rignore-0.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1aee47babfceea6b8f78e4001190bfa547443946c3caf65a969e32c31018922", size = 728786 }, - { url = "https://files.pythonhosted.org/packages/4b/08/482161a0e747a941806e4c16a1a46c96fb7384b2f085a9aab4f753cacc97/rignore-0.7.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a52a153f74d3bd015c18a1175c8dc213f6efbfde587119600f6a0eac0ab0e99c", size = 903588 }, - { url = "https://files.pythonhosted.org/packages/73/ca/4f34f124c2074a6f309606d9992bb869416859946db60f3b435435dbb8aa/rignore-0.7.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa88e718bf71deefd5addc8bcab7fd1e2b3c5b5b13548f0639dc38e30cbeebff", size = 876621 }, - { url = "https://files.pythonhosted.org/packages/46/52/f6cfb42c727cac2cc71e27daee07025345ae4c0e243f7aa5c84c67248ffd/rignore-0.7.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efda522bbd7edf1eda56ee66c47e748c263ee281c0f104cb72dd39b92c42957e", size = 1178074 }, - { url = "https://files.pythonhosted.org/packages/a5/03/e389caf5723e37be9d7b9a2f92c86521ed2a665ca1b8ca79f0145d95fc3d/rignore-0.7.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1869f57ae99dc8cc50b6db0eac39a4901a678b443571604f335b0bc80125b03", size = 945450 }, - { url = "https://files.pythonhosted.org/packages/51/7a/1ca8e857bca2e5886398548503d77ebec4f5acfd6eb1abe6726d9f3df42a/rignore-0.7.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b84ef63188e86586ccc1cfe0286062874480b6001db8237e3b68fc4bca7ef829", size = 961314 }, - { url = "https://files.pythonhosted.org/packages/06/e3/c97ddd463f8a3cc0aec1962a7c862b055c24bc8b4f998594b3cdd92be01c/rignore-0.7.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20a1a73654f762e14a2ecd1abb26b0e94ee899faf514dbcc5b9a85ca0ddfc5f4", size = 987999 }, - { url = "https://files.pythonhosted.org/packages/f2/fa/07c19bbbfd7d2edb6b5d5ef8f3f7c6cd8b60e36c0069bae0d9cb4928c2c1/rignore-0.7.2-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:81e39085962f33cc28e45dac764105f2a87bff5f9da8268f48f6ce2bc09821ac", size = 1083754 }, - { url = "https://files.pythonhosted.org/packages/3e/67/807d5f73b5071332cbf291ac595f809dfb7df965b74eaf016276f7d5f685/rignore-0.7.2-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:da502f0e12447ba644c45dc3f399c63413806f0ef9701affc280fc45387d9c45", size = 1140166 }, - { url = "https://files.pythonhosted.org/packages/9c/dd/e698cbea22668ee8fd2a24030ce6ea9b288eba23c3d98d4833dfe1aff0e8/rignore-0.7.2-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:acf9e30a034eb246673335b47af070c067d3520ba22e7b1eaeddf3e7456cb232", size = 1120450 }, - { url = "https://files.pythonhosted.org/packages/68/57/f6498864fec2df02c184e38a4f6bf47c1b9c664a119ee33dc950fd361497/rignore-0.7.2-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:b08ca8a7b462490298d68559157eb006b94535583d8e018dbd61530b63477a66", size = 1127142 }, - { url = "https://files.pythonhosted.org/packages/b5/2c/01237e1ad4fdc6bf370cd193730347f83573bf2094d2a99d58e122865c38/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57d65850b7d762a44e4a9b54379fc763042cb0cfbe42ce0d8735ca8bad64e92c", size = 902098 }, - { url = "https://files.pythonhosted.org/packages/37/e4/67fdd0fc28a7c77a0906814faa6e7f8de929897c722adb77944f7cd6338d/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0553d732ca6413e6c1949d0b793fea807cd5decc38163ec002e7f9faea98f279", size = 874886 }, - { url = "https://files.pythonhosted.org/packages/07/0d/740b9ee613e852728ae251eeecb92c7f40e8e17896e75f373ff66f764c83/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:760421831764eaaa7d278e10808a01f83da21414750f13084a9715f84050b3b6", size = 1176754 }, - { url = "https://files.pythonhosted.org/packages/6e/9a/bf6891dd85a860a7b9d5f5241db56e1c00a50625996f7638563a5efc2f62/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3634010a2e9f1307ed7ea6b30d2f5f6e620bb09d4ec4dc44761fa9b9d7d7ec54", size = 945090 }, - { url = "https://files.pythonhosted.org/packages/81/6b/3d2be74922963b932a2cced42ecb1876d717390c02a6b155e73bab9e878a/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:399224e352c573946e0d1eb2dc11191a2a00220576fa41493e00da99485b969a", size = 959835 }, - { url = "https://files.pythonhosted.org/packages/24/23/072b6d8c7a7c2479ae2bb8a51b8dba487821baa6d4a82e092e8d7b9b503c/rignore-0.7.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e2776241440dd5e92ea1cae6913d23d18f59e7428676d73534b8a29a21f4ca6a", size = 986706 }, - { url = "https://files.pythonhosted.org/packages/65/dd/93804cb0d26b04bffe7474d4c01f63e6b735d67998dbf9a6e8868666d984/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b84e15b1951314592b56421b78eac6324cc870645fdf15fc69f3ab6ae9bd4d71", size = 1082126 }, - { url = "https://files.pythonhosted.org/packages/13/7e/9f0c611b5c5e7c6902b07e3cf98032196c179126fc083d0e166fd8b043a1/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a9224806d74f9bce116d622d777b1a052b6e3a60458724f3e66a18a19e764e90", size = 1139089 }, - { url = "https://files.pythonhosted.org/packages/b1/f9/00e1c439804f288510c1ef6171afecd3fdd06d736edd8c5ee4b105d694ad/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:e8a6a19d11659ea29f8ac16bea2df4826c9c35bdd0d075182123934e45647903", size = 1119255 }, - { url = "https://files.pythonhosted.org/packages/67/2f/e17c92f7b6a38475a966fa684acda8617c50d21c9dc63297a54c568dcd14/rignore-0.7.2-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:68a25d918f5aab4f0af8530e171e5179252d4506bfc89785e5bdc67d8230d0a5", size = 1125901 }, - { url = "https://files.pythonhosted.org/packages/98/d4/2423229071da0e21b5f07c0d5be2eccbf5e3697c6437f1d4d871ff85f201/rignore-0.7.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e28e87d494c9d2889c2a6acaec3f750dfe28e907fa459e68858ffe7bc100e730", size = 903537 }, - { url = "https://files.pythonhosted.org/packages/36/62/72cf2fa40307a8641bc323c1e9f5c1725b472bd6db4566ab316e58232279/rignore-0.7.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:349d2bfb6472ee3cfc1cd162bc65f05a3066ea3a68c52235fcd41c9ae1be3132", size = 876565 }, - { url = "https://files.pythonhosted.org/packages/d5/a6/87d086921c4ed253edef048b04e9943aefccd3e934f90f25b88c1b33d094/rignore-0.7.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cfe08e516dc6540361a36d69d3f1b7a9f0989a3040fbea8955f73f4de97e464", size = 1180042 }, - { url = "https://files.pythonhosted.org/packages/b2/70/8244af506b2a30c423664a1854239ba2962822a52c4fea72f1c403ebecb0/rignore-0.7.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed7617a09d5d95733876dfff7f8f4ba9dd9d89fe61305100cd449579f9a7043e", size = 945747 }, - { url = "https://files.pythonhosted.org/packages/92/86/41cda571cb3b0ed6c5765ca9e43ead7363c529bab4850e66f6c6c2a77b18/rignore-0.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6659ce6a32d8ded24fc6d0fcdab3a36bebabdbbea19c177184c889cfade9d80e", size = 961080 }, - { url = "https://files.pythonhosted.org/packages/e6/d7/d232e5c0bc3e265050b92d273fd733abf3c810374412c68151fbb6c6a421/rignore-0.7.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ddb0852e6fc31c8d511f920d38a6b3ac9b7c1e8e5c6066817778583545ceb83", size = 988145 }, - { url = "https://files.pythonhosted.org/packages/8d/b4/ec45d51a81f058f9cfad3ebbc06e140b3ef4a99dcefc53c0a1d6d9468575/rignore-0.7.2-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c4c03c52034e47fd8e9bb4a96161e7845a18087de90615e3bcb5b92f465adb58", size = 1083682 }, - { url = "https://files.pythonhosted.org/packages/b1/2f/b6bd01aaad86e418ff8bce9061d3c81a09c690a68f5b652a2bc4d4883ffe/rignore-0.7.2-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:6c5d6a5620875460dcf344c47e876c86c959cec9931c8eae2babb7df0ba1c0ef", size = 1140213 }, - { url = "https://files.pythonhosted.org/packages/40/76/92e13e8d3a732198b2bd34f8c3e78be17e86f0d6a70afdb64d8ab2a5caa5/rignore-0.7.2-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7ba732e984491bcfa7239a2cab90d7feafe0b2056170482eb2ed2d2738a60878", size = 1120726 }, - { url = "https://files.pythonhosted.org/packages/aa/6b/54319f1cb81bfefff272a81953e65189918e92da01bbff2281974498d09a/rignore-0.7.2-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2b24a1d78767edde34748801a20e51720f9515e4357162cfc9dadadac577ce50", size = 1126817 }, +version = "0.7.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/7a/b970cd0138b0ece72eb28f086e933f9ed75b795716ad3de5ab22994b3b54/rignore-0.7.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f3c74a7e5ee77aea669c95fdb3933f2a6c7549893700082e759128a29cf67e45", size = 884999 }, + { url = "https://files.pythonhosted.org/packages/ca/05/23faca29616d8966ada63fb0e13c214107811fa9a0aba2275e4c7ca63bd5/rignore-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7202404958f5fe3474bac91f65350f0b1dde1a5e05089f2946549b7e91e79ec", size = 824824 }, + { url = "https://files.pythonhosted.org/packages/fa/2e/05a1e61f04cf2548524224f0b5f21ca19ea58f7273a863bac10846b8ff69/rignore-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bde7c5835fa3905bfb7e329a4f1d7eccb676de63da7a3f934ddd5c06df20597", size = 899121 }, + { url = "https://files.pythonhosted.org/packages/ff/35/71518847e10bdbf359badad8800e4681757a01f4777b3c5e03dbde8a42d8/rignore-0.7.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:626c3d4ba03af266694d25101bc1d8d16eda49c5feb86cedfec31c614fceca7d", size = 873813 }, + { url = "https://files.pythonhosted.org/packages/f6/c8/32ae405d3e7fd4d9f9b7838f2fcca0a5005bb87fa514b83f83fd81c0df22/rignore-0.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a43841e651e7a05a4274b9026cc408d1912e64016ede8cd4c145dae5d0635be", size = 1168019 }, + { url = "https://files.pythonhosted.org/packages/25/98/013c955982bc5b4719bf9a5bea58be317eea28aa12bfd004025e3cd7c000/rignore-0.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7978c498dbf7f74d30cdb8859fe612167d8247f0acd377ae85180e34490725da", size = 942822 }, + { url = "https://files.pythonhosted.org/packages/90/fb/9a3f3156c6ed30bcd597e63690353edac1fcffe9d382ad517722b56ac195/rignore-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d22f72ab695c07d2d96d2a645208daff17084441b5d58c07378c9dd6f9c4c87", size = 959820 }, + { url = "https://files.pythonhosted.org/packages/5e/b2/93bf609633021e9658acaff24cfb055d8cdaf7f5855d10ebb35307900dda/rignore-0.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5bd8e1a91ed1a789b2cbe39eeea9204a6719d4f2cf443a9544b521a285a295f", size = 985050 }, + { url = "https://files.pythonhosted.org/packages/69/bc/ec2d040469bdfd7b743df10f2201c5d285009a4263d506edbf7a06a090bb/rignore-0.7.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fc03efad5789365018e94ac4079f851a999bc154d1551c45179f7fcf45322", size = 1079164 }, + { url = "https://files.pythonhosted.org/packages/df/26/4b635f4ea5baf4baa8ba8eee06163f6af6e76dfbe72deb57da34bb24b19d/rignore-0.7.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ce2617fe28c51367fd8abfd4eeea9e61664af63c17d4ea00353d8ef56dfb95fa", size = 1139028 }, + { url = "https://files.pythonhosted.org/packages/6a/54/a3147ebd1e477b06eb24e2c2c56d951ae5faa9045b7b36d7892fec5080d9/rignore-0.7.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c4ad2cee85068408e7819a38243043214e2c3047e9bd4c506f8de01c302709e", size = 1119024 }, + { url = "https://files.pythonhosted.org/packages/fb/f4/27475db769a57cff18fe7e7267b36e6cdb5b1281caa185ba544171106cba/rignore-0.7.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:02cd240bfd59ecc3907766f4839cbba20530a2e470abca09eaa82225e4d946fb", size = 1128531 }, + { url = "https://files.pythonhosted.org/packages/97/32/6e782d3b352e4349fa0e90bf75b13cb7f11d8908b36d9e2b262224b65d9a/rignore-0.7.6-cp310-cp310-win32.whl", hash = "sha256:fe2bd8fa1ff555259df54c376abc73855cb02628a474a40d51b358c3a1ddc55b", size = 646817 }, + { url = "https://files.pythonhosted.org/packages/c0/8a/53185c69abb3bb362e8a46b8089999f820bf15655629ff8395107633c8ab/rignore-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:d80afd6071c78baf3765ec698841071b19e41c326f994cfa69b5a1df676f5d39", size = 727001 }, + { url = "https://files.pythonhosted.org/packages/25/41/b6e2be3069ef3b7f24e35d2911bd6deb83d20ed5642ad81d5a6d1c015473/rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c", size = 885285 }, + { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882 }, + { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362 }, + { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633 }, + { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633 }, + { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434 }, + { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461 }, + { url = "https://files.pythonhosted.org/packages/b5/87/1e1a145731f73bdb7835e11f80da06f79a00d68b370d9a847de979575e6d/rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049", size = 985323 }, + { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173 }, + { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012 }, + { url = "https://files.pythonhosted.org/packages/78/96/a9ca398a8af74bb143ad66c2a31303c894111977e28b0d0eab03867f1b43/rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e", size = 1118827 }, + { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182 }, + { url = "https://files.pythonhosted.org/packages/bd/f4/1526eb01fdc2235aca1fd9d0189bee4021d009a8dcb0161540238c24166e/rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5", size = 646547 }, + { url = "https://files.pythonhosted.org/packages/7c/c8/dda0983e1845706beb5826459781549a840fe5a7eb934abc523e8cd17814/rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a", size = 727139 }, + { url = "https://files.pythonhosted.org/packages/e3/47/eb1206b7bf65970d41190b879e1723fc6bbdb2d45e53565f28991a8d9d96/rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581", size = 657598 }, + { url = "https://files.pythonhosted.org/packages/0b/0e/012556ef3047a2628842b44e753bb15f4dc46806780ff090f1e8fe4bf1eb/rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8", size = 883488 }, + { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411 }, + { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821 }, + { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963 }, + { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216 }, + { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942 }, + { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787 }, + { url = "https://files.pythonhosted.org/packages/55/54/2ffea79a7c1eabcede1926347ebc2a81bc6b81f447d05b52af9af14948b9/rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20", size = 984245 }, + { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647 }, + { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186 }, + { url = "https://files.pythonhosted.org/packages/75/95/a86c84909ccc24af0d094b50d54697951e576c252a4d9f21b47b52af9598/rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca", size = 1117604 }, + { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725 }, + { url = "https://files.pythonhosted.org/packages/c7/28/fa5dcd1e2e16982c359128664e3785f202d3eca9b22dd0b2f91c4b3d242f/rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046", size = 646145 }, + { url = "https://files.pythonhosted.org/packages/26/87/69387fb5dd81a0f771936381431780b8cf66fcd2cfe9495e1aaf41548931/rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543", size = 726090 }, + { url = "https://files.pythonhosted.org/packages/24/5f/e8418108dcda8087fb198a6f81caadbcda9fd115d61154bf0df4d6d3619b/rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051", size = 656317 }, + { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057 }, + { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150 }, + { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406 }, + { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050 }, + { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835 }, + { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945 }, + { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067 }, + { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438 }, + { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365 }, + { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066 }, + { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036 }, + { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550 }, + { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097 }, + { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170 }, + { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184 }, + { url = "https://files.pythonhosted.org/packages/b9/b4/e7577504d926ced2d6a3fa5ec5f27756639a1ed58a6a3fbefcf3a5659721/rignore-0.7.6-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b3746bda73f2fe6a9c3ab2f20b792e7d810b30acbdba044313fbd2d0174802e7", size = 886535 }, + { url = "https://files.pythonhosted.org/packages/2b/74/098bc71a33e2997bc3291d500760123d23e3a6d354380d26c8a7ddc036de/rignore-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:67a99cf19a5137cc12f14b78dc1bb3f48500f1d5580702c623297d5297bf2752", size = 826621 }, + { url = "https://files.pythonhosted.org/packages/7b/73/5f8c276d71009a7e73fb3af6ec3bb930269efeae5830de5c796fa1fb020f/rignore-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9e851cfa87033c0c3fd9d35dd8b102aff2981db8bc6e0cab27b460bfe38bf3f", size = 900335 }, + { url = "https://files.pythonhosted.org/packages/0d/5f/dde3758084a087e6a5cd981c5277c6171d12127deed64fc4fbf12fb8ceaa/rignore-0.7.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e9b0def154665036516114437a5d603274e5451c0dc9694f622cc3b7e94603e7", size = 874274 }, + { url = "https://files.pythonhosted.org/packages/58/b9/da85646824ab728036378ce62c330316108a52f30f36e6c69cac6ceda376/rignore-0.7.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b81274a47e8121224f7f637392b5dfcd9558e32a53e67ba7d04007d8b5281da9", size = 1171639 }, + { url = "https://files.pythonhosted.org/packages/35/d1/8c12b779b7f0302c03c1d41511f2ab47012afecdfcd684fbec80af06b331/rignore-0.7.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d75d0b0696fb476664bea1169c8e67b13197750b91eceb4f10b3c7f379c7a204", size = 943985 }, + { url = "https://files.pythonhosted.org/packages/79/bf/c233a85d31e4f94b911e92ee7e2dd2b962a5c2528f5ebd79a702596f0626/rignore-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ad3aa4dca77cef9168d0c142f72376f5bd27d1d4b8a81561bd01276d3ad9fe1", size = 961707 }, + { url = "https://files.pythonhosted.org/packages/9d/eb/cadee9316a5f2a52b4aa7051967ecb94ec17938d6b425bd842d9317559eb/rignore-0.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00f8a59e19d219f44a93af7173de197e0d0e61c386364da20ebe98a303cbe38c", size = 986638 }, + { url = "https://files.pythonhosted.org/packages/d0/f0/2c3042c8c9639056593def5e99c3bfe850fbb9a38d061ba67b6314315bad/rignore-0.7.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6c682f3cdd741e7a30af2581f6a382ac910080977cd1f97c651467b6268352", size = 1080136 }, + { url = "https://files.pythonhosted.org/packages/fc/28/7237b9eb1257b593ee51cd7ef8eed7cc32f50ccff18cb4d7cfe1e6dc54d7/rignore-0.7.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ae4e93193f75ebf6b820241594a78f347785cfd5a5fbbac94634052589418352", size = 1139413 }, + { url = "https://files.pythonhosted.org/packages/a5/df/c3f382a31ad7ed68510b411c28fec42354d2c43fecb7c053d998ee9410ed/rignore-0.7.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1163d8b5d3a320d4d7cc8635213328850dc41f60e438c7869d540061adf66c98", size = 1120204 }, + { url = "https://files.pythonhosted.org/packages/9c/3d/e8585c4e9c0077255ba599684aee78326176ab13ff13805ea62aa7e3235f/rignore-0.7.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3e685f47b4c58b2df7dee81ebc1ec9dbb7f798b9455c3f22be6d75ac6bddee30", size = 1129757 }, + { url = "https://files.pythonhosted.org/packages/fd/56/852226c13f89ddbbf12d639900941dc55dcbcf79f5d15294796fd3279d73/rignore-0.7.6-cp39-cp39-win32.whl", hash = "sha256:2af6a0a76575220863cd838693c808a94e750640e0c8a3e9f707e93c2f131fdf", size = 648265 }, + { url = "https://files.pythonhosted.org/packages/cc/c6/14e7585dc453a870fe99b1270ee95e2adff02ea0d297cd6e2c4aa46cd43a/rignore-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:a326eab6db9ab85b4afb5e6eb28736a9f2b885a9246d9e8c1989bc693dd059a0", size = 728715 }, + { url = "https://files.pythonhosted.org/packages/85/12/62d690b4644c330d7ac0f739b7f078190ab4308faa909a60842d0e4af5b2/rignore-0.7.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3d3a523af1cd4ed2c0cba8d277a32d329b0c96ef9901fb7ca45c8cfaccf31a5", size = 887462 }, + { url = "https://files.pythonhosted.org/packages/05/bc/6528a0e97ed2bd7a7c329183367d1ffbc5b9762ae8348d88dae72cc9d1f5/rignore-0.7.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:990853566e65184a506e1e2af2d15045afad3ebaebb8859cb85b882081915110", size = 826918 }, + { url = "https://files.pythonhosted.org/packages/3e/2c/7d7bad116e09a04e9e1688c6f891fa2d4fd33f11b69ac0bd92419ddebeae/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cab9ff2e436ce7240d7ee301c8ef806ed77c1fd6b8a8239ff65f9bbbcb5b8a3", size = 900922 }, + { url = "https://files.pythonhosted.org/packages/09/ba/e5ea89fbde8e37a90ce456e31c5e9d85512cef5ae38e0f4d2426eb776a19/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1a6671b2082c13bfd9a5cf4ce64670f832a6d41470556112c4ab0b6519b2fc4", size = 876987 }, + { url = "https://files.pythonhosted.org/packages/d0/fb/93d14193f0ec0c3d35b763f0a000e9780f63b2031f3d3756442c2152622d/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2468729b4c5295c199d084ab88a40afcb7c8b974276805105239c07855bbacee", size = 1171110 }, + { url = "https://files.pythonhosted.org/packages/9e/46/08436312ff96ffa29cfa4e1a987efc37e094531db46ba5e9fda9bb792afd/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:775710777fd71e5fdf54df69cdc249996a1d6f447a2b5bfb86dbf033fddd9cf9", size = 943339 }, + { url = "https://files.pythonhosted.org/packages/34/28/3b3c51328f505cfaf7e53f408f78a1e955d561135d02f9cb0341ea99f69a/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4565407f4a77f72cf9d91469e75d15d375f755f0a01236bb8aaa176278cc7085", size = 961680 }, + { url = "https://files.pythonhosted.org/packages/5c/9e/cbff75c8676d4f4a90bd58a1581249d255c7305141b0868f0abc0324836b/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc44c33f8fb2d5c9da748de7a6e6653a78aa740655e7409895e94a247ffa97c8", size = 987045 }, + { url = "https://files.pythonhosted.org/packages/8c/25/d802d1d369502a7ddb8816059e7c79d2d913e17df975b863418e0aca4d8a/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8f32478f05540513c11923e8838afab9efef0131d66dca7f67f0e1bbd118af6a", size = 1080310 }, + { url = "https://files.pythonhosted.org/packages/43/f0/250b785c2e473b1ab763eaf2be820934c2a5409a722e94b279dddac21c7d/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1b63a3dd76225ea35b01dd6596aa90b275b5d0f71d6dc28fce6dd295d98614aa", size = 1140998 }, + { url = "https://files.pythonhosted.org/packages/f5/d6/bb42fd2a8bba6aea327962656e20621fd495523259db40cfb4c5f760f05c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fe6c41175c36554a4ef0994cd1b4dbd6d73156fca779066456b781707402048e", size = 1121178 }, + { url = "https://files.pythonhosted.org/packages/97/f4/aeb548374129dce3dc191a4bb598c944d9ed663f467b9af830315d86059c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a0c6792406ae36f4e7664dc772da909451d46432ff8485774526232d4885063", size = 1130190 }, + { url = "https://files.pythonhosted.org/packages/82/78/a6250ff0c49a3cdb943910ada4116e708118e9b901c878cfae616c80a904/rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f", size = 886470 }, + { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976 }, + { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739 }, + { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843 }, + { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348 }, + { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315 }, + { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047 }, + { url = "https://files.pythonhosted.org/packages/b8/86/3030fdc363a8f0d1cd155b4c453d6db9bab47a24fcc64d03f61d9d78fe6a/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0", size = 986090 }, + { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656 }, + { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789 }, + { url = "https://files.pythonhosted.org/packages/6b/5b/bb4f9420802bf73678033a4a55ab1bede36ce2e9b41fec5f966d83d932b3/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79", size = 1120308 }, + { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444 }, + { url = "https://files.pythonhosted.org/packages/47/98/80ef6fda78161e88ef9336fcbe851afccf78c48e69e8266a23fb7922b5aa/rignore-0.7.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e6ba1511c0ab8cd1ed8d6055bb0a6e629f48bfe04854293e0cd2dd88bd7153f8", size = 887180 }, + { url = "https://files.pythonhosted.org/packages/21/d7/8666e7081f8476b003d8d2c8f39ecc17c93b7efd261740d15b6830acde82/rignore-0.7.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:50586d90be15f9aa8a2e2ee5a042ee6c51e28848812a35f0c95d4bfc0533d469", size = 827029 }, + { url = "https://files.pythonhosted.org/packages/01/aa/3aba657d17b1737f4180b143866fedd269de15f361a8cb26ba363c0c3c13/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b129873dd0ade248e67f25a09b5b72288cbef76ba1a9aae6bac193ee1d8be72", size = 901338 }, + { url = "https://files.pythonhosted.org/packages/90/cc/d8c2c9770f5f61b28999c582804f282f2227c155ba13dfb0e9ea03daeaaf/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9d6dd947556ddebfd62753005104986ee14a4e0663818aed19cdf2c33a6b5d5", size = 877563 }, + { url = "https://files.pythonhosted.org/packages/55/63/42dd625bf96989be4a928b5444ddec9101ee63a98a15646e611b3ce58b82/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91b95faa532efba888b196331e9af69e693635d469185ac52c796e435e2484e5", size = 1171087 }, + { url = "https://files.pythonhosted.org/packages/bf/1e/4130fb622c2081c5322caf7a8888d1d265b99cd5d62cb714b512b8911233/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1016f430fb56f7e400838bbc56fdf43adddb6fcb7bf2a14731dfd725c2fae6c", size = 944335 }, + { url = "https://files.pythonhosted.org/packages/0f/b9/3d3ef7773da85e002fab53b1fdd9e9bb111cc86792b761cb38bd00c1532e/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f00c519861926dc703ecbb7bbeb884be67099f96f98b175671fa0a54718f55d1", size = 961500 }, + { url = "https://files.pythonhosted.org/packages/1f/bc/346c874a31a721064935c60666a19016b6b01cd716cf73d52dc64e467b30/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e34d172bf50e881b7c02e530ae8b1ea96093f0b16634c344f637227b39707b41", size = 987741 }, + { url = "https://files.pythonhosted.org/packages/6d/b8/d12dc548da8fdb63292a38727b035153495220cd93730019ee8ed3bdcffb/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:101d3143619898db1e7bede2e3e647daf19bb867c4fb25978016d67978d14868", size = 1081057 }, + { url = "https://files.pythonhosted.org/packages/8e/51/7eea5d949212709740ad07e01c524336e44608ef0614a2a1cb31c9a0ea30/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c9f3b420f54199a2b2b3b532d8c7e0860be3fa51f67501113cca6c7bfc392840", size = 1141653 }, + { url = "https://files.pythonhosted.org/packages/c4/2b/76ec843cc392fcb4e37d6a8340e823a0bf644872e191d2f5652a4c2c18ee/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:1c6795e3694d750ae5ef172eab7d68a52aefbd9168d2e06647df691db2b03a50", size = 1121465 }, + { url = "https://files.pythonhosted.org/packages/7c/9d/e69ad5cf03211a1076f9fe04ca2698c9cb8208b63419c928c26646bdf1d9/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:750a83a254b020e1193bfa7219dc7edca26bd8888a94cdc59720cbe386ab0c72", size = 1130110 }, ] [[package]] name = "ruff" -version = "0.14.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/34/8218a19b2055b80601e8fd201ec723c74c7fe1ca06d525a43ed07b6d8e85/ruff-0.14.2.tar.gz", hash = "sha256:98da787668f239313d9c902ca7c523fe11b8ec3f39345553a51b25abc4629c96", size = 5539663 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/dd/23eb2db5ad9acae7c845700493b72d3ae214dce0b226f27df89216110f2b/ruff-0.14.2-py3-none-linux_armv6l.whl", hash = "sha256:7cbe4e593505bdec5884c2d0a4d791a90301bc23e49a6b1eb642dd85ef9c64f1", size = 12533390 }, - { url = "https://files.pythonhosted.org/packages/5a/8c/5f9acff43ddcf3f85130d0146d0477e28ccecc495f9f684f8f7119b74c0d/ruff-0.14.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8d54b561729cee92f8d89c316ad7a3f9705533f5903b042399b6ae0ddfc62e11", size = 12887187 }, - { url = "https://files.pythonhosted.org/packages/99/fa/047646491479074029665022e9f3dc6f0515797f40a4b6014ea8474c539d/ruff-0.14.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c8753dfa44ebb2cde10ce5b4d2ef55a41fb9d9b16732a2c5df64620dbda44a3", size = 11925177 }, - { url = "https://files.pythonhosted.org/packages/15/8b/c44cf7fe6e59ab24a9d939493a11030b503bdc2a16622cede8b7b1df0114/ruff-0.14.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0bbeffb8d9f4fccf7b5198d566d0bad99a9cb622f1fc3467af96cb8773c9e3", size = 12358285 }, - { url = "https://files.pythonhosted.org/packages/45/01/47701b26254267ef40369aea3acb62a7b23e921c27372d127e0f3af48092/ruff-0.14.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7047f0c5a713a401e43a88d36843d9c83a19c584e63d664474675620aaa634a8", size = 12303832 }, - { url = "https://files.pythonhosted.org/packages/2d/5c/ae7244ca4fbdf2bee9d6405dcd5bc6ae51ee1df66eb7a9884b77b8af856d/ruff-0.14.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bf8d2f9aa1602599217d82e8e0af7fd33e5878c4d98f37906b7c93f46f9a839", size = 13036995 }, - { url = "https://files.pythonhosted.org/packages/27/4c/0860a79ce6fd4c709ac01173f76f929d53f59748d0dcdd662519835dae43/ruff-0.14.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1c505b389e19c57a317cf4b42db824e2fca96ffb3d86766c1c9f8b96d32048a7", size = 14512649 }, - { url = "https://files.pythonhosted.org/packages/7f/7f/d365de998069720a3abfc250ddd876fc4b81a403a766c74ff9bde15b5378/ruff-0.14.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a307fc45ebd887b3f26b36d9326bb70bf69b01561950cdcc6c0bdf7bb8e0f7cc", size = 14088182 }, - { url = "https://files.pythonhosted.org/packages/6c/ea/d8e3e6b209162000a7be1faa41b0a0c16a133010311edc3329753cc6596a/ruff-0.14.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61ae91a32c853172f832c2f40bd05fd69f491db7289fb85a9b941ebdd549781a", size = 13599516 }, - { url = "https://files.pythonhosted.org/packages/fa/ea/c7810322086db68989fb20a8d5221dd3b79e49e396b01badca07b433ab45/ruff-0.14.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1967e40286f63ee23c615e8e7e98098dedc7301568bd88991f6e544d8ae096", size = 13272690 }, - { url = "https://files.pythonhosted.org/packages/a9/39/10b05acf8c45786ef501d454e00937e1b97964f846bf28883d1f9619928a/ruff-0.14.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2877f02119cdebf52a632d743a2e302dea422bfae152ebe2f193d3285a3a65df", size = 13496497 }, - { url = "https://files.pythonhosted.org/packages/59/a1/1f25f8301e13751c30895092485fada29076e5e14264bdacc37202e85d24/ruff-0.14.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e681c5bc777de5af898decdcb6ba3321d0d466f4cb43c3e7cc2c3b4e7b843a05", size = 12266116 }, - { url = "https://files.pythonhosted.org/packages/5c/fa/0029bfc9ce16ae78164e6923ef392e5f173b793b26cc39aa1d8b366cf9dc/ruff-0.14.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e21be42d72e224736f0c992cdb9959a2fa53c7e943b97ef5d081e13170e3ffc5", size = 12281345 }, - { url = "https://files.pythonhosted.org/packages/a5/ab/ece7baa3c0f29b7683be868c024f0838770c16607bea6852e46b202f1ff6/ruff-0.14.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8264016f6f209fac16262882dbebf3f8be1629777cf0f37e7aff071b3e9b92e", size = 12629296 }, - { url = "https://files.pythonhosted.org/packages/a4/7f/638f54b43f3d4e48c6a68062794e5b367ddac778051806b9e235dfb7aa81/ruff-0.14.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ca36b4cb4db3067a3b24444463ceea5565ea78b95fe9a07ca7cb7fd16948770", size = 13371610 }, - { url = "https://files.pythonhosted.org/packages/8d/35/3654a973ebe5b32e1fd4a08ed2d46755af7267da7ac710d97420d7b8657d/ruff-0.14.2-py3-none-win32.whl", hash = "sha256:41775927d287685e08f48d8eb3f765625ab0b7042cc9377e20e64f4eb0056ee9", size = 12415318 }, - { url = "https://files.pythonhosted.org/packages/71/30/3758bcf9e0b6a4193a6f51abf84254aba00887dfa8c20aba18aa366c5f57/ruff-0.14.2-py3-none-win_amd64.whl", hash = "sha256:0df3424aa5c3c08b34ed8ce099df1021e3adaca6e90229273496b839e5a7e1af", size = 13565279 }, - { url = "https://files.pythonhosted.org/packages/2e/5d/aa883766f8ef9ffbe6aa24f7192fb71632f31a30e77eb39aa2b0dc4290ac/ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a", size = 12554956 }, +version = "0.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630 }, + { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925 }, + { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040 }, + { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755 }, + { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641 }, + { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854 }, + { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088 }, + { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717 }, + { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656 }, + { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922 }, + { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501 }, + { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319 }, + { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209 }, + { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709 }, + { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808 }, + { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546 }, + { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331 }, ] [[package]] name = "runpod" -version = "1.7.13" -source = { registry = "https://pypi.org/simple" } +version = "1.7.14.dev14+gcc05a5bec" +source = { git = "https://github.com/runpod/runpod-python?rev=main#cc05a5bec7a81fd809b3a40c73e47f4316de1de4" } dependencies = [ { name = "aiohttp", extra = ["speedups"] }, { name = "aiohttp-retry" }, @@ -2761,10 +2805,6 @@ dependencies = [ { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/d3/2b27cd36c9a770ed3f74c240bc73721c1315f9d89935474375935c7721b7/runpod-1.7.13.tar.gz", hash = "sha256:8448b096f2c4ef1db50b6e455d6eada45f974eaf6c95934d55279205a184d158", size = 281861 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/8c/2f556c9e275f956ecdd14a9a73b0129ed98e687f16df026e23be3db42b18/runpod-1.7.13-py3-none-any.whl", hash = "sha256:033ae142027d36f0c1db95103ef6d0b23fd9ec875fd8dba5154e3c519d3bcf70", size = 154593 }, -] [[package]] name = "s3transfer" @@ -2796,7 +2836,7 @@ wheels = [ [[package]] name = "secretstorage" -version = "3.4.0" +version = "3.4.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.10'", @@ -2805,23 +2845,23 @@ dependencies = [ { name = "cryptography", marker = "python_full_version >= '3.10'" }, { name = "jeepney", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/9f/11ef35cf1027c1339552ea7bfe6aaa74a8516d8b5caf6e7d338daf54fd80/secretstorage-3.4.0.tar.gz", hash = "sha256:c46e216d6815aff8a8a18706a2fbfd8d53fcbb0dce99301881687a1b0289ef7c", size = 19748 } +sdist = { url = "https://files.pythonhosted.org/packages/32/8a/ed6747b1cc723c81f526d4c12c1b1d43d07190e1e8258dbf934392fc850e/secretstorage-3.4.1.tar.gz", hash = "sha256:a799acf5be9fb93db609ebaa4ab6e8f1f3ed5ae640e0fa732bfea59e9c3b50e8", size = 19871 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272 }, + { url = "https://files.pythonhosted.org/packages/b0/6d/24ebb101484f1911a6be6695b76ce43219caa110ebbe07d8c3a5f3106cca/secretstorage-3.4.1-py3-none-any.whl", hash = "sha256:c55d57b4da3de568d8c3af89dad244ab24c35ca1da8625fc1b550edf005ebc41", size = 15301 }, ] [[package]] name = "sentry-sdk" -version = "2.43.0" +version = "2.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/18/09875b4323b03ca9025bae7e6539797b27e4fc032998a466b4b9c3d24653/sentry_sdk-2.43.0.tar.gz", hash = "sha256:52ed6e251c5d2c084224d73efee56b007ef5c2d408a4a071270e82131d336e20", size = 368953 } +sdist = { url = "https://files.pythonhosted.org/packages/62/26/ff7d93a14a0ec309021dca2fb7c62669d4f6f5654aa1baf60797a16681e0/sentry_sdk-2.44.0.tar.gz", hash = "sha256:5b1fe54dfafa332e900b07dd8f4dfe35753b64e78e7d9b1655a28fd3065e2493", size = 371464 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/31/8228fa962f7fd8814d634e4ebece8780e2cdcfbdf0cd2e14d4a6861a7cd5/sentry_sdk-2.43.0-py2.py3-none-any.whl", hash = "sha256:4aacafcf1756ef066d359ae35030881917160ba7f6fc3ae11e0e58b09edc2d5d", size = 400997 }, + { url = "https://files.pythonhosted.org/packages/a8/56/c16bda4d53012c71fa1b588edde603c6b455bc8206bf6de7b83388fcce75/sentry_sdk-2.44.0-py2.py3-none-any.whl", hash = "sha256:9e36a0372b881e8f92fdbff4564764ce6cec4b7f25424d0a3a8d609c9e4651a7", size = 402352 }, ] [[package]] @@ -2853,15 +2893,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.49.1" +version = "0.49.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/3f/507c21db33b66fb027a332f2cb3abbbe924cc3a79ced12f01ed8645955c9/starlette-0.49.1.tar.gz", hash = "sha256:481a43b71e24ed8c43b11ea02f5353d77840e01480881b8cb5a26b8cae64a8cb", size = 2654703 } +sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175 }, + { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340 }, ] [[package]] @@ -2885,8 +2925,10 @@ dev = [ { name = "ruff" }, ] test = [ - { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-cov" }, { name = "pytest-mock" }, { name = "twine" }, @@ -2900,7 +2942,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "questionary", specifier = ">=2.0.0" }, { name = "rich", specifier = ">=14.0.0" }, - { name = "runpod" }, + { name = "runpod", git = "https://github.com/runpod/runpod-python?rev=main" }, { name = "typer", specifier = ">=0.12.0" }, ] From 590df467482a43fce0ef934e4024703ef0ac2fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:16:53 -0800 Subject: [PATCH 17/26] refactor(cli): simplify skeleton template examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify skeleton template to focus on basic patterns: **GPU Worker (workers/gpu/):** - Simple hello-world response with GPU detection - Returns GPU name, count, and memory via PyTorch - Minimal dependencies (only torch) - Clear demonstration of @remote decorator **CPU Worker (workers/cpu/):** - Simple hello-world response - No complex data processing - Minimal dependencies (no pandas/numpy) - Clear demonstration of CpuLiveServerless **README Updates:** - Simplified documentation focusing on Tetra patterns - Removed complex ML/data processing examples - Added clear dependency management section - Updated API examples to /hello endpoints **Changes:** - Renamed workers/example/ → workers/gpu/ - Simplified gpu_hello() to return GPU info - Simplified cpu_hello() to basic response - Updated router endpoints from /matrix and /process to /hello - Reduced README from 620 lines to 252 lines (-368 lines) This makes the skeleton more approachable for new users learning the @remote decorator pattern without the cognitive overhead of understanding PyTorch matrix operations or pandas data analysis. --- .../cli/utils/skeleton_template/README.md | 622 ++++-------------- .../cli/utils/skeleton_template/main.py | 45 +- .../utils/skeleton_template/requirements.txt | 2 +- .../skeleton_template/workers/__init__.py | 0 .../skeleton_template/workers/cpu/__init__.py | 20 + .../skeleton_template/workers/cpu/endpoint.py | 38 ++ .../workers/example/__init__.py | 49 -- .../workers/example/endpoint.py | 52 -- .../skeleton_template/workers/gpu/__init__.py | 20 + .../skeleton_template/workers/gpu/endpoint.py | 62 ++ 10 files changed, 297 insertions(+), 613 deletions(-) create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/__init__.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py delete mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py delete mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py create mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py diff --git a/src/tetra_rp/cli/utils/skeleton_template/README.md b/src/tetra_rp/cli/utils/skeleton_template/README.md index 0c3ce29b..2b8fcadd 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/README.md +++ b/src/tetra_rp/cli/utils/skeleton_template/README.md @@ -1,30 +1,18 @@ # {{project_name}} -A Flash application for distributed inference and serving on Runpod's serverless infrastructure. +Flash application demonstrating distributed GPU and CPU computing on Runpod's serverless infrastructure. -## Quick Start (60 seconds) +## Quick Start ### 1. Install Dependencies -**Using pip:** ```bash pip install -r requirements.txt ``` -**Using uv (faster):** -```bash -uv pip install -r requirements.txt -``` - -**Using conda (if environment was created):** -```bash -conda activate {{project_name}} -pip install -r requirements.txt -``` - ### 2. Configure Environment -Add your Runpod API key to `.env`: +Create `.env` file: ```bash RUNPOD_API_KEY=your_api_key_here @@ -38,7 +26,7 @@ Get your API key from [Runpod Settings](https://www.runpod.io/console/user/setti flash run ``` -The server starts at **http://localhost:8000** +Server starts at **http://localhost:8000** ### 4. Test the API @@ -46,553 +34,219 @@ The server starts at **http://localhost:8000** # Health check curl http://localhost:8000/ping -# Test GPU worker endpoint -curl -X POST http://localhost:8000/example/ \ +# GPU worker +curl -X POST http://localhost:8000/gpu/hello \ -H "Content-Type: application/json" \ - -d '{"data": "example task"}' + -d '{"message": "Hello GPU!"}' -# Test CPU interface endpoint -curl http://localhost:8000/interface/list +# CPU worker +curl -X POST http://localhost:8000/cpu/hello \ + -H "Content-Type: application/json" \ + -d '{"message": "Hello CPU!"}' ``` -Test using the OpenAPI (Swagger) UI at **http://localhost:8000/docs** - -## Understanding the Architecture - -Flash applications consist of three components: +Visit **http://localhost:8000/docs** for interactive API documentation. -### 1. **Flash Server** (`main.py`) -- FastAPI application that coordinates all workers -- Runs locally during development -- Serves as the entry point for all API requests +## What This Demonstrates -### 2. **GPU Workers** (`workers/example/`) -- **Queue-based handlers** for GPU-intensive tasks -- Each request is queued and processed by available workers -- Auto-scales based on queue depth -- Best for: ML inference, image processing, video encoding +### GPU Worker (`workers/gpu/`) +Simple GPU-based serverless function: +- Remote execution with `@remote` decorator +- GPU resource configuration +- Automatic scaling (0-3 workers) +- No external dependencies required -Example configuration: ```python -from tetra_rp import remote, LiveServerless, GpuGroup - -config = LiveServerless( - name="example_worker", - gpus=[GpuGroup.ADA_24], # RTX 4090 - workersMin=0, # Scale to zero when idle - workersMax=3 # Max concurrent workers +@remote( + resource_config=LiveServerless( + name="gpu_worker", + gpus=[GpuGroup.ADA_24], # RTX 4090 + workersMin=0, + workersMax=3, + ) ) - -@remote(config) -class ExampleWorker: - def process(self, input_data: dict) -> dict: - # Your GPU processing logic here - return {"result": "processed"} +async def gpu_hello(input_data: dict) -> dict: + # Your GPU code here + return {"status": "success", "message": "Hello from GPU!"} ``` -### 3. **CPU Interfaces** (`workers/interface/`) -- **Load-balancer handlers** for CPU-based API endpoints -- Direct HTTP requests, no queue -- Lower latency, better for APIs -- Best for: REST APIs, data transformations, business logic +### CPU Worker (`workers/cpu/`) +Simple CPU-based serverless function: +- CPU-only execution (no GPU overhead) +- CpuLiveServerless configuration +- Efficient for API endpoints +- Automatic scaling (0-5 workers) -Example configuration: ```python -from tetra_rp import remote, CpuLiveServerless, CpuInstanceType - -config = CpuLiveServerless( - name="interface_worker", - instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM - workersMin=0, - workersMax=5 +@remote( + resource_config=CpuLiveServerless( + name="cpu_worker", + instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM + workersMin=0, + workersMax=5, + ) ) - -@remote(config) -def todo_get_list(): - return {"items": [...]} +async def cpu_hello(input_data: dict) -> dict: + # Your CPU code here + return {"status": "success", "message": "Hello from CPU!"} ``` ## Project Structure ``` {{project_name}}/ -├── main.py # FastAPI application entry point +├── main.py # FastAPI application ├── workers/ -│ ├── example/ # GPU worker (queue-based) -│ │ ├── __init__.py # FastAPI router -│ │ └── endpoint.py # Worker implementation with @remote decorator -│ └── interface/ # CPU interface (load-balancer) -│ ├── __init__.py # FastAPI router -│ └── endpoint.py # Functions with @remote decorator -├── .env # Environment variables (RUNPOD_API_KEY) -├── .flashignore # Files to exclude from build -├── requirements.txt # Python dependencies -└── README.md # This file +│ ├── gpu/ # GPU worker +│ │ ├── __init__.py # FastAPI router +│ │ └── endpoint.py # @remote decorated function +│ └── cpu/ # CPU worker +│ ├── __init__.py # FastAPI router +│ └── endpoint.py # @remote decorated function +├── .env # Environment variables +├── requirements.txt # Dependencies +└── README.md # This file ``` -## Deployment +## Key Concepts -### Build and Deploy +### Remote Execution +The `@remote` decorator transparently executes functions on serverless infrastructure: +- Code runs locally during development +- Automatically deploys to Runpod when configured +- Handles serialization, dependencies, and resource management -```bash -# Build the application (discovers all handlers) -flash build +### Resource Scaling +Both workers scale to zero when idle to minimize costs: +- **idleTimeout**: Minutes before scaling down (default: 5) +- **workersMin**: 0 = completely scales to zero +- **workersMax**: Maximum concurrent workers -# Create deployment environment -flash deploy new production +### GPU Types +Available GPU options for `LiveServerless`: +- `GpuGroup.ADA_24` - RTX 4090 (24GB) +- `GpuGroup.ADA_48_PRO` - RTX 6000 Ada, L40 (48GB) +- `GpuGroup.AMPERE_80` - A100 (80GB) +- `GpuGroup.ANY` - Any available GPU -# Deploy to Runpod serverless -flash deploy send production - -# Check deployment status and get endpoint URLs -flash deploy report production -``` - -### What Happens During Build? - -1. **Handler Discovery**: Flash scans your code for `@remote` decorators -2. **Classification**: Determines if handler is queue-based (GPU) or load-balancer (CPU) -3. **Resource Configuration**: Creates endpoint configuration for each handler -4. **Packaging**: Bundles code and dependencies for deployment +### CPU Types +Available CPU options for `CpuLiveServerless`: +- `CpuInstanceType.CPU3G_2_8` - 2 vCPU, 8GB RAM (General Purpose) +- `CpuInstanceType.CPU3C_4_8` - 4 vCPU, 8GB RAM (Compute Optimized) +- `CpuInstanceType.CPU5G_4_16` - 4 vCPU, 16GB RAM (Latest Gen) +- `CpuInstanceType.ANY` - Any available GPU -### Monitor Deployments +## Development Workflow +### Test Workers Locally ```bash -# View all deployed resources -flash report +# Test GPU worker +python -m workers.gpu.endpoint -# Check specific environment -flash deploy report production - -# View logs in Runpod Console -# Links are shown in the report output +# Test CPU worker +python -m workers.cpu.endpoint ``` -## API Reference - -### Health Endpoints - -#### `GET /` -Root endpoint returning application status. - -**Response:** -```json -{ - "status": "ok", - "message": "Flash application running" -} -``` - -#### `GET /ping` -Health check endpoint. - -**Response:** -```json -{ - "healthy": true -} -``` - -### Example GPU Worker - -#### `POST /example/` -Queue-based GPU processing endpoint. - -**Request:** -```json -{ - "data": "example task" -} -``` - -**Response:** -```json -{ - "status": "success", - "input": {"input": "example task"}, - "output": "Processed: {'input': 'example task'}" -} -``` - -### Example CPU Interface - -#### `GET /interface/list` -Get list of todo items. - -**Response:** -```json -{ - "item_1": "Make a list", - "item_2": "Show a list", - "item_3": "Delete a list" -} -``` - -#### `POST /interface/add` -Add a new todo item. - -**Request:** -```json -{ - "data": "New task" -} -``` - -**Response:** -```json -{ - "result": "added item: New task" -} -``` - -#### `POST /interface/delete` -Delete a todo item. - -**Request:** -```json -{ - "data": "Old task" -} -``` - -**Response:** -```json -{ - "result": "deleted item: Old task" -} -``` - -## Configuration - -### Environment Variables (`.env`) - +### Run the Application ```bash -# Required: Runpod API Key -RUNPOD_API_KEY=your_api_key_here - -# Optional: Server port (default: 80) -PORT=8000 - -# Optional: Log level (default: INFO) -LOG_LEVEL=DEBUG -``` - -### GPU Worker Configuration - -```python -from tetra_rp import LiveServerless, GpuGroup - -config = LiveServerless( - name="my_worker", - gpus=[GpuGroup.ADA_24], # GPU type - gpuCount=1, # GPUs per worker - workersMin=0, # Minimum workers (0 = scale to zero) - workersMax=3, # Maximum workers - idleTimeout=5, # Minutes before scaling down - env={"MODEL_PATH": "/models"} # Environment variables -) +flash run ``` -**Available GPU Types:** -- `GpuGroup.ADA_24` - NVIDIA GeForce RTX 4090 (24GB) -- `GpuGroup.ADA_48_PRO` - NVIDIA RTX 6000 Ada, L40, L40S (48GB) -- `GpuGroup.ADA_80_PRO` - NVIDIA H100 PCIe, H100 80GB HBM3 (80GB) -- `GpuGroup.AMPERE_24` - NVIDIA RTX A5000, L4, GeForce RTX 3090 (24GB) -- `GpuGroup.AMPERE_48` - NVIDIA A40, RTX A6000 (48GB) -- `GpuGroup.AMPERE_80` - NVIDIA A100 80GB PCIe, A100-SXM4-80GB (80GB) -- `GpuGroup.HOPPER_141` - NVIDIA H200 (141GB) - -### CPU Interface Configuration +### Deploy to Production +```bash +# Discover and configure handlers +flash build -```python -from tetra_rp import CpuLiveServerless, CpuInstanceType +# Create deployment environment +flash deploy new production -config = CpuLiveServerless( - name="my_interface", - instanceIds=[CpuInstanceType.CPU3G_2_8], # 2 vCPU, 8GB RAM - workersMin=0, - workersMax=5, - idleTimeout=5, - env={"API_TIMEOUT": "30"} -) +# Deploy to Runpod +flash deploy send production ``` -**Available CPU Types:** -- `CPU3G_1_4` - 1 vCPU, 4GB RAM (3rd Gen General Purpose) -- `CPU3G_2_8` - 2 vCPU, 8GB RAM (3rd Gen General Purpose) -- `CPU3G_4_16` - 4 vCPU, 16GB RAM (3rd Gen General Purpose) -- `CPU3G_8_32` - 8 vCPU, 32GB RAM (3rd Gen General Purpose) -- `CPU3C_1_2` - 1 vCPU, 2GB RAM (3rd Gen Compute-Optimized) -- `CPU3C_2_4` - 2 vCPU, 4GB RAM (3rd Gen Compute-Optimized) -- `CPU3C_4_8` - 4 vCPU, 8GB RAM (3rd Gen Compute-Optimized) -- `CPU3C_8_16` - 8 vCPU, 16GB RAM (3rd Gen Compute-Optimized) -- `CPU5C_1_2` - 1 vCPU, 2GB RAM (5th Gen Compute-Optimized) -- `CPU5C_2_4` - 2 vCPU, 4GB RAM (5th Gen Compute-Optimized) -- `CPU5C_4_8` - 4 vCPU, 8GB RAM (5th Gen Compute-Optimized) -- `CPU5C_8_16` - 8 vCPU, 16GB RAM (5th Gen Compute-Optimized) - ## Adding New Workers ### Add a GPU Worker -1. Create new directory in `workers/`: -```bash -mkdir -p workers/my_gpu_worker -``` - -2. Create `endpoint.py`: +1. Create `workers/my_worker/endpoint.py`: ```python from tetra_rp import remote, LiveServerless -config = LiveServerless(name="my_gpu_worker") - -@remote(config) -class MyWorker: - def __init__(self): - # Load models, initialize resources - pass - - def process(self, data: dict) -> dict: - # Your GPU processing logic - return {"result": "processed"} -``` - -3. Create `__init__.py`: -```python -from fastapi import APIRouter -from .endpoint import MyWorker - -router = APIRouter() - -@router.post("/process") -async def process(data: dict): - worker = MyWorker() - return await worker.process(data) -``` - -4. Register in `main.py`: -```python -from workers.my_gpu_worker import router as my_gpu_router -app.include_router(my_gpu_router, prefix="/my_gpu_worker", tags=["workers"]) -``` - -### Add a CPU Interface - -1. Create new directory in `workers/`: -```bash -mkdir -p workers/my_api -``` - -2. Create `endpoint.py`: -```python -from tetra_rp import remote, CpuLiveServerless - -config = CpuLiveServerless(name="my_api") - -@remote(config) -def get_data(id: str): - return {"id": id, "data": "..."} +config = LiveServerless(name="my_worker") -@remote(config) -def process_data(data: dict): - return {"processed": data} +@remote(resource_config=config, dependencies=["torch"]) +async def my_function(data: dict) -> dict: + import torch + # Your code here + return {"result": "success"} ``` -3. Create `__init__.py`: +2. Create `workers/my_worker/__init__.py`: ```python from fastapi import APIRouter -from .endpoint import get_data, process_data +from .endpoint import my_function router = APIRouter() -@router.get("/data/{id}") -async def get_data_endpoint(id: str): - return await get_data(id) - @router.post("/process") -async def process_endpoint(data: dict): - return await process_data(data) +async def handler(data: dict): + return await my_function(data) ``` -4. Register in `main.py`: +3. Add to `main.py`: ```python -from workers.my_api import router as my_api_router -app.include_router(my_api_router, prefix="/my_api", tags=["api"]) +from workers.my_worker import router as my_router +app.include_router(my_router, prefix="/my_worker") ``` -## Common Patterns - -### Pattern 1: ML Model Inference (GPU) - -```python -from tetra_rp import remote, LiveServerless, GpuGroup - -config = LiveServerless( - name="model_inference", - gpus=[GpuGroup.ADA_24], - workersMax=3 -) - -@remote(config) -class ModelInference: - def __init__(self): - # Imports must be inside the function/class - import torch - from transformers import AutoModel - - self.model = AutoModel.from_pretrained("model-name") - self.model.cuda() - - def predict(self, text: str) -> dict: - import torch - - with torch.no_grad(): - result = self.model(text) - - return {"prediction": result} -``` - -### Pattern 2: API Gateway (CPU) +### Add a CPU Worker +Same pattern but use `CpuLiveServerless`: ```python from tetra_rp import remote, CpuLiveServerless, CpuInstanceType config = CpuLiveServerless( - name="api_gateway", - instanceIds=[CpuInstanceType.CPU5C_4_8], # Compute optimized - workersMax=10 + name="my_cpu_worker", + instanceIds=[CpuInstanceType.CPU3G_2_8] ) -@remote(config) -def fetch_user(user_id: str): - # Call external APIs, databases, etc. +@remote(resource_config=config, dependencies=["requests"]) +async def fetch_data(url: str) -> dict: import requests - response = requests.get(f"https://api.example.com/users/{user_id}") - return response.json() - -@remote(config) -def process_webhook(data: dict): - # Process webhook events - return {"status": "processed", "data": data} + return requests.get(url).json() ``` -### Pattern 3: Batch Processing (GPU) +## Adding Dependencies +Specify dependencies in the `@remote` decorator: ```python -from tetra_rp import remote, LiveServerless, GpuGroup - -config = LiveServerless( - name="batch_processor", - gpus=[GpuGroup.AMPERE_80], - gpuCount=2, # Multi-GPU - workersMax=5 +@remote( + resource_config=config, + dependencies=["torch>=2.0.0", "transformers"], # Python packages + system_dependencies=["ffmpeg"] # System packages ) - -@remote(config) -class BatchProcessor: - def process_batch(self, items: list[dict]) -> list[dict]: - import torch - - results = [] - for item in items: - # Process each item - result = self.process_single(item) - results.append(result) - - return results -``` - -## Troubleshooting - -### Local Development - -**Issue: `ModuleNotFoundError` when running `flash run`** -```bash -# Solution: Install dependencies -pip install -r requirements.txt -``` - -**Issue: Port already in use** -```bash -# Solution: Change port in .env -PORT=8001 - -# Or kill the process using port 8000 -lsof -ti:8000 | xargs kill -9 -``` - -**Issue: API key not found** -```bash -# Solution: Make sure .env file exists with RUNPOD_API_KEY -cp .env.example .env -# Edit .env and add your API key -``` - -### Deployment Issues - -**Issue: Build fails with import errors** -```bash -# Solution: Add missing dependencies to requirements.txt -# Then rebuild: -flash build -``` - -**Issue: Handler not discovered during build** -```bash -# Solution: Make sure you're using @remote decorator -# And the function/class is imported in __init__.py +async def my_function(data: dict) -> dict: + # Dependencies are automatically installed + import torch + import transformers ``` -**Issue: Deployment stuck or failing** -```bash -# Solution: Check logs in Runpod console -# Get console link from: -flash deploy report production -``` +## Environment Variables -**Issue: Worker not scaling** ```bash -# Solution: Check worker configuration -# Make sure workersMax > 0 -# Check idleTimeout isn't too aggressive -``` - -### Performance Issues +# Required +RUNPOD_API_KEY=your_api_key -**Issue: High latency on GPU workers** -```bash -# Queue-based handlers have higher latency -# Solution: Consider using CPU interface if no GPU needed -# Or increase workersMin to keep workers warm -``` - -**Issue: Cold starts are slow** -```bash -# Solution: Set workersMin > 0 to keep workers running -# Or optimize model loading in __init__ +# Optional +PORT=8000 +LOG_LEVEL=INFO ``` -## Available Commands - -| Command | Description | -|---------|-------------| -| `flash run` | Start local development server | -| `flash build` | Build application for deployment | -| `flash deploy new ` | Create deployment environment | -| `flash deploy send ` | Deploy to environment | -| `flash deploy report ` | View environment status and URLs | -| `flash deploy list` | List all environments | -| `flash report` | Show all deployed resources | -| `flash clean` | Remove all deployed resources | - -## Learn More - -- **Runpod Console**: [https://www.runpod.io/console](https://www.runpod.io/console) -- **Runpod Documentation**: [https://docs.runpod.io](https://docs.runpod.io) -- **Get API Key**: [https://www.runpod.io/console/user/settings](https://www.runpod.io/console/user/settings) - -## Support +## Next Steps -- **Issues**: Report bugs at [GitHub Issues](https://github.com/runpod/flash/issues) -- **Discord**: Join the [Runpod Discord](https://discord.gg/runpod) -- **Email**: support@runpod.io +- Add your ML models or processing logic +- Configure GPU/CPU resources based on your needs +- Add authentication to your endpoints +- Implement error handling and retries +- Add monitoring and logging +- Deploy to production with `flash deploy` diff --git a/src/tetra_rp/cli/utils/skeleton_template/main.py b/src/tetra_rp/cli/utils/skeleton_template/main.py index ee45999b..79497557 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/main.py +++ b/src/tetra_rp/cli/utils/skeleton_template/main.py @@ -1,52 +1,43 @@ -""" -Flash Application - Flash Server - -This is the main entry point for your Flash application. -It runs a FastAPI server that coordinates with GPU and CPU workers. - -Each APIRouter is discovered as a handler during build and can be deployed -as a separate serverless endpoint (queue-based or load-balancer based). -""" - import os import logging from fastapi import FastAPI -from workers.example import example_worker_router -from workers.interface import example_interface_router +from workers.gpu import gpu_router +from workers.cpu import cpu_router logger = logging.getLogger(__name__) -# Create FastAPI app -app = FastAPI(title="Flash Application") +app = FastAPI( + title="Flash Application", + description="Distributed GPU and CPU computing with Runpod Flash", + version="0.1.0", +) + +# Include routers +app.include_router(gpu_router, prefix="/gpu", tags=["GPU Workers"]) +app.include_router(cpu_router, prefix="/cpu", tags=["CPU Workers"]) @app.get("/") def home(): - """Root endpoint.""" - return {"status": "ok", "message": "Flash application running"} + return { + "message": "Flash Application", + "docs": "/docs", + "endpoints": {"gpu_matrix": "/gpu/matrix", "cpu_process": "/cpu/process"}, + } @app.get("/ping") def ping(): - """Health check endpoint.""" - return {"healthy": True} - - -# Include worker routers -# Each router is discovered as a handler during build -app.include_router(example_worker_router, prefix="/example", tags=["workers"]) -app.include_router(example_interface_router, prefix="/interface", tags=["workers"]) + return {"status": "healthy"} -# Run the app when the script is executed if __name__ == "__main__": import uvicorn - port = int(os.getenv("PORT", 80)) + port = int(os.getenv("PORT", 8000)) logger.info(f"Starting Flash server on port {port}") - # Start the server uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/src/tetra_rp/cli/utils/skeleton_template/requirements.txt b/src/tetra_rp/cli/utils/skeleton_template/requirements.txt index 1666687a..532a30b0 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/requirements.txt +++ b/src/tetra_rp/cli/utils/skeleton_template/requirements.txt @@ -1 +1 @@ -tetra_rp>=0.14.0 +tetra_rp diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py new file mode 100644 index 00000000..f4eccae8 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/__init__.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter +from pydantic import BaseModel + +from .endpoint import cpu_hello + + +cpu_router = APIRouter() + + +class MessageRequest(BaseModel): + """Request model for CPU worker.""" + + message: str = "Hello from CPU!" + + +@cpu_router.post("/hello") +async def hello(request: MessageRequest): + """Simple CPU worker endpoint.""" + result = await cpu_hello({"message": request.message}) + return result diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py new file mode 100644 index 00000000..d6528d6d --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/cpu/endpoint.py @@ -0,0 +1,38 @@ +from tetra_rp import remote, CpuLiveServerless, CpuInstanceType + + +cpu_config = CpuLiveServerless( + name="cpu_worker", + instanceIds=[CpuInstanceType.ANY], + workersMin=0, + workersMax=5, + idleTimeout=5, +) + + +@remote(resource_config=cpu_config) +async def cpu_hello(input_data: dict) -> dict: + """Simple CPU worker example.""" + import platform + from datetime import datetime + + message = input_data.get("message", "Hello from CPU worker!") + + return { + "status": "success", + "message": message, + "worker_type": "CPU", + "timestamp": datetime.now().isoformat(), + "platform": platform.system(), + "python_version": platform.python_version(), + } + + +# Test locally with: python -m workers.cpu.endpoint +if __name__ == "__main__": + import asyncio + + test_payload = {"message": "Testing CPU worker"} + print(f"Testing CPU worker with payload: {test_payload}") + result = asyncio.run(cpu_hello(test_payload)) + print(f"Result: {result}") diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py deleted file mode 100644 index 326a0f9b..00000000 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/example/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Example GPU Worker Router - -This worker demonstrates the APIRouter pattern for Flash applications. - -Each router is discovered as a handler during build: -- Queue-based: Single POST endpoint (async processing) -- Load-balancer: Multiple endpoints or any GET (synchronous API) - -This router will be deployed as a Queue-based endpoint. -""" - -from fastapi import APIRouter -from pydantic import BaseModel -from .endpoint import ExampleWorker - - -# Create APIRouter for this worker -# This router will be discovered as a handler during build -example_worker_router = APIRouter() - - -class ProcessRequest(BaseModel): - """Request model for processing endpoint.""" - - data: str - - -class ProcessResponse(BaseModel): - """Response model for processing endpoint.""" - - status: str - input: dict - output: str - - -# Define routes for this worker -# Single POST endpoint → Queue-based handler (async processing) -@example_worker_router.post("/", response_model=ProcessResponse) -async def process_data(request: ProcessRequest): - """ - Process data using GPU worker. - - This is a queue-based handler (single POST endpoint). - Requests are queued and processed asynchronously. - """ - worker = ExampleWorker() - result = await worker.process({"input": request.data}) - return result diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py deleted file mode 100644 index a2f111aa..00000000 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/example/endpoint.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Example GPU Worker -""" - -from tetra_rp import remote, LiveServerless, GpuGroup - - -# Configure GPU resource for this worker -config = LiveServerless( - name="example_worker", - gpus=[GpuGroup.ADA_24], # RTX 4090 -) - - -@remote(config) -class ExampleWorker: - """Example GPU worker for processing tasks.""" - - def __init__(self): - """Initialize the worker.""" - print("ExampleWorker initialized") - - def process(self, input_data: dict) -> dict: - """ - Process input data and return result. - - Args: - input_data: Dictionary with input parameters - - Returns: - Dictionary with processing results - """ - # Your GPU processing logic here - result = { - "status": "success", - "input": input_data, - "output": f"Processed: {input_data}", - } - - return result - - -# Run this script by itself to test in Live Serverless -if __name__ == "__main__": - import asyncio - - worker = ExampleWorker() - payload = {"input": {"foo": "bar"}} - - result = asyncio.run(worker.process(payload)) - - print(result) diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py new file mode 100644 index 00000000..e64a2b74 --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/__init__.py @@ -0,0 +1,20 @@ +from fastapi import APIRouter +from pydantic import BaseModel + +from .endpoint import gpu_hello + + +gpu_router = APIRouter() + + +class MessageRequest(BaseModel): + """Request model for GPU worker.""" + + message: str = "Hello from GPU!" + + +@gpu_router.post("/hello") +async def hello(request: MessageRequest): + """Simple GPU worker endpoint.""" + result = await gpu_hello({"message": request.message}) + return result diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py new file mode 100644 index 00000000..01706d4b --- /dev/null +++ b/src/tetra_rp/cli/utils/skeleton_template/workers/gpu/endpoint.py @@ -0,0 +1,62 @@ +from tetra_rp import remote, LiveServerless, GpuGroup + + +gpu_config = LiveServerless( + name="gpu_worker", + gpus=[GpuGroup.ANY], + workersMin=0, + workersMax=3, + idleTimeout=5, +) + + +@remote(resource_config=gpu_config, dependencies=["torch"]) +async def gpu_hello(input_data: dict) -> dict: + """Simple GPU worker example with GPU detection.""" + import platform + from datetime import datetime + + try: + import torch + + gpu_available = torch.cuda.is_available() + if gpu_available: + gpu_name = torch.cuda.get_device_name(0) + gpu_count = torch.cuda.device_count() + gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3) + else: + gpu_name = "No GPU detected" + gpu_count = 0 + gpu_memory = 0 + except Exception as e: + gpu_available = False + gpu_name = f"Error detecting GPU: {str(e)}" + gpu_count = 0 + gpu_memory = 0 + + message = input_data.get("message", "Hello from GPU worker!") + + return { + "status": "success", + "message": message, + "worker_type": "GPU", + "gpu_info": { + "available": gpu_available, + "name": gpu_name, + "count": gpu_count, + "memory_gb": round(gpu_memory, 2) if gpu_memory else 0, + }, + "timestamp": datetime.now().isoformat(), + "platform": platform.system(), + "python_version": platform.python_version(), + } + + +# Test locally with: python -m workers.gpu.endpoint +if __name__ == "__main__": + import asyncio + + test_payload = {"message": "Testing GPU worker"} + print(f"Testing GPU worker with payload: {test_payload}") + result = asyncio.run(gpu_hello(test_payload)) + print(f"Result: {result}") From 06482169af53d2f99a7c277408f15f27b81af322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:22:39 -0800 Subject: [PATCH 18/26] fix(cli): update flash init output to reflect new skeleton structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update project structure display in flash init command output to show the current skeleton structure: - workers/example/ → workers/gpu/ - workers/interface/ → workers/cpu/ This matches the skeleton template refactoring from commit 590df46. --- src/tetra_rp/cli/commands/init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tetra_rp/cli/commands/init.py b/src/tetra_rp/cli/commands/init.py index 575a8037..62911c5e 100644 --- a/src/tetra_rp/cli/commands/init.py +++ b/src/tetra_rp/cli/commands/init.py @@ -145,9 +145,9 @@ def init_command( panel_content += f" {actual_project_name}/\n" panel_content += " ├── main.py # Flash Server (FastAPI)\n" - panel_content += " ├── workers/ # GPU workers\n" - panel_content += " │ └── example/\n" - panel_content += " │ └── interface/\n" + panel_content += " ├── workers/\n" + panel_content += " │ ├── gpu/ # GPU worker\n" + panel_content += " │ └── cpu/ # CPU worker\n" panel_content += " ├── .env\n" panel_content += " ├── requirements.txt\n" panel_content += " └── README.md\n" From a4fccdb48d8b98da5f2487f9a390543f39820924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:31:32 -0800 Subject: [PATCH 19/26] refactor(cli): remove conda environment management from flash init Simplify flash init command by removing all conda-related functionality: **Removed:** - Conda utility imports - REQUIRED_PACKAGES list - --no-env flag - Conda environment creation and package installation logic - Conditional output based on conda environment status **Simplified:** - Next steps always show: pip install, add API key, flash run - Users manage their own virtual environments (venv, conda, poetry, etc.) - Reduced file from 191 lines to 116 lines (-75 lines) This removes unnecessary complexity and lets users choose their preferred environment management tool. --- src/tetra_rp/cli/commands/init.py | 88 +++---------------------------- 1 file changed, 8 insertions(+), 80 deletions(-) diff --git a/src/tetra_rp/cli/commands/init.py b/src/tetra_rp/cli/commands/init.py index 62911c5e..db0fc0c1 100644 --- a/src/tetra_rp/cli/commands/init.py +++ b/src/tetra_rp/cli/commands/init.py @@ -9,33 +9,14 @@ from rich.table import Table from ..utils.skeleton import create_project_skeleton, detect_file_conflicts -from ..utils.conda import ( - check_conda_available, - create_conda_environment, - install_packages_in_env, - environment_exists, - get_activation_command, -) console = Console() -# Required packages for flash run to work smoothly -REQUIRED_PACKAGES = [ - "fastapi>=0.104.0", - "uvicorn[standard]>=0.24.0", - "python-dotenv>=1.0.0", - "pydantic>=2.0.0", - "aiohttp>=3.9.0", -] - def init_command( project_name: Optional[str] = typer.Argument( None, help="Project name or '.' for current directory" ), - no_env: bool = typer.Option( - False, "--no-env", help="Skip conda environment creation" - ), force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"), ): """Create new Flash project with Flash Server and GPU workers.""" @@ -90,50 +71,6 @@ def init_command( with console.status(status_msg): create_project_skeleton(project_dir, should_overwrite) - # Create conda environment if requested - env_created = False - if not no_env: - if not check_conda_available(): - console.print( - "[yellow]Warning: conda not found. Skipping environment creation.[/yellow]" - ) - console.print( - "Install Miniconda or Anaconda, or use --no-env flag to skip this step." - ) - else: - # Check if environment already exists - if environment_exists(actual_project_name): - console.print( - f"[yellow]Conda environment '{actual_project_name}' already exists. Skipping creation.[/yellow]" - ) - env_created = True - else: - # Create conda environment - with console.status( - f"Creating conda environment '{actual_project_name}'..." - ): - success, message = create_conda_environment(actual_project_name) - - if not success: - console.print(f"[yellow]Warning: {message}[/yellow]") - console.print( - "You can manually create the environment and install dependencies." - ) - else: - env_created = True - - # Install required packages - with console.status("Installing dependencies..."): - success, message = install_packages_in_env( - actual_project_name, REQUIRED_PACKAGES, use_pip=True - ) - - if not success: - console.print(f"[yellow]Warning: {message}[/yellow]") - console.print( - "You can manually install dependencies: pip install -r requirements.txt" - ) - # Success output if is_current_dir: panel_content = f"Flash project '[bold]{actual_project_name}[/bold]' initialized in current directory!\n\n" @@ -152,9 +89,6 @@ def init_command( panel_content += " ├── requirements.txt\n" panel_content += " └── README.md\n" - if env_created: - panel_content += f"\nConda environment '[bold]{actual_project_name}[/bold]' created and configured" - title = "Project Initialized" if is_current_dir else "Project Created" console.print(Panel(panel_content, title=title, expand=False)) @@ -169,19 +103,13 @@ def init_command( steps_table.add_row(f"{step_num}.", f"cd {actual_project_name}") step_num += 1 - if env_created: - steps_table.add_row( - f"{step_num}.", f"{get_activation_command(actual_project_name)}" - ) - step_num += 1 - steps_table.add_row(f"{step_num}.", "Add your RUNPOD_API_KEY to .env") - step_num += 1 - steps_table.add_row(f"{step_num}.", "flash run") - else: - steps_table.add_row(f"{step_num}.", "pip install -r requirements.txt") - step_num += 1 - steps_table.add_row(f"{step_num}.", "Add your RUNPOD_API_KEY to .env") - step_num += 1 - steps_table.add_row(f"{step_num}.", "flash run") + steps_table.add_row(f"{step_num}.", "pip install -r requirements.txt") + step_num += 1 + steps_table.add_row(f"{step_num}.", "Add your RUNPOD_API_KEY to .env") + step_num += 1 + steps_table.add_row(f"{step_num}.", "flash run") console.print(steps_table) + + console.print("\nVisit http://localhost:8888/docs") + console.print("\nCheck out the README.md for more") From 73669560b1c1f766a59020e595e2614cc8f62c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:32:20 -0800 Subject: [PATCH 20/26] docs(cli): update flash init documentation to remove conda references Update CLI documentation to reflect removal of conda environment management: **flash-init.md:** - Removed --no-env option - Removed conda environment creation documentation - Updated project structure to show gpu/ and cpu/ workers - Simplified next steps to use pip install **README.md:** - Removed conda activate from quick start - Removed --no-env from flash init options - Updated project structure to match skeleton template - Updated test endpoints to /gpu/hello and /cpu/hello Documentation now matches the simplified init command behavior. --- src/tetra_rp/cli/docs/README.md | 46 +++++++++++++++++++--------- src/tetra_rp/cli/docs/flash-init.md | 47 +++++++++++++---------------- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/src/tetra_rp/cli/docs/README.md b/src/tetra_rp/cli/docs/README.md index 9784cd8e..0ef6f284 100644 --- a/src/tetra_rp/cli/docs/README.md +++ b/src/tetra_rp/cli/docs/README.md @@ -11,9 +11,11 @@ flash init my-project # Navigate to project cd my-project -# Setup environment -conda activate my-project -cp .env.example .env # Add RUNPOD_API_KEY +# Install dependencies +pip install -r requirements.txt + +# Add your Runpod API key to .env +# RUNPOD_API_KEY=your_key_here # Run development server flash run @@ -26,17 +28,22 @@ flash run Create a new Flash project. ```bash -flash init PROJECT_NAME [OPTIONS] +flash init [PROJECT_NAME] [OPTIONS] ``` **Options:** -- `--no-env`: Skip conda environment creation -- `--force, -f`: Overwrite existing directory +- `--force, -f`: Overwrite existing files -**Example:** +**Examples:** ```bash +# Create new project flash init my-project -flash init my-project --no-env + +# Initialize in current directory +flash init . + +# Overwrite existing files +flash init my-project --force ``` [Full documentation](./flash-init.md) @@ -71,10 +78,14 @@ flash run --port 3000 ``` my-project/ ├── main.py # Flash Server (FastAPI) -├── workers/ # GPU workers -│ ├── __init__.py -│ └── example_worker.py -├── .env.example +├── workers/ +│ ├── gpu/ # GPU worker +│ │ ├── __init__.py +│ │ └── endpoint.py +│ └── cpu/ # CPU worker +│ ├── __init__.py +│ └── endpoint.py +├── .env ├── requirements.txt └── README.md ``` @@ -90,12 +101,17 @@ RUNPOD_API_KEY=your_api_key_here ```bash # Health check -curl http://localhost:8888/ +curl http://localhost:8888/ping # Call GPU worker -curl -X POST http://localhost:8888/process \ +curl -X POST http://localhost:8888/gpu/hello \ + -H "Content-Type: application/json" \ + -d '{"message": "Hello GPU!"}' + +# Call CPU worker +curl -X POST http://localhost:8888/cpu/hello \ -H "Content-Type: application/json" \ - -d '{"data": "test input"}' + -d '{"message": "Hello CPU!"}' ``` ## Getting Help diff --git a/src/tetra_rp/cli/docs/flash-init.md b/src/tetra_rp/cli/docs/flash-init.md index 98b8a5f3..2776fca1 100644 --- a/src/tetra_rp/cli/docs/flash-init.md +++ b/src/tetra_rp/cli/docs/flash-init.md @@ -1,32 +1,32 @@ # flash init -Create a new Flash project with Flash Server and GPU workers. +Create a new Flash project with Flash Server and GPU/CPU workers. ## Usage ```bash -flash init PROJECT_NAME [OPTIONS] +flash init [PROJECT_NAME] [OPTIONS] ``` ## Arguments -- `PROJECT_NAME` (required): Name of the project directory to create +- `PROJECT_NAME` (optional): Name of the project directory to create + - If omitted or `.`, initializes in current directory ## Options -- `--no-env`: Skip conda environment creation -- `--force, -f`: Overwrite existing directory +- `--force, -f`: Overwrite existing files ## Examples ```bash -# Create project with conda environment +# Create new project directory flash init my-project -# Create without conda environment -flash init my-project --no-env +# Initialize in current directory +flash init . -# Overwrite existing directory +# Overwrite existing files flash init my-project --force ``` @@ -35,30 +35,25 @@ flash init my-project --force ``` my-project/ ├── main.py # Flash Server (FastAPI) -├── workers/ # GPU workers -│ ├── __init__.py -│ └── example_worker.py -├── .env.example +├── workers/ +│ ├── gpu/ # GPU worker example +│ │ ├── __init__.py +│ │ └── endpoint.py +│ └── cpu/ # CPU worker example +│ ├── __init__.py +│ └── endpoint.py +├── .env ├── requirements.txt -├── .gitignore └── README.md ``` -## Dependencies Installed - -When conda environment is created: -- `fastapi>=0.104.0` -- `uvicorn[standard]>=0.24.0` -- `python-dotenv>=1.0.0` -- `pydantic>=2.0.0` -- `aiohttp>=3.9.0` -- `tetra-rp>=0.12.0` - ## Next Steps ```bash cd my-project -conda activate my-project # if env created -cp .env.example .env # add RUNPOD_API_KEY +pip install -r requirements.txt # or use your preferred environment manager +# Add RUNPOD_API_KEY to .env flash run ``` + +Visit http://localhost:8888/docs for interactive API documentation. From ea74f5745a016315b4bf3b7f13d16f931deb8420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:37:48 -0800 Subject: [PATCH 21/26] fix(cli): use recursive glob for worker dependency extraction Change glob pattern from '*.py' to '**/*.py' in extract_remote_dependencies() to recursively search worker subdirectories. This fixes the build command to work with the new skeleton structure where workers are organized in subdirectories (workers/gpu/, workers/cpu/) instead of directly in the workers/ directory. The build command can now find and extract dependencies from @remote decorators in workers/gpu/endpoint.py and workers/cpu/endpoint.py. --- src/tetra_rp/cli/commands/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tetra_rp/cli/commands/build.py b/src/tetra_rp/cli/commands/build.py index 8f60c93c..d583e15f 100644 --- a/src/tetra_rp/cli/commands/build.py +++ b/src/tetra_rp/cli/commands/build.py @@ -313,7 +313,7 @@ def extract_remote_dependencies(workers_dir: Path) -> list[str]: """ dependencies = [] - for py_file in workers_dir.glob("*.py"): + for py_file in workers_dir.glob("**/*.py"): if py_file.name == "__init__.py": continue From 2df184a40f498417ffbeb55a6a9b3b7a238c8346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:38:52 -0800 Subject: [PATCH 22/26] fix(cli): improve pip error reporting in flash build Add detailed error messages when pip check fails: - Show stderr output from pip --version command - Show exception details if subprocess fails - Add helpful hint to install pip with ensurepip This helps users diagnose why pip detection is failing instead of showing a generic 'pip not found' error. --- src/tetra_rp/cli/commands/build.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tetra_rp/cli/commands/build.py b/src/tetra_rp/cli/commands/build.py index d583e15f..ca0afb63 100644 --- a/src/tetra_rp/cli/commands/build.py +++ b/src/tetra_rp/cli/commands/build.py @@ -372,15 +372,25 @@ def install_dependencies( result = subprocess.run( pip_cmd + ["--version"], capture_output=True, + text=True, timeout=5, ) if result.returncode != 0: console.print( "[red]Error:[/red] pip not found in current Python environment" ) + if result.stderr: + console.print(f"[yellow]Details:[/yellow] {result.stderr.strip()}") + console.print( + "\n[yellow]Hint:[/yellow] Install pip with: python -m ensurepip --upgrade" + ) return False - except (subprocess.SubprocessError, FileNotFoundError): + except (subprocess.SubprocessError, FileNotFoundError) as e: console.print("[red]Error:[/red] pip not found in current Python environment") + console.print(f"[yellow]Details:[/yellow] {str(e)}") + console.print( + "\n[yellow]Hint:[/yellow] Install pip with: python -m ensurepip --upgrade" + ) return False cmd = pip_cmd + [ From 8f3ecd576f91d293b71c055f34abb37ffb822d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 02:39:55 -0800 Subject: [PATCH 23/26] fix(cli): add uv pip fallback for flash build Detect and use 'uv pip' as fallback when 'python -m pip' is not available. This handles uv-created virtual environments that don't include pip by default. The build command now: 1. Tries python -m pip first 2. Falls back to uv pip if pip not in venv 3. Shows a note when using uv pip 4. Provides helpful install instructions if neither works Fixes build errors in uv environments without pip installed. --- src/tetra_rp/cli/commands/build.py | 49 +++++++++++++++++++----------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/tetra_rp/cli/commands/build.py b/src/tetra_rp/cli/commands/build.py index ca0afb63..3f4ccbc9 100644 --- a/src/tetra_rp/cli/commands/build.py +++ b/src/tetra_rp/cli/commands/build.py @@ -351,7 +351,7 @@ def install_dependencies( build_dir: Path, requirements: list[str], no_deps: bool ) -> bool: """ - Install dependencies to build directory using pip. + Install dependencies to build directory using pip or uv pip. Args: build_dir: Build directory (pip --target) @@ -364,10 +364,10 @@ def install_dependencies( if not requirements: return True - # Use only sys.executable -m pip to ensure correct environment + # Try python -m pip first pip_cmd = [sys.executable, "-m", "pip"] + pip_available = False - # Check if pip is available try: result = subprocess.run( pip_cmd + ["--version"], @@ -375,22 +375,35 @@ def install_dependencies( text=True, timeout=5, ) - if result.returncode != 0: - console.print( - "[red]Error:[/red] pip not found in current Python environment" - ) - if result.stderr: - console.print(f"[yellow]Details:[/yellow] {result.stderr.strip()}") - console.print( - "\n[yellow]Hint:[/yellow] Install pip with: python -m ensurepip --upgrade" + if result.returncode == 0: + pip_available = True + except (subprocess.SubprocessError, FileNotFoundError): + pass + + # If pip not available, try uv pip + if not pip_available: + try: + result = subprocess.run( + ["uv", "pip", "--version"], + capture_output=True, + text=True, + timeout=5, ) - return False - except (subprocess.SubprocessError, FileNotFoundError) as e: - console.print("[red]Error:[/red] pip not found in current Python environment") - console.print(f"[yellow]Details:[/yellow] {str(e)}") - console.print( - "\n[yellow]Hint:[/yellow] Install pip with: python -m ensurepip --upgrade" - ) + if result.returncode == 0: + pip_cmd = ["uv", "pip"] + pip_available = True + console.print( + "[yellow]Note:[/yellow] Using 'uv pip' (pip not found in venv)" + ) + except (subprocess.SubprocessError, FileNotFoundError): + pass + + # If neither available, error out + if not pip_available: + console.print("[red]Error:[/red] Neither pip nor uv pip found") + console.print("\n[yellow]Install pip with one of:[/yellow]") + console.print(" • python -m ensurepip --upgrade") + console.print(" • uv pip install pip") return False cmd = pip_cmd + [ From a4cfb6e3578d6967b3df14e7838531da965ffec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 03:30:40 -0800 Subject: [PATCH 24/26] fix(cli): remove workers/interface and update outdated endpoint references - Remove workers/interface/ directory from skeleton template (was supposed to be removed but wasn't) - Fix main.py home endpoint to reference correct paths: /gpu/hello and /cpu/hello (was /gpu/matrix and /cpu/process) - Addresses PR feedback about skeleton complexity --- .../cli/utils/skeleton_template/main.py | 2 +- .../workers/interface/__init__.py | 55 ------------------- .../workers/interface/endpoint.py | 48 ---------------- 3 files changed, 1 insertion(+), 104 deletions(-) delete mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py delete mode 100644 src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py diff --git a/src/tetra_rp/cli/utils/skeleton_template/main.py b/src/tetra_rp/cli/utils/skeleton_template/main.py index 79497557..fb1c5999 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/main.py +++ b/src/tetra_rp/cli/utils/skeleton_template/main.py @@ -25,7 +25,7 @@ def home(): return { "message": "Flash Application", "docs": "/docs", - "endpoints": {"gpu_matrix": "/gpu/matrix", "cpu_process": "/cpu/process"}, + "endpoints": {"gpu_hello": "/gpu/hello", "cpu_hello": "/cpu/hello"}, } diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py deleted file mode 100644 index 5878c654..00000000 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Example CPU Interface Router - -This router demonstrates CPU-based API endpoints using @remote decorated functions. - -Each @remote function is deployed as a separate serverless CPU endpoint. -The router provides RESTful access to these functions. - -This router will be deployed as a Load-balancer endpoint (multiple GET/POST). -""" - -from fastapi import APIRouter -from pydantic import BaseModel - -# Import remote functions -from .endpoint import todo_get_list, todo_add_item, todo_delete_item - - -# Create APIRouter for this interface -# This router will be discovered as a handler during build -example_interface_router = APIRouter() - - -class TodoItem(BaseModel): - """Request model for todo item operations.""" - - data: str - - -# Define routes for this interface -# Multiple endpoints → Load-balancer handler (synchronous API) -@example_interface_router.get("/list") -async def get_todo_list(): - """ - Get the todo list. - - This is a load-balancer handler (multiple endpoints). - Requests are routed directly to available workers. - """ - result = await todo_get_list() - return result - - -@example_interface_router.post("/add") -async def add_todo_item(item: TodoItem): - """Add a new todo item.""" - result = await todo_add_item(item.data) - return {"result": result} - - -@example_interface_router.post("/delete") -async def delete_todo_item(item: TodoItem): - """Delete a todo item.""" - result = await todo_delete_item(item.data) - return {"result": result} diff --git a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py b/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py deleted file mode 100644 index fd534e18..00000000 --- a/src/tetra_rp/cli/utils/skeleton_template/workers/interface/endpoint.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Example CPU Interface -""" - -from tetra_rp import remote, CpuLiveServerless - - -# Configure CPU resource for this interface -config = CpuLiveServerless(name="interface_worker") - - -@remote(config) -def todo_get_list(): - return { - "item_1": "Make a list", - "item_2": "Show a list", - "item_3": "Delete a list", - } - - -@remote(config) -def todo_add_item(item): - return f"added item: {item}" - - -@remote(config) -def todo_delete_item(item): - return f"deleted item: {item}" - - -# Run this script by itself to test the CPU interface -if __name__ == "__main__": - import asyncio - - async def test_interface(): - """Test all interface functions in parallel.""" - # Run all functions concurrently - list_result, add_result, delete_result = await asyncio.gather( - todo_get_list(), - todo_add_item("Test task"), - todo_delete_item("Old task"), - ) - - print("List result:", list_result) - print("Add result:", add_result) - print("Delete result:", delete_result) - - asyncio.run(test_interface()) From 1b54bfb99982cfcb5ab1779cecf61a200277de25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 03:33:50 -0800 Subject: [PATCH 25/26] fix(cli): standardize default port to 8888 in skeleton template - Change main.py default port from 8000 to 8888 to match flash run command - Ensures consistency between skeleton template and CLI behavior - Documentation already uses port 8888 correctly - Addresses Copilot feedback about port inconsistency --- src/tetra_rp/cli/utils/skeleton_template/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tetra_rp/cli/utils/skeleton_template/main.py b/src/tetra_rp/cli/utils/skeleton_template/main.py index fb1c5999..a7f14a68 100644 --- a/src/tetra_rp/cli/utils/skeleton_template/main.py +++ b/src/tetra_rp/cli/utils/skeleton_template/main.py @@ -37,7 +37,7 @@ def ping(): if __name__ == "__main__": import uvicorn - port = int(os.getenv("PORT", 8000)) + port = int(os.getenv("PORT", 8888)) logger.info(f"Starting Flash server on port {port}") uvicorn.run(app, host="0.0.0.0", port=port) From 5a27411038b46b1e62bc7545bfeb2b5840a4e706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20Qui=C3=B1anola?= Date: Fri, 14 Nov 2025 05:24:18 -0800 Subject: [PATCH 26/26] feat(cli): limit flash CLI to init and run commands for this release Temporarily disable build and deploy commands with coming soon message. This release focuses on core init and run functionality, with build and deploy features scheduled for future releases. --- src/tetra_rp/cli/commands/build.py | 10 ++++++++++ src/tetra_rp/cli/commands/deploy.py | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/tetra_rp/cli/commands/build.py b/src/tetra_rp/cli/commands/build.py index 3f4ccbc9..d6c5c046 100644 --- a/src/tetra_rp/cli/commands/build.py +++ b/src/tetra_rp/cli/commands/build.py @@ -44,6 +44,16 @@ def build_command( flash build --keep-build # Keep temporary build directory flash build -o my-app.tar.gz # Custom archive name """ + console.print( + Panel( + "[yellow]The build command is coming soon.[/yellow]\n\n" + "This feature is under development and will be available in a future release.", + title="Coming Soon", + expand=False, + ) + ) + return + try: # Validate project structure project_dir, app_name = discover_flash_project() diff --git a/src/tetra_rp/cli/commands/deploy.py b/src/tetra_rp/cli/commands/deploy.py index 325ab1eb..d985f113 100644 --- a/src/tetra_rp/cli/commands/deploy.py +++ b/src/tetra_rp/cli/commands/deploy.py @@ -21,6 +21,16 @@ def list_command(): """Show available deployment environments.""" + console.print( + Panel( + "[yellow]The deploy command is coming soon.[/yellow]\n\n" + "This feature is under development and will be available in a future release.", + title="Coming Soon", + expand=False, + ) + ) + return + environments = get_deployment_environments() if not environments: @@ -85,6 +95,16 @@ def new_command( ): """Create a new deployment environment.""" + console.print( + Panel( + "[yellow]The deploy command is coming soon.[/yellow]\n\n" + "This feature is under development and will be available in a future release.", + title="Coming Soon", + expand=False, + ) + ) + return + environments = get_deployment_environments() if name in environments: