diff --git a/.env.example b/.env.example index 05e81e5a..307a4b46 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,22 @@ GROQ_API_KEY=your_groq_api_key_here # OpenRouter Model Selection (optional) OPENROUTER_MODEL=openai/gpt-4o-mini +# ============================================================================= +# GENERIC OPENAI CHAT COMPLETIONS API CLIENT +# ============================================================================= + +# API key for any OpenAI Chat Completions API compatible endpoint (e.g. Poe) +# Get a Poe API key from: https://creator.poe.com/ +OPENAI_COMPATIBLE_API_KEY=your_api_key_here + +# Base URL of the target endpoint +# Example for Poe: https://api.poe.com/llm/v1 +OPENAI_COMPATIBLE_BASE_URL=https://api.poe.com/llm/v1 + +# Model name to use via the generic client (default: gpt-4o) +# Example for Poe: MiniMax-Text-01, Claude-3-7-Sonnet, etc. +OPENAI_COMPATIBLE_MODEL=gpt-4o + # Set it to a positive integer(window size in lines) to trim log blocks that only differ in timestamps. This saves input tokens and reduces context length. # For example, if LOG_TRIM=2, the code will compare every 1 line, and then every 2 lines for duplication. LOG_TRIM=0 diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 00000000..91a024d3 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,195 @@ +name: Integration Smoke Test + +# Trigger on every push to main branch and on every pull request to main branch. +# This ensures regressions are caught before merging to main. +on: + push: + branches: ['main'] + paths-ignore: + - '**.md' + - '.env.example' + - 'assets/**' + - 'LICENSE.txt' + - 'NOTICE.txt' + - '.github/ISSUE_TEMPLATE/**' + pull_request: + branches: ['main'] + paths-ignore: + - '**.md' + - '.env.example' + - 'assets/**' + - 'LICENSE.txt' + - 'NOTICE.txt' + - '.github/ISSUE_TEMPLATE/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke-test: + name: no-op hotel-reservation smoke test + runs-on: ubuntu-latest + # Full cluster setup + app deploy + workload + teardown typically takes 15-25 min. + timeout-minutes: 45 + + steps: + # ----------------------------------------------------------------------- + # 1. Source checkout + # ----------------------------------------------------------------------- + - name: Checkout repository (with submodules) + uses: actions/checkout@v4 + with: + # aiopslab-applications contains the K8s manifests and Helm charts + # required by the orchestrator to deploy HotelReservation. + submodules: recursive + + # ----------------------------------------------------------------------- + # 2. Cluster tooling + # kubectl is pre-installed on ubuntu-latest; we only need kind + helm. + # ----------------------------------------------------------------------- + - name: Install kind + run: | + # Download to /tmp to avoid colliding with the repo's kind/ directory + curl -Lo /tmp/kind-bin https://kind.sigs.k8s.io/dl/v0.27.0/kind-linux-amd64 + chmod +x /tmp/kind-bin + sudo mv /tmp/kind-bin /usr/local/bin/kind + kind version + + - name: Install Helm + run: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + + # Pre-pull the node image so cluster creation doesn't time out waiting + # for a large Docker pull inside the kind bootstrap. + - name: Pre-pull kind node image + run: docker pull jacksonarthurclark/aiopslab-kind-x86:latest + + # OpenEBS Node Disk Manager (NDM) mounts /run/udev into its pod to scan + # block devices. The kind-config-ci.yaml passes this as an extraMount so + # kind places the host path inside the node container. On GitHub-hosted + # runners /run/udev may not exist or may be a socket file, which causes + # kubelet to reject the hostPath mount with "is not a directory". We + # create it as an empty directory before kind cluster creation so the + # mount path type check (Directory) passes. + - name: Prepare /run/udev for OpenEBS NDM + run: sudo mkdir -p /run/udev + + - name: Create kind cluster + run: | + kind create cluster \ + --config kind/kind-config-x86.yaml \ + --wait 120s + kubectl cluster-info + kubectl get nodes + + # ----------------------------------------------------------------------- + # 2b. Pre-install OpenEBS before pytest + # + # The orchestrator's init_problem() applies the OpenEBS manifest and + # waits with a hard max_wait=300s. On a cold runner the pod images + # (~800 MB) must be pulled from Docker Hub first, which can easily + # exceed 5 minutes and cause a timeout. Pre-installing here lets the + # images pull at their own pace (up to 10 min), so by the time pytest + # calls wait_for_ready("openebs") the pods are already Ready. + # kubectl apply is idempotent so the orchestrator re-applying is fine. + # ----------------------------------------------------------------------- + - name: Pre-install OpenEBS + run: | + kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml + echo "Waiting up to 10 min for OpenEBS pods to be ready (cold image pull)..." + kubectl wait pod --all -n openebs \ + --for=condition=Ready \ + --timeout=600s + kubectl patch storageclass openebs-hostpath \ + -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' + echo "OpenEBS is ready." + + # ----------------------------------------------------------------------- + # 2c. Pre-install Prometheus before pytest + # + # init_problem() deploys Prometheus via Helm and waits with max_wait=300s. + # On a cold runner, pulling Prometheus + sub-chart images (node-exporter, + # kube-state-metrics, alertmanager, pushgateway) from Docker Hub can + # take 3-6 min, exceeding the 5-minute hard timeout. + # + # Pre-installing here means Prometheus._is_prometheus_running() will + # return True when init_problem() calls Prometheus.deploy(), causing it + # to skip redeployment entirely — wait_for_ready("observe") returns + # immediately. + # + # Chart path mirrors Prometheus.load_service_json(): + # BASE_DIR / "observer/prometheus/prometheus/" + # = aiopslab/observer/prometheus/prometheus/ + # ----------------------------------------------------------------------- + - name: Pre-install Prometheus + run: | + kubectl create namespace observe --dry-run=client -o yaml | kubectl apply -f - + kubectl apply -f aiopslab/observer/prometheus/prometheus-pvc.yml -n observe + helm dependency update aiopslab/observer/prometheus/prometheus/ + helm install prometheus aiopslab/observer/prometheus/prometheus/ \ + -n observe --create-namespace + echo "Waiting up to 10 min for Prometheus pods to be ready (cold image pull)..." + kubectl wait pod --all -n observe \ + --for=condition=Ready \ + --timeout=600s + echo "Prometheus is ready." + + # ----------------------------------------------------------------------- + # 3. Python + dependencies + # ----------------------------------------------------------------------- + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Poetry + run: pip install poetry + + # Install core framework + dev tools; skip heavy ML client packages + # (vllm, flwr, etc.) that need CUDA and are not required for the smoke test. + - name: Install dependencies + run: poetry install --without clients --with dev + + # ----------------------------------------------------------------------- + # 4. Framework configuration + # config.yml is gitignored; generate it on the fly. + # k8s_host=kind tells the orchestrator to use the local kubeconfig. + # ----------------------------------------------------------------------- + - name: Generate aiopslab/config.yml + run: | + cat > aiopslab/config.yml <<'EOF' + k8s_host: kind + k8s_user: runner + ssh_key_path: ~/.ssh/id_rsa + data_dir: data + qualitative_eval: false + print_session: false + EOF + + # ----------------------------------------------------------------------- + # 5. Run smoke test + # KubeCtl defaults AIOPSLAB_CLUSTER=kind → context=kind-kind, which + # matches the default cluster name created above. + # ----------------------------------------------------------------------- + - name: Run integration smoke test + run: poetry run pytest tests/integration/smoke_test.py -v -s -m integration + + # ----------------------------------------------------------------------- + # 6. Diagnostics on failure + # ----------------------------------------------------------------------- + - name: Dump cluster state on failure + if: failure() + run: | + echo "=== All namespaced resources ===" + kubectl get all --all-namespaces + echo "=== Recent events ===" + kubectl get events --all-namespaces --sort-by='.lastTimestamp' | tail -40 + kind export logs --name kind /tmp/kind-logs + + - name: Upload kind logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: kind-logs + path: /tmp/kind-logs + retention-days: 7 diff --git a/.gitignore b/.gitignore index 71e2721f..2208923a 100644 --- a/.gitignore +++ b/.gitignore @@ -161,172 +161,12 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - # Visual Studio Code .vscode/ +# Claude Code +.claude/ + # Weight & Biases wandb/ @@ -354,9 +194,14 @@ aiopslab/observer/prometheus/prometheus/charts/prometheus-node-exporter-4.23.2.t aiopslab/observer/prometheus/prometheus/charts/kube-state-metrics-5.15.3.tgz aiopslab/observer/prometheus/prometheus/Chart.lock aiopslab/observer/prometheus/prometheus/charts/alertmanager-1.7.0.tgz -aiopslab/observer/prometheus/prometheus/Chart.lock -aiopslab/observer/prometheus/prometheus/Chart.lock # Ignore customized config files aiopslab/config.yml scripts/ansible/inventory.yml +.claude/settings.local.json + +# Ignore local setup instructions +running_on_wsl.txt +validation_summary.txt +cleanup.txt +SESSION_SUMMARY.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..8e2289f9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,622 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +AIOpsLab is a holistic framework for evaluating autonomous AIOps agents in interactive cloud environments. It orchestrates microservice deployments, fault injection, workload generation, and telemetry collection to benchmark AI agents on detection, localization, analysis, and mitigation tasks. + +## Development Commands + +### Environment Setup +```bash +# Install dependencies using Poetry (recommended, requires Python >= 3.11) +poetry env use python3.11 +poetry install +eval $(poetry env activate) + +# Alternative: pip install +pip install -e . +``` + +### Running Agents Locally +```bash +# Interactive CLI with human agent +python3 cli.py +# In the REPL: +(aiopslab) $ start # e.g., misconfig_app_hotel_res-detection-1 +(aiopslab) $ submit("Yes") + +# Run baseline agents (GPT, Qwen, DeepSeek, etc.) +python3 clients/gpt.py +python3 clients/qwen.py +python3 clients/deepseek.py +python3 clients/vllm.py + +# Configure API keys in .env file +cp .env.example .env +# Edit .env to add OPENAI_API_KEY, QWEN_API_KEY, etc. +``` + +### Running AIOpsLab as a Service +```bash +# Start FastAPI service on remote machine +SERVICE_HOST=0.0.0.0 SERVICE_PORT=1818 SERVICE_WORKERS=1 python service.py + +# Test endpoints +curl http://:/health +curl http://:/problems +curl http://:/agents + +# Run simulation via API +curl -X POST http://:/simulate \ + -H "Content-Type: application/json" \ + -d '{"problem_id": "misconfig_app_hotel_res-mitigation-1", "agent_name": "vllm", "max_steps": 10}' +``` + +### Testing +```bash +# Run unit tests +python -m unittest discover tests/ + +# Run specific test files +python -m unittest tests.parser.test_parser +python -m unittest tests.registry.test_get_actions +``` + +### Cluster Setup + +#### Option 1: Local simulated cluster (kind) +```bash +kind create cluster --config kind/kind-config-x86.yaml # For x86 +kind create cluster --config kind/kind-config-arm.yaml # For ARM + +# Configure AIOpsLab +cd aiopslab +cp config.yml.example config.yml +# Edit config.yml: set k8s_host to localhost or kind +``` + +#### Option 2: Azure VMs with Terraform + Ansible +See section: [Azure Deployment with Terraform + Ansible](#azure-deployment-with-terraform--ansible) + +### Cluster Management +```bash +# Use k9s for monitoring (recommended) +k9s + +# Or use kubectl directly +kubectl get pods -n +kubectl logs -n +``` + +## Core Architecture + +### Orchestrator Architecture + +**Central Coordination Flow:** +``` +Orchestrator + ├── init_problem() → deploys app, injects fault, starts workload + ├── start_problem() → agent-environment interaction loop + │ ├── ask_agent() → get next action from agent + │ ├── ask_env() → execute action via session.problem + │ └── returns observation to agent + └── Evaluates results and saves session +``` + +**Key Components:** +- **Orchestrator** (`aiopslab/orchestrator/orchestrator.py`): Central coordinator managing lifecycle from initialization to evaluation +- **Session** (`aiopslab/session.py`): Tracks agent-environment interaction history, timing, results; supports JSON export and W&B logging +- **ResponseParser** (`aiopslab/orchestrator/parser.py`): Extracts API calls from agent responses using AST parsing for complex arguments + +### Problem Structure + +Problems inherit from Task subclasses (Detection, Localization, Analysis, Mitigation) and define: + +- `inject_fault()`: Injects problem using fault injector +- `recover_fault()`: Cleans up injected fault +- `start_workload()`: Initiates load generation (can be sync or async) +- `eval(soln, trace, duration)`: Evaluates agent solution +- `get_task_description()`: Human/LLM-readable description +- `get_instructions()`: API usage instructions +- `get_available_actions()`: Dict of available APIs with docstrings + +**Problem ID Format:** `--` +- Example: `pod_failure_hotel_res-detection-1` +- Example: `k8s_target_port-misconfig-mitigation-2` + +**Problem Registry** (`aiopslab/orchestrator/problems/registry.py`): +- Centralized mapping of problem IDs to instances +- 60+ problems across multiple categories +- Access via `orch.probs.get_problem_ids()` or list at `/problems` endpoint + +### Agent Integration + +**Required Agent Interface:** +```python +class YourAgent: + def init_context(self, problem_desc: str, instructions: str, apis: dict): + """Initialize agent with problem context""" + pass + + async def get_action(self, observation: str) -> str: + """Return next action as markdown code block""" + return "Action:\n```\napi_name(args)\n```" +``` + +**Agent Lifecycle:** +1. Create agent instance with model parameters +2. Register with orchestrator: `orch.register_agent(agent, name="agent-name")` +3. Initialize problem: `problem_desc, instructs, apis = orch.init_problem(problem_id)` +4. Set agent context: `agent.init_context(problem_desc, instructs, apis)` +5. Start problem: `await orch.start_problem(max_steps=30)` + +**Agent Registry** (`clients/registry.py`): +- Maps agent names to implementations +- Supported: GPT, Qwen, DeepSeek, vLLM, OpenRouter, Groq +- Access via `AgentRegistry().get_agent_ids()` + +### Action System + +**Action Categories by Task:** +- **Detection**: `get_logs`, `get_metrics`, `get_traces`, `exec_shell`, `submit` +- **Localization**: Same as detection + targeted analysis +- **Mitigation**: All above + scaling, config patching, restarts +- **Analysis**: Comprehensive debugging capabilities + +**Action Decorators:** +```python +@action # Standard action +@read # Read-only (no state change) +@write # State-modifying action +``` + +Actions are dynamically discovered via these decorators and exposed to agents with auto-generated documentation from docstrings. + +**Action Execution:** +- Agent response parsed by `ResponseParser` +- Routed via `Task.perform_action(api_name, *args, **kwargs)` +- Returns string observation or error +- Invalid actions raise `InvalidActionError` + +### Service Layer + +**Application Base** (`aiopslab/service/apps/base.py`): +- Abstract interface for all applications +- Loads metadata from JSON (`aiopslab/service/metadata/`) +- Key attributes: `namespace`, `helm_configs`, `k8s_deploy_path`, `docker_deploy_path` +- Methods: `load_app_json()`, `get_app_summary()`, `create_namespace()`, `cleanup()` + +**Available Applications:** +- HotelReservation: Microservice hotel booking +- SocialNetwork: Social media app +- AstronomyShop: OpenTelemetry e-commerce demo +- FlightTicket, TrainTicket, TiDBCluster, Flower (federated learning) + +**Helm Integration** (`aiopslab/service/helm.py`): +- Static methods for chart install/uninstall +- Automatic dependency resolution +- Namespace creation and extra args support + +**Kubectl Wrapper** (`aiopslab/service/kubectl.py`): +- Abstracts K8s API calls +- Pod management, namespace ops, config maps, logs +- Container runtime detection (Docker/Containerd) + +### Fault Injection + +**Fault Injector Hierarchy:** +``` +FaultInjector (base) + ├── SymptomFaultInjector (Chaos Mesh-based) ← Most common + ├── ApplicationFaultInjector (App-level) + ├── OSFaultInjector (OS-level) + ├── HardwareFaultInjector + ├── OperatorFaultInjector (K8s operator) + ├── OTelFaultInjector (OpenTelemetry) + ├── VirtualFaultInjector (Mock faults) + └── NoOpFaultInjector (Testing) +``` + +**SymptomFaultInjector** (`aiopslab/generators/fault/inject_symp.py`): +- Uses Chaos Mesh v2.6.2 for fault injection +- Installed via Helm in `chaos-mesh` namespace +- Supported fault types: + - `inject_pod_failure()`: Kill pods + - `inject_network_delay()`: Add latency + - `inject_network_loss()`: Drop packets + - Container kills, resource exhaustion, etc. +- Pattern: `inject_()` and `recover_()` +- Creates temporary YAML for each experiment + +**Workload Generation** (`aiopslab/generators/workload/wrk.py`): +- Wraps wrk2 load generator +- Configurable: rate, connections, threads, duration, distribution (normal/exponential/uniform) +- Supports Lua script payloads via ConfigMap +- Launches as K8s Job for distributed load + +### Observability + +**Telemetry Collection** (`aiopslab/observer/observe.py`): +- Multi-threaded collection of traces, logs, metrics +- Time-window based (configurable start/end) +- Saves to: `telemetry_data_YYYYMMDD_HHMMSS/` + +**Metric Collection** (`aiopslab/observer/metric_api.py`): +- `PrometheusAPI` queries Prometheus +- Collects: CPU, memory, network I/O per pod/container +- Container limits, Istio metrics (latency, throughput) +- Exports as CSV/DataFrame + +**Log Collection** (`aiopslab/observer/log_api.py`): +- `LogAPI` connects to Elasticsearch +- Filters by namespace, pod, timestamp range +- Exports as CSV + +**Trace Collection** (`aiopslab/observer/trace_api.py`): +- `TraceAPI` queries Jaeger for distributed traces +- Service-level trace analysis + +### Evaluation + +**Evaluation Flow:** +1. Agent submits solution via `submit(answer)` +2. Problem's `eval(soln, trace, duration)` is called +3. Task base class provides default metrics: + - **Detection**: Accuracy (Yes/No), TTD (Time to Detection) + - **Localization**: Accuracy (service name match), TTL (Time to Localization) + - **Analysis**: Root cause accuracy, TTA (Time to Analysis) + - **Mitigation**: Success rate, TTM (Time to Mitigation) +4. Custom metrics added via `self.add_result(metric_name, value)` +5. Results saved to `data/results/` as JSON +6. Optional W&B logging (set `USE_WANDB=true` in .env) + +**LLM-as-Judge** (optional): +- Set `qualitative_eval: true` in `config.yml` +- Evaluates reasoning quality using LLM +- Prompts in `aiopslab/orchestrator/evaluators/prompts.py` + +## Code Patterns and Conventions + +### Async/Await Support +- `start_workload()` can be sync or async +- Orchestrator detects via `inspect.iscoroutinefunction()` +- All agent `get_action()` methods are async + +### Template Method Pattern +Task base class defines contract, subclasses override: +- `get_task_description()` +- `get_instructions()` +- `perform_action()` +- `eval()` + +### Factory Pattern +- `ProblemRegistry` creates problems on demand via lambdas +- `AgentRegistry` instantiates agents by name +- Enables parameterized problem variants + +### Context Manager Pattern +- `CriticalSection` for thread-safe fault recovery +- `atexit` handlers ensure cleanup on unexpected exit +- Fault recovery registered during `inject_fault()` + +### Configuration Management +**`aiopslab/config.yml`** (copy from `config.yml.example`): +- `k8s_host`: Control plane hostname (localhost/kind/) +- `k8s_user`: Username on control plane +- `ssh_key_path`: Path to SSH key +- `data_dir`: Where telemetry/results are stored +- `qualitative_eval`: Enable LLM-as-Judge evaluation +- `print_session`: Print session trace after completion + +**Environment Variables** (`.env`): +- API keys: `OPENAI_API_KEY`, `DEEPSEEK_API_KEY`, `DASHSCOPE_API_KEY`, `GROQ_API_KEY` +- W&B: `USE_WANDB=true` + +## Adding New Components + +### Adding a New Problem + +1. **Define Problem Class** (in `aiopslab/orchestrator/problems//`): +```python +from aiopslab.orchestrator.tasks.localization import LocalizationTask +from aiopslab.service.apps.myapp import MyApp + +class MyProblem(LocalizationTask): + def __init__(self): + self.app = MyApp() + + def start_workload(self): + # Workload generation logic + pass + + def inject_fault(self): + # Fault injection logic + pass + + def eval(self, soln, trace, duration): + super().eval(soln, trace, duration) # Default metrics + # Add custom metrics + self.add_result("custom_metric", value) + return self.results +``` + +2. **Register Problem** (in `aiopslab/orchestrator/problems/registry.py`): +```python +from aiopslab.orchestrator.problems.my_problem import MyProblem + +class ProblemRegistry: + def __init__(self): + self.PROBLEM_REGISTRY = { + # ... existing problems ... + "my_problem-localization-1": MyProblem, + } +``` + +### Adding a New Application + +1. **Create Metadata JSON** (`aiopslab/service/metadata/myapp.json`): +```json +{ + "name": "MyApp", + "description": "Description of the app", + "namespace": "test-myapp", + "Helm Config": { + "release_name": "myapp-release", + "chart_path": "path/to/helm/chart", + "namespace": "test-myapp" + } +} +``` + +2. **Create Application Class** (`aiopslab/service/apps/myapp.py`): +```python +from aiopslab.service.apps.base import Application + +class MyApp(Application): + def __init__(self): + super().__init__("path/to/metadata/myapp.json") +``` + +### Adding a New Agent + +1. **Implement Agent** (`clients/myagent.py`): +```python +class MyAgent: + def init_context(self, problem_desc: str, instructions: str, apis: dict): + self.problem_desc = problem_desc + self.instructions = instructions + self.apis = apis + + async def get_action(self, observation: str) -> str: + # Your agent logic + return f"Action:\n```\n{api_call}\n```" +``` + +2. **Register Agent** (`clients/registry.py`): +```python +from clients.myagent import MyAgent + +class AgentRegistry: + def __init__(self): + self.AGENT_REGISTRY = { + # ... existing agents ... + "myagent": MyAgent, + } +``` + +## Important Implementation Notes + +### Shell Command Restrictions +- No interactive commands: `kubectl edit`, `docker logs -f` +- Use specific APIs instead: `get_logs()`, `get_metrics()`, `get_traces()` + +### Problem Execution Lifecycle +1. **Initialization**: Deploy app, create storage (OpenEBS for K8s) +2. **Fault Injection**: Deploy Chaos Mesh experiment, register recovery +3. **Workload Start**: Launch wrk2 job at specified rate +4. **Agent Loop**: Up to max_steps iterations of agent-environment interaction +5. **Evaluation**: Measure correctness and efficiency +6. **Cleanup**: Recover fault, delete namespace, remove storage + +### Fault Recovery +- Always registered with `atexit` during `inject_fault()` +- Thread-safe via `CriticalSection` context manager +- Cleanup happens even on unexpected termination + +### Testing Strategy +- Unit tests in `tests/` organized by functionality +- Parser tests: API extraction, argument parsing, context extraction +- Registry tests: Action discovery via decorators +- Shell tests: Command execution validation +- Use mocks where possible to avoid K8s dependencies + +## Key Files Reference + +- `cli.py`: Interactive CLI for human agents +- `service.py`: FastAPI service for remote execution +- `assessment.py`: Batch evaluation script +- `aiopslab/orchestrator/orchestrator.py`: Main orchestration engine +- `aiopslab/session.py`: Session tracking and persistence +- `aiopslab/orchestrator/parser.py`: Action parsing from agent responses +- `aiopslab/orchestrator/problems/registry.py`: Problem definitions +- `clients/registry.py`: Agent implementations +- `aiopslab/service/apps/`: Application interfaces +- `aiopslab/generators/fault/`: Fault injection implementations +- `aiopslab/generators/workload/wrk.py`: Workload generation +- `aiopslab/observer/`: Telemetry collection (logs, metrics, traces) + +## Common Workflows + +### Evaluating an Agent on a Problem +```python +from aiopslab.orchestrator import Orchestrator +from clients.gpt import GPTAgent + +# Create and register agent +agent = GPTAgent() +orch = Orchestrator() +orch.register_agent(agent, name="gpt-agent") + +# Initialize and start problem +problem_desc, instructs, apis = orch.init_problem("pod_failure_hotel_res-detection-1") +agent.init_context(problem_desc, instructs, apis) +await orch.start_problem(max_steps=30) + +# Results saved to data/results/.json +``` + +### Batch Evaluation +```python +# Use assessment.py for batch evaluation +python assessment.py +# Configure problems and agents in the script +``` + +### Debugging Agent Responses +- Enable session printing: `print_session: true` in `config.yml` +- Check parsed actions in session trace +- Use `ResponseParser` directly for testing: + ```python + from aiopslab.orchestrator.parser import ResponseParser + parser = ResponseParser() + result = parser.parse(agent_response) + ``` + +--- + +## Azure Deployment with Terraform + Ansible + +### Deployment Modes + +| Mode | AIOpsLab Runs On | K8s Cluster | Use Case | +|------|------------------|-------------|----------| +| **Mode A** | Controller VM (inside cluster) | Same machine | Production, full fault injection support | +| **Mode B** | Your laptop (remote kubectl) | Azure VMs | Development, debugging | + +**Note:** `VirtualizationFaultInjector` requires Docker on the machine running AIOpsLab. Use official Poetry installer, not `apt install python3-poetry`. + +**Tested on:** WSL2 (Ubuntu 22.04) on Windows 11 with Azure VMs (Ubuntu 22.04 LTS, amd64). The `deploy.py` auto-install targets Linux/amd64; macOS and native Windows are not currently supported. + +### Quick Start (Mode B - Laptop) + +```bash +# Single command: provisions VMs, runs Ansible, installs tools, configures AIOpsLab +python3 scripts/terraform/deploy.py --apply --resource-group --workers 2 --mode B + +# After deploy completes, start AIOpsLab: +eval $(poetry env activate) +python3 cli.py +``` + +### Quick Start (Mode A - Controller VM) + +```bash +# Clone mode: git clones the repo on the controller +python3 scripts/terraform/deploy.py --apply --resource-group --workers 2 --mode A + +# Dev mode: rsync local code to the controller +python3 scripts/terraform/deploy.py --apply --resource-group --workers 2 --mode A --dev + +# After deploy, SSH to controller: +ssh -i ~/.ssh/id_rsa azureuser@ +cd ~/AIOpsLab && eval $(poetry env activate) +python3 cli.py +``` + +### Other deploy.py commands +```bash +# Dry-run: +python3 scripts/terraform/deploy.py --plan --resource-group --workers 2 + +# Re-run setup without reprovisioning VMs (e.g., after code changes): +python3 scripts/terraform/deploy.py --setup-only --mode A --dev + +# Restrict NSG access (SSH + K8s API) to a service tag or CIDR: +python3 scripts/terraform/deploy.py --apply --resource-group --allowed-ips CorpNetPublic + +# Destroy infrastructure: +python3 scripts/terraform/deploy.py --destroy --resource-group +``` + +### Key Files + +| File | Purpose | +|------|---------| +| `scripts/terraform/deploy.py` | Single-command deployment (Terraform + Ansible + AIOpsLab setup) | +| `scripts/terraform/main.tf` | Azure VM provisioning (controller + workers) | +| `scripts/terraform/variables.tf` | Configurable parameters (VM size, count, etc.) | +| `scripts/terraform/generate_inventory.py` | Creates Ansible inventory from Terraform output | +| `scripts/ansible/setup_common.yml` | Installs Docker, K8s packages on all nodes | +| `scripts/ansible/remote_setup_controller_worker.yml` | Initializes K8s cluster, joins workers | +| `scripts/ansible/setup_aiopslab.yml` | Mode A: installs Python 3.11, Poetry, Helm, clones/rsyncs repo, runs poetry install | +| `scripts/ansible/templates/config.yml.j2` | Mode A: Jinja2 template for aiopslab/config.yml | +| `scripts/ansible/inventory.yml` | Generated inventory (don't edit manually) | + +### Important Configuration + +**Ansible Inventory Variables** (`inventory.yml`): +- `k8s_user`: SSH username (e.g., `azureuser`) +- `user_home_base`: Home directory base path + - `/home` for cloud VMs (Azure, AWS, GCP) + - `/users` for Emulab testbed +- `private_ip`: Internal IP for K8s cluster communication +- `ansible_host`: Public IP for SSH access + +**AIOpsLab config.yml** (for Mode B): +```yaml +k8s_host: # e.g., 20.150.145.167 +k8s_user: azureuser +ssh_key_path: ~/.ssh/id_rsa +``` + +### Common Issues and Fixes + +| Issue | Cause | Fix | +|-------|-------|-----| +| `conntrack not found` | Missing package | Added to `setup_common.yml` prerequisites | +| Certificate error with kubectl | Cert missing public IP | Playbook adds `--apiserver-cert-extra-sans` | +| Kubeconfig uses private IP | Can't reach from laptop | Playbook auto-updates to public IP | +| Helm chart not found | Submodules not cloned | Run `git submodule update --init --recursive` | +| Submodule init fails in WSL | Worktree `.git` file has Windows paths | Run from Git Bash, not WSL | +| `poetry shell` not found | Removed in Poetry 2.0 | Use `eval $(poetry env activate)` instead | +| Poetry "not supported" Python | System python too old | `poetry env use python3.11 && poetry install` | +| Path `/users/` not found | Wrong home base for cloud | Set `user_home_base: /home` in inventory | + +### NSG (Network Security Group) Rules + +The Terraform config creates NSG rules for: +- **SSH (22)**: Open to all (`*`) by default. Restrict via `--allowed-ips` flag or `nsg_allowed_source` variable. +- **K8s API (6443)**: Open to all (`*`) by default. Restrict via `--allowed-ips` flag or `nsg_allowed_source` variable. + +To allow access from other IPs, modify `main.tf` or add rules via Azure CLI. + +### Destroying Infrastructure + +```bash +cd scripts/terraform +terraform destroy -var="resource_group_name=" +``` + +### Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Your Laptop (WSL) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Terraform │→ │ Ansible │→ │ AIOpsLab │ │ +│ │ (Azure VMs)│ │ (K8s setup) │ │ (kubectl + cli.py) │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Azure (VNet 10.0.0.0/16) │ +│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Controller │ │ Worker 1 │ │ Worker N │ │ +│ │ (K8s control) │ │ │ │ │ │ +│ │ Public + Priv │ │ Private IP │ │ Private IP │ │ +│ └─────────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` diff --git a/README.md b/README.md index cdf87afd..53a1921c 100644 --- a/README.md +++ b/README.md @@ -29,23 +29,41 @@ Moreover, AIOpsLab provides a built-in benchmark suite with a set of problems to ### Requirements - Python >= 3.11 - [Helm](https://helm.sh/) +- [Poetry](https://python-poetry.org/docs/) (recommended) or pip - Additional requirements depend on the deployment option selected, which is explained in the next section -Recommended installation: +### Step 1: Install Python 3.11 ```bash -sudo apt install python3.11 python3.11-venv python3.11-dev python3-pip # poetry requires python >= 3.11 +sudo apt update +sudo apt install python3.11 python3.11-venv python3.11-dev -y ``` -We recommend [Poetry](https://python-poetry.org/docs/) for managing dependencies. You can also use a standard `pip install -e .` to install the dependencies. +### Step 2: Install Poetry (Official Installer) +```bash +# Use the official installer (NOT apt - the apt version is outdated) +curl -sSL https://install.python-poetry.org | python3.11 - +export PATH="$HOME/.local/bin:$PATH" + +# Add to your shell profile for persistence +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +``` + +> **Warning**: Do NOT use `sudo apt install python3-poetry` - it installs an outdated version that may not work with the lock file. +### Step 3: Clone and Install ```bash git clone --recurse-submodules cd AIOpsLab poetry env use python3.11 -export PATH="$HOME/.local/bin:$PATH" # export poetry to PATH if needed -poetry install # -vvv for verbose output -poetry self add poetry-plugin-shell # installs poetry shell plugin -poetry shell +poetry install +eval $(poetry env activate) +``` + +> **Troubleshooting**: If you get a "lock file not compatible" error, run `poetry lock` first, then `poetry install`. + +Alternative installation with pip: +```bash +pip install -e . ```

🚀 Quick Start

@@ -81,9 +99,24 @@ export no_proxy=localhost After finishing cluster creation, proceed to the next "Update `config.yml`" step. -### b) Remote cluster +### b) Remote cluster (Manual setup with Ansible) AIOpsLab supports any remote kubernetes cluster that your `kubectl` context is set to, whether it's a cluster from a cloud provider or one you build yourself. We have some Ansible playbooks to setup clusters on providers like [CloudLab](https://www.cloudlab.us/) and our own machines. Follow this [README](./scripts/ansible/README.md) to set up your own cluster, and then proceed to the next "Update `config.yml`" step. +### c) Azure VMs with Terraform + Ansible (Recommended for cloud) +Single command provisions VMs, sets up K8s, and configures AIOpsLab: + +```bash +# Mode B (AIOpsLab on laptop, remote kubectl): +python3 scripts/terraform/deploy.py --apply --resource-group --workers 2 --mode B + +# Mode A (AIOpsLab on controller VM, full fault injection support): +python3 scripts/terraform/deploy.py --apply --resource-group --workers 2 --mode A +``` + +See [Terraform README](./scripts/terraform/README.md) for all options (`--allowed-ips`, `--dev`, `--setup-only`, etc.). + +> **Note**: Mode B is convenient for development but some fault injectors (e.g., VirtualizationFaultInjector) require Docker on the local machine. Use Mode A for full functionality. + ### Update `config.yml` ```bash cd aiopslab diff --git a/TutorialSetup.md b/TutorialSetup.md index dbc126dc..ba991f62 100644 --- a/TutorialSetup.md +++ b/TutorialSetup.md @@ -20,8 +20,7 @@ cd AIOpsLab poetry env use python3.11 export PATH="$HOME/.local/bin:$PATH" # export poetry to PATH if needed poetry install # -vvv for verbose output -poetry self add poetry-plugin-shell # installs poetry shell plugin -poetry shell +eval $(poetry env activate) ``` Create `config.yml` diff --git a/aiopslab/generators/fault/inject_symp.py b/aiopslab/generators/fault/inject_symp.py index 1c56bdf4..b1eb23cf 100644 --- a/aiopslab/generators/fault/inject_symp.py +++ b/aiopslab/generators/fault/inject_symp.py @@ -23,10 +23,17 @@ def __init__(self, namespace: str): "chart_path": "chaos-mesh/chaos-mesh", "namespace": "chaos-mesh", "version": "2.6.2", + "remote_chart": True, } container_runtime = self.kubectl.get_container_runtime() + if container_runtime is None: + raise ValueError( + "Could not detect container runtime. " + "Ensure the cluster is running and at least one node is Ready." + ) + if "docker" in container_runtime: pass elif "containerd" in container_runtime: diff --git a/aiopslab/orchestrator/problems/misconfig_app/misconfig_app_hotel_res.py b/aiopslab/orchestrator/problems/misconfig_app/misconfig_app_hotel_res.py index 822d777d..58084577 100644 --- a/aiopslab/orchestrator/problems/misconfig_app/misconfig_app_hotel_res.py +++ b/aiopslab/orchestrator/problems/misconfig_app/misconfig_app_hotel_res.py @@ -3,7 +3,6 @@ """MongoDB storage user unregistered problem in the HotelReservation application.""" -from time import sleep from typing import Any from aiopslab.orchestrator.tasks import * @@ -167,32 +166,12 @@ def eval(self, soln: Any, trace: list[SessionItem], duration: float) -> dict: super().eval(soln, trace, duration) # Check if all services (not only faulty service) is back to normal (Running) - all_normal = True - # Polling for 1 minute to check if all services are back to normal - for _ in range(12): # 5 seconds interval, 12 times, total 1 minute - pod_list = self.kubectl.list_pods(self.namespace) - for pod in pod_list.items: - # Check container statuses - for container_status in pod.status.container_statuses: - if container_status.state.waiting: - reason = container_status.state.waiting.reason - if reason in ["CrashLoopBackOff", "Error", "ImagePullBackOff", "ErrImagePull"]: - print(f"Container {container_status.name} is in error state: {reason}") - all_normal = False - elif container_status.state.terminated and container_status.state.terminated.reason != "Completed": - print(f"Container {container_status.name} is terminated with reason: {container_status.state.terminated.reason}") - all_normal = False - elif not container_status.ready: - print(f"Container {container_status.name} is not ready") - all_normal = False - - if not all_normal: - break - - if not all_normal: - break - # Wait for 5 seconds before checking again - sleep(5) - - self.results["success"] = all_normal + # Use wait_for_ready helper to poll for pod readiness + try: + self.kubectl.wait_for_ready(self.namespace, sleep=5, max_wait=60) + self.results["success"] = True + except Exception as e: + print(f"Pods are not all ready: {e}") + self.results["success"] = False + return self.results diff --git a/aiopslab/service/helm.py b/aiopslab/service/helm.py index c436f890..2ac23ffe 100644 --- a/aiopslab/service/helm.py +++ b/aiopslab/service/helm.py @@ -3,12 +3,23 @@ """Interface for helm operations""" +import os import subprocess from aiopslab.service.kubectl import KubeCtl class Helm: + @staticmethod + def _validate_chart_path(chart_path, remote_chart): + """Check that a local chart path exists, raising if not.""" + if not remote_chart and chart_path and not os.path.exists(chart_path): + raise FileNotFoundError( + f"Helm chart not found at: {chart_path}\n" + f"This is likely because git submodules were not cloned.\n" + f"Run: git submodule update --init --recursive" + ) + @staticmethod def install(**args): """Install a helm chart @@ -29,6 +40,8 @@ def install(**args): extra_args = args.get("extra_args") remote_chart = args.get("remote_chart", False) + Helm._validate_chart_path(chart_path, remote_chart) + if not remote_chart: # Install dependencies for chart before installation dependency_command = f"helm dependency update {chart_path}" @@ -141,6 +154,10 @@ def upgrade(**args): values_file = args.get("values_file") set_values = args.get("set_values", {}) + remote_chart = args.get("remote_chart", False) + + Helm._validate_chart_path(chart_path, remote_chart) + command = [ "helm", "upgrade", @@ -148,10 +165,11 @@ def upgrade(**args): chart_path, "-n", namespace, - "-f", - values_file, ] + if values_file: + command.extend(["-f", values_file]) + # Add --set options if provided for key, value in set_values.items(): command.append("--set") diff --git a/aiopslab/service/kubectl.py b/aiopslab/service/kubectl.py index 560a9ad5..a80855f4 100644 --- a/aiopslab/service/kubectl.py +++ b/aiopslab/service/kubectl.py @@ -18,10 +18,20 @@ def __init__(self): """Initialize the KubeCtl object and load the Kubernetes configuration.""" import os - - # Support parallel execution via AIOPSLAB_CLUSTER environment variable - cluster_env = os.environ.get('AIOPSLAB_CLUSTER', 'kind') - context = f"kind-{cluster_env}" + from aiopslab.paths import config as app_config + + # For kind clusters, support parallel execution via AIOPSLAB_CLUSTER env var. + # For remote clusters (k8s_host is an IP/hostname), use the default kubeconfig context. + k8s_host = app_config.get("k8s_host", "kind") + cluster_env = os.environ.get('AIOPSLAB_CLUSTER') + + if cluster_env: + context = f"kind-{cluster_env}" + elif k8s_host == "kind": + context = "kind-kind" + else: + context = None # use default kubeconfig context + config.load_kube_config(context=context) self.core_v1_api = client.CoreV1Api() @@ -44,15 +54,27 @@ def get_cluster_ip(self, service_name, namespace): service_info = self.core_v1_api.read_namespaced_service(service_name, namespace) return service_info.spec.cluster_ip # type: ignore - def get_container_runtime(self): + def get_container_runtime(self, max_wait: int = 60, poll_interval: int = 2): """ Retrieve the container runtime used by the cluster. If the cluster uses multiple container runtimes, the first one found will be returned. + + Args: + max_wait: Maximum seconds to wait for a Ready node (default: 60) + poll_interval: Seconds between checks (default: 2) + + Returns: + Container runtime version string, or None if no Ready node found within max_wait. """ - for node in self.core_v1_api.list_node().items: - for status in node.status.conditions: - if status.type == "Ready" and status.status == "True": - return node.status.node_info.container_runtime_version + elapsed = 0 + while elapsed < max_wait: + for node in self.core_v1_api.list_node().items: + for status in node.status.conditions: + if status.type == "Ready" and status.status == "True": + return node.status.node_info.container_runtime_version + time.sleep(poll_interval) + elapsed += poll_interval + return None def get_pod_name(self, namespace, label_selector): """Get the name of the first pod in a namespace that matches a given label selector.""" @@ -76,6 +98,19 @@ def get_deployment(self, name: str, namespace: str): """Fetch the deployment configuration.""" return self.apps_v1_api.read_namespaced_deployment(name, namespace) + @staticmethod + def _pod_is_ready_or_succeeded(pod): + """Return True when a pod is ready or has completed successfully.""" + status = getattr(pod, "status", None) + if getattr(status, "phase", None) == "Succeeded": + return True + + container_statuses = getattr(status, "container_statuses", None) + return bool(container_statuses) and all( + getattr(container_status, "ready", False) + for container_status in container_statuses + ) + def wait_for_ready(self, namespace, sleep=2, max_wait=300): """Wait for all pods in a namespace to be in a Ready state before proceeding.""" @@ -92,8 +127,7 @@ def wait_for_ready(self, namespace, sleep=2, max_wait=300): if pod_list.items: ready_pods = [ pod for pod in pod_list.items - if pod.status.container_statuses and - all(cs.ready for cs in pod.status.container_statuses) + if self._pod_is_ready_or_succeeded(pod) ] if len(ready_pods) == len(pod_list.items): diff --git a/aiopslab/service/telemetry/prometheus.py b/aiopslab/service/telemetry/prometheus.py index 25a2b72b..fa737352 100644 --- a/aiopslab/service/telemetry/prometheus.py +++ b/aiopslab/service/telemetry/prometheus.py @@ -132,12 +132,19 @@ def _is_prometheus_running(self) -> bool: namespace = self.helm_configs.get("namespace") if not release_name or not namespace: return False - status_output = Helm.status(release_name=release_name, namespace=namespace) + status_output = Helm.status( + release_name=self.name.lower(), + namespace=self.namespace, + ) for line in status_output.splitlines(): if line.strip().startswith("STATUS:"): status_value = line.split(":", 1)[1].strip().lower() return status_value == "deployed" return False + except RuntimeError: + logging.warning("Prometheus release not found, will install.") + return False except Exception as e: logging.exception(f"Unexpected error while checking Prometheus status: {e}") return False + diff --git a/clients/README.md b/clients/README.md index 95d6e433..d0147d6a 100644 --- a/clients/README.md +++ b/clients/README.md @@ -13,6 +13,7 @@ These clients are some baselines that we have implemented and evaluated to help - [ReAct](/clients/react.py): A naive LLM agent that uses the ReAct framework. - [FLASH](/clients/flash.py): A naive LLM agent that uses status supervision and hindsight integration components to ensure the high reliability of workflow execution. - [OpenRouter](/clients/openrouter.py): A naive OpenRouter LLM agent with only shell access. +- [Generic OpenAI](/clients/generic_openai.py): A generic agent that works with any provider exposing the [OpenAI Chat Completions API](https://platform.openai.com/docs/api-reference/chat) (`/v1/chat/completions`), such as [Poe](https://creator.poe.com/docs/external-applications/openai-compatible-api), vLLM, LocalAI, standard OpenAI deployments, or other compatible services. The `base_url` and model are fully configurable via environment variables. ### Using the vLLM Client @@ -84,6 +85,19 @@ cp .env.example .env - `OPENROUTER_MODEL`: OpenRouter model to use (default: `openai/gpt-4o-mini`) - `USE_WANDB`: Enable Weights & Biases logging (default: `false`) +### Generic OpenAI-Compatible Client +The [Generic OpenAI client](/clients/generic_openai.py) works with any provider that implements the OpenAI Chat Completions API (`/v1/chat/completions`), such as [Poe](https://creator.poe.com/docs/external-applications/openai-compatible-api), vLLM, LocalAI, or standard OpenAI. + +Set the following environment variables: +- `OPENAI_COMPATIBLE_API_KEY`: API key for your target endpoint (required) +- `OPENAI_COMPATIBLE_BASE_URL`: Base URL of your target endpoint, e.g. `https://api.poe.com/llm/v1` (required) +- `OPENAI_COMPATIBLE_MODEL`: Model name to use, e.g. `MiniMax-Text-01` (default: `gpt-4o`) + +Then run: +```bash +python clients/generic_openai.py +``` + ### Keyless Authentication The script [`gpt_azure_identity.py`](/clients/gpt_azure_identity.py) supports keyless authentication for **securely** accessing Azure OpenAI endpoints. It supports two authentication methods: diff --git a/clients/deepseek.py b/clients/deepseek.py index 7227bd78..8a110d0e 100644 --- a/clients/deepseek.py +++ b/clients/deepseek.py @@ -22,7 +22,7 @@ def __init__(self): self.history = [] self.llm = DeepSeekClient() - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict( diff --git a/clients/flash.py b/clients/flash.py index 01e3d13d..8f194c83 100644 --- a/clients/flash.py +++ b/clients/flash.py @@ -55,7 +55,7 @@ def __init__(self): self.llm = GPTClient() self.hindsight_builder = HindsightBuilder() - def init_context(self, problem_desc: str, instructions: str, apis: dict): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) self.submit_api = self._filter_dict(apis, lambda k, _: "submit" in k) self.telemetry_apis = self._filter_dict( @@ -105,7 +105,7 @@ async def get_action(self, input_text: str) -> str: return response[0] - async def diagnose_with_hindsight(self, input: str, history: dict): + async def diagnose_with_hindsight(self, input: str, history: list[dict[str, str]]): """Diagnose the incident and integrate hindsight from the environment status.""" logger.info("Starting diagnosis with hindsight integration...") hindsight = self.hindsight_builder.develop_hindsight(input, history) @@ -147,7 +147,7 @@ def generate_prompt(self, input: str, history: List[Dict]) -> str: return prompt - def develop_hindsight(self, input: str, history: dict) -> str: + def develop_hindsight(self, input: str, history: list[dict[str, str]]) -> str: """ Develop hindsight based on the input and provide guidance for the next action. """ diff --git a/clients/generic_openai.py b/clients/generic_openai.py new file mode 100644 index 00000000..96eb474c --- /dev/null +++ b/clients/generic_openai.py @@ -0,0 +1,100 @@ +"""Generic OpenAI-compatible chat client (with shell access) for AIOpsLab. + +This agent works with any provider that implements the OpenAI Chat Completions +API endpoint (/v1/chat/completions), such as Poe +(https://creator.poe.com/docs/external-applications/openai-compatible-api), +standard OpenAI deployments, vLLM, LocalAI, or other compatible services. + +Configure the endpoint and model via environment variables or constructor arguments: + OPENAI_COMPATIBLE_API_KEY — API key for the target endpoint + OPENAI_COMPATIBLE_BASE_URL — Base URL of the target endpoint (e.g. https://api.poe.com/llm/v1) + OPENAI_COMPATIBLE_MODEL — Model name to use (e.g. MiniMax-Text-01) +""" + +import os +import asyncio +import wandb +from aiopslab.orchestrator import Orchestrator +from aiopslab.orchestrator.problems.registry import ProblemRegistry +from clients.utils.llm import GenericOpenAIClient +from clients.utils.templates import DOCS_SHELL_ONLY +from dotenv import load_dotenv + +# Load environment variables from the .env file +load_dotenv() + + +class GenericOpenAIAgent: + def __init__( + self, + base_url: str | None = None, + model: str | None = None, + api_key: str | None = None, + ): + self.history = [] + self.llm = GenericOpenAIClient( + base_url=base_url, + model=model, + api_key=api_key, + ) + + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): + """Initialize the context for the agent.""" + + self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) + self.submit_api = self._filter_dict(apis, lambda k, _: "submit" in k) + stringify_apis = lambda apis: "\n\n".join( + [f"{k}\n{v}" for k, v in apis.items()] + ) + + self.system_message = DOCS_SHELL_ONLY.format( + prob_desc=problem_desc, + shell_api=stringify_apis(self.shell_api), + submit_api=stringify_apis(self.submit_api), + ) + + self.task_message = instructions + + self.history.append({"role": "system", "content": self.system_message}) + self.history.append({"role": "user", "content": self.task_message}) + + async def get_action(self, input) -> str: + """Wrapper to interface the agent with AIOpsLab. + + Args: + input (str): The input from the orchestrator/environment. + + Returns: + str: The response from the agent. + """ + self.history.append({"role": "user", "content": input}) + response = self.llm.run(self.history) + model_name = self.llm.model + print(f"===== Agent (GenericOpenAI - {model_name}) ====\n{response[0]}") + self.history.append({"role": "assistant", "content": response[0]}) + return response[0] + + def _filter_dict(self, dictionary, filter_func): + return {k: v for k, v in dictionary.items() if filter_func(k, v)} + + +if __name__ == "__main__": + # Load use_wandb from environment variable with a default of False + use_wandb = os.getenv("USE_WANDB", "false").lower() == "true" + + if use_wandb: + wandb.init(project="AIOpsLab", entity="AIOpsLab") + + problems = ProblemRegistry().PROBLEM_REGISTRY + for pid in problems: + agent = GenericOpenAIAgent() + + orchestrator = Orchestrator() + orchestrator.register_agent(agent, name="generic-openai") + + problem_desc, instructs, apis = orchestrator.init_problem(pid) + agent.init_context(problem_desc, instructs, apis) + asyncio.run(orchestrator.start_problem(max_steps=30)) + + if use_wandb: + wandb.finish() diff --git a/clients/gpt.py b/clients/gpt.py index 5e6d4b46..8b85f2c1 100644 --- a/clients/gpt.py +++ b/clients/gpt.py @@ -62,7 +62,7 @@ def __init__(self): def test(self): return self.llm.run([{"role": "system", "content": "hello"}]) - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) diff --git a/clients/gpt_azure_identity.py b/clients/gpt_azure_identity.py index aaf3dba8..4ab123a4 100644 --- a/clients/gpt_azure_identity.py +++ b/clients/gpt_azure_identity.py @@ -21,7 +21,7 @@ def __init__(self, auth_type: str, azure_config_file: str): self.history = [] self.llm = GPTClient(auth_type=auth_type, azure_config_file=azure_config_file) - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) diff --git a/clients/llama.py b/clients/llama.py index a05795e4..f06c0e86 100644 --- a/clients/llama.py +++ b/clients/llama.py @@ -14,7 +14,7 @@ def __init__(self): self.history = [] self.llm = LLaMAClient() - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.telemetry_apis = self._filter_dict(apis, lambda k, _: "get_logs" in k) diff --git a/clients/openrouter.py b/clients/openrouter.py index 6db2a706..8f2c3ddd 100644 --- a/clients/openrouter.py +++ b/clients/openrouter.py @@ -63,7 +63,7 @@ def __init__(self, model="anthropic/claude-3.5-sonnet"): def test(self): return self.llm.run([{"role": "system", "content": "hello"}]) - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) diff --git a/clients/qwen.py b/clients/qwen.py index 2feed49d..9252205c 100644 --- a/clients/qwen.py +++ b/clients/qwen.py @@ -15,7 +15,7 @@ def __init__(self): self.history = [] self.llm = QwenClient() - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict( diff --git a/clients/react.py b/clients/react.py index a1a27051..86f6edb5 100644 --- a/clients/react.py +++ b/clients/react.py @@ -60,7 +60,7 @@ def __init__(self): self.history = [] self.llm = GPTClient() - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict(apis, lambda k, _: "exec_shell" in k) diff --git a/clients/registry.py b/clients/registry.py index d881c761..82cfd358 100644 --- a/clients/registry.py +++ b/clients/registry.py @@ -5,6 +5,7 @@ from clients.deepseek import DeepSeekAgent from clients.vllm import vLLMAgent from clients.openrouter import OpenRouterAgent +from clients.generic_openai import GenericOpenAIAgent class AgentRegistry: """Registry for agent implementations.""" @@ -16,6 +17,7 @@ def __init__(self): "deepseek": DeepSeekAgent, "vllm": vLLMAgent, "openrouter": OpenRouterAgent, + "generic": GenericOpenAIAgent, } def register(self, name, agent_cls): diff --git a/clients/utils/llm.py b/clients/utils/llm.py index 6632a80a..48a86cb0 100644 --- a/clients/utils/llm.py +++ b/clients/utils/llm.py @@ -132,7 +132,7 @@ def inference(self, payload: list[dict[str, str]]) -> list[str]: ) except Exception as e: print(f"Exception: {repr(e)}") - raise e + raise return [c.message.content for c in response.choices] # type: ignore @@ -168,7 +168,7 @@ def inference(self, payload: list[dict[str, str]]) -> list[str]: except Exception as e: print(f"Exception: {repr(e)}") - raise e + raise return [c.message.content for c in response.choices] # type: ignore @@ -207,7 +207,7 @@ def inference(self, payload: list[dict[str, str]]) -> list[str]: ) except Exception as e: print(f"Exception: {repr(e)}") - raise e + raise reasoning_content = "" answer_content = "" @@ -274,7 +274,7 @@ def inference(self, payload: list[dict[str, str]]) -> list[str]: ) except Exception as e: print(f"Exception: {repr(e)}") - raise e + raise return [c.message.content for c in response.choices] # type: ignore @@ -318,7 +318,85 @@ def inference(self, payload: list[dict[str, str]]) -> list[str]: ) except Exception as e: print(f"Exception: {repr(e)}") - raise e + raise + + return [c.message.content for c in response.choices] # type: ignore + + def run(self, payload: list[dict[str, str]]) -> list[str]: + response = self.inference(payload) + if self.cache is not None: + self.cache.add_to_cache(payload, response) + self.cache.save_cache() + return response + + +class GenericOpenAIClient: + """Generic client for any OpenAI Chat Completions compatible endpoint. + + Uses the standard Chat Completions API (client.chat.completions.create), + making it compatible with any provider that implements the OpenAI Chat + Completions spec — including Poe, OpenRouter, vLLM, LocalAI, DeepSeek, + and standard OpenAI deployments, as well as Azure- or other cloud-hosted + gateways that expose an OpenAI-compatible `/v1/chat/completions` endpoint + via `base_url`. + + Note: Native Azure OpenAI endpoints typically require the AzureOpenAI client + with an `azure_endpoint` and `api_version`, and are not used via `base_url` + in this class unless they are fronted by such a compatibility gateway. + Environment variables: + OPENAI_COMPATIBLE_API_KEY: API key for the target endpoint. + OPENAI_COMPATIBLE_BASE_URL: Base URL of the target endpoint. + OPENAI_COMPATIBLE_MODEL: Model name to use (default: gpt-4o). + + All three can be overridden by passing explicit arguments to the constructor. + """ + + def __init__( + self, + base_url: Optional[str] = None, + model: Optional[str] = None, + api_key: Optional[str] = None, + max_tokens: int = 16000, + ): + self.cache = Cache() + self.model = model or os.getenv("OPENAI_COMPATIBLE_MODEL", "gpt-4o") + self.max_tokens = max_tokens + resolved_base_url = base_url or os.getenv("OPENAI_COMPATIBLE_BASE_URL") + if not resolved_base_url: + raise ValueError( + "base_url must be provided either as a constructor argument or via " + "the OPENAI_COMPATIBLE_BASE_URL environment variable." + ) + resolved_api_key = api_key or os.getenv("OPENAI_COMPATIBLE_API_KEY") + if not resolved_api_key: + raise ValueError( + "api_key must be provided either as a constructor argument or via " + "the OPENAI_COMPATIBLE_API_KEY environment variable." + ) + self.client = OpenAI(api_key=resolved_api_key, base_url=resolved_base_url) + + def inference(self, payload: list[dict[str, str]]) -> list[str]: + if self.cache is not None: + cache_result = self.cache.get_from_cache(payload) + if cache_result is not None: + return cache_result + + try: + response = self.client.chat.completions.create( + messages=payload, # type: ignore + model=self.model, + max_tokens=self.max_tokens, + temperature=0.5, + top_p=0.95, + frequency_penalty=0.0, + presence_penalty=0.0, + n=1, + timeout=60, + stop=[], + ) + except Exception as e: + print(f"Exception: {repr(e)}") + raise return [c.message.content for c in response.choices] # type: ignore @@ -358,7 +436,7 @@ def inference(self, payload: list[dict[str, str]]) -> list[str]: ) except Exception as e: print(f"Exception: {repr(e)}") - raise e + raise return [c.message.content for c in response.choices] # type: ignore diff --git a/clients/vllm.py b/clients/vllm.py index 64e67730..3656f7e5 100644 --- a/clients/vllm.py +++ b/clients/vllm.py @@ -26,7 +26,7 @@ def __init__(self, max_tokens=max_tokens, ) - def init_context(self, problem_desc: str, instructions: str, apis: str): + def init_context(self, problem_desc: str, instructions: str, apis: dict[str, str]): """Initialize the context for the agent.""" self.shell_api = self._filter_dict( diff --git a/poetry.lock b/poetry.lock index 03003382..81dccb58 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,7 +6,7 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -14,132 +14,131 @@ files = [ [[package]] name = "aiohttp" -version = "3.13.3" +version = "3.14.3" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7"}, - {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821"}, - {file = "aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455"}, - {file = "aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29"}, - {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11"}, - {file = "aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd"}, - {file = "aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64"}, - {file = "aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1"}, - {file = "aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4"}, - {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29"}, - {file = "aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239"}, - {file = "aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168"}, - {file = "aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc"}, - {file = "aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce"}, - {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a"}, - {file = "aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046"}, - {file = "aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9"}, - {file = "aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0"}, - {file = "aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0"}, - {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591"}, - {file = "aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf"}, - {file = "aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415"}, - {file = "aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1"}, - {file = "aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c"}, - {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43"}, - {file = "aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1"}, - {file = "aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592"}, - {file = "aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8"}, - {file = "aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df"}, - {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa"}, - {file = "aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767"}, - {file = "aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7"}, - {file = "aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a"}, - {file = "aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704"}, - {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f"}, - {file = "aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1"}, - {file = "aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538"}, - {file = "aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88"}, +python-versions = ">=3.10" +groups = ["clients", "dev"] +files = [ + {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a"}, + {file = "aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479"}, + {file = "aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f"}, + {file = "aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32"}, + {file = "aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e"}, + {file = "aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c"}, + {file = "aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a"}, + {file = "aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db"}, + {file = "aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910"}, + {file = "aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7"}, + {file = "aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa"}, + {file = "aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d"}, + {file = "aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228"}, + {file = "aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42"}, + {file = "aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d"}, + {file = "aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19"}, + {file = "aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559"}, + {file = "aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a"}, + {file = "aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c"}, + {file = "aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86"}, + {file = "aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c"}, + {file = "aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0"}, + {file = "aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5"}, + {file = "aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e"}, + {file = "aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71"}, + {file = "aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0"}, + {file = "aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883"}, + {file = "aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2"}, + {file = "aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062"}, + {file = "aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7"}, + {file = "aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646"}, + {file = "aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147"}, + {file = "aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41"}, + {file = "aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf"}, + {file = "aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da"}, + {file = "aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100"}, + {file = "aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0"}, + {file = "aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85"}, + {file = "aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9"}, + {file = "aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b"}, + {file = "aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7"}, + {file = "aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc"}, ] [package.dependencies] @@ -149,10 +148,11 @@ attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" propcache = ">=0.2.0" +typing_extensions = {version = ">=4.4", markers = "python_version < \"3.13\""} yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\" and sys_platform != \"android\" and sys_platform != \"ios\"", "aiodns (>=3.3.0) ; sys_platform != \"android\" and sys_platform != \"ios\"", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\" and sys_platform != \"android\" and sys_platform != \"ios\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -160,7 +160,7 @@ version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -176,19 +176,39 @@ version = "20250706" description = "Extensive database of location and timezone data for nearly every airport and landing strip in the world." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "airportsdata-20250706-py3-none-any.whl", hash = "sha256:750e882a75e391572ae859d4cb78cb801f5f2ca71b07849a381670cb01780677"}, {file = "airportsdata-20250706.tar.gz", hash = "sha256:66d7a03e825d592d85ed650f2c1d4b4302d1c04f8f37a15f1eda29a5e03d4af0"}, ] +[[package]] +name = "alembic" +version = "1.18.4" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.10" +groups = ["clients"] +files = [ + {file = "alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a"}, + {file = "alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.4.23" +typing-extensions = ">=4.12" + +[package.extras] +tz = ["tzdata"] + [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -200,7 +220,7 @@ version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, @@ -222,7 +242,7 @@ version = "3.9.1" description = "ASGI specs, helper code, and adapters" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "asgiref-3.9.1-py3-none-any.whl", hash = "sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c"}, {file = "asgiref-3.9.1.tar.gz", hash = "sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142"}, @@ -237,7 +257,7 @@ version = "0.8.1" description = "Read/rewrite/write Python ASTs" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, @@ -249,7 +269,7 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, @@ -269,7 +289,7 @@ version = "0.2.40" description = "Enabling Next-Gen LLM Applications via Multi-Agent Conversation Framework" optional = false python-versions = "<3.13,>=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "autogen-agentchat-0.2.40.tar.gz", hash = "sha256:bfdd25ab63fb75a701095315d0d7214f1616411b9edbcdf6183da35a956cc42e"}, {file = "autogen_agentchat-0.2.40-py3-none-any.whl", hash = "sha256:03f11ab89442a3b2408e7e46aa4a66d0be44e6f4447467efbb3ef4e35940176e"}, @@ -324,7 +344,7 @@ version = "1.28.1" description = "Microsoft Azure Machine Learning Client Library for Python" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_ai_ml-1.28.1-py3-none-any.whl", hash = "sha256:0f59557b98ed3c131e6f6c8bd02d2583e7e2a436db3145eb142f6520d22f5de8"}, {file = "azure_ai_ml-1.28.1.tar.gz", hash = "sha256:aa44638d033e64393c3952f59a73822f1d3f893057bd0f1957f811d7e6a58036"}, @@ -361,7 +381,7 @@ version = "1.1.28" description = "Microsoft Azure Client Library for Python (Common)" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"}, {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"}, @@ -373,7 +393,7 @@ version = "1.38.0" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335"}, {file = "azure_core-1.38.0.tar.gz", hash = "sha256:8194d2682245a3e4e3151a667c686464c3786fed7918b394d035bdcd61bb5993"}, @@ -393,7 +413,7 @@ version = "1.0.0b12" description = "Microsoft Azure Azure Core OpenTelemetry plugin Library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_core_tracing_opentelemetry-1.0.0b12-py3-none-any.whl", hash = "sha256:38fd42709f1cc4bbc4f2797008b1c30a6a01617e49910c05daa3a0d0c65053ac"}, {file = "azure_core_tracing_opentelemetry-1.0.0b12.tar.gz", hash = "sha256:bb454142440bae11fd9d68c7c1d67ae38a1756ce808c5e4d736730a7b4b04144"}, @@ -409,7 +429,7 @@ version = "1.23.1" description = "Microsoft Azure Identity Library for Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "azure_identity-1.23.1-py3-none-any.whl", hash = "sha256:7eed28baa0097a47e3fb53bd35a63b769e6b085bb3cb616dfce2b67f28a004a1"}, {file = "azure_identity-1.23.1.tar.gz", hash = "sha256:226c1ef982a9f8d5dcf6e0f9ed35eaef2a4d971e7dd86317e9b9d52e70a035e4"}, @@ -428,7 +448,7 @@ version = "1.6.0" description = "Microsoft Azure Management Core Library for Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_mgmt_core-1.6.0-py3-none-any.whl", hash = "sha256:0460d11e85c408b71c727ee1981f74432bc641bb25dfcf1bb4e90a49e776dbc4"}, {file = "azure_mgmt_core-1.6.0.tar.gz", hash = "sha256:b26232af857b021e61d813d9f4ae530465255cb10b3dde945ad3743f7a58e79c"}, @@ -443,7 +463,7 @@ version = "1.6.13" description = "Microsoft Azure Monitor Opentelemetry Distro Client Library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_monitor_opentelemetry-1.6.13-py3-none-any.whl", hash = "sha256:c6a5b0b73b5639054675aec2a8f215cdb6b7d1e5cb21d93188bbdd55673eca30"}, {file = "azure_monitor_opentelemetry-1.6.13.tar.gz", hash = "sha256:59b1b01e64318d78f0d2909c8fa9b3008dbf88e7ea6e491d2042afa5bbe94971"}, @@ -469,7 +489,7 @@ version = "1.0.0b41" description = "Microsoft Azure Monitor Opentelemetry Exporter Client Library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_monitor_opentelemetry_exporter-1.0.0b41-py2.py3-none-any.whl", hash = "sha256:cbba629cca53e0e33416c61e08ebaabe833e740cfbfd7f2e9151821f92c66a51"}, {file = "azure_monitor_opentelemetry_exporter-1.0.0b41.tar.gz", hash = "sha256:b363e6f89c0dee16d02782a310a60d626e4c081ef49d533ff5225a40cbab12cc"}, @@ -490,7 +510,7 @@ version = "12.26.0" description = "Microsoft Azure Blob Storage Client Library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe"}, {file = "azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f"}, @@ -511,7 +531,7 @@ version = "12.21.0" description = "Microsoft Azure File DataLake Storage Client Library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_storage_file_datalake-12.21.0-py3-none-any.whl", hash = "sha256:e26ef15adbf8f8b2b84823ae01dfc2e5368a2008878288c1b40f1988176af564"}, {file = "azure_storage_file_datalake-12.21.0.tar.gz", hash = "sha256:b49cd2156ea325f6f44a8f6674d73c5949e9ac48d6480faf901b2939855fcdd3"}, @@ -532,7 +552,7 @@ version = "12.22.0" description = "Microsoft Azure Azure File Share Storage Client Library for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "azure_storage_file_share-12.22.0-py3-none-any.whl", hash = "sha256:a42482a7d1d940780f4bf7eda5cb031076c8571b17dda4414f0b91d5111d0eb2"}, {file = "azure_storage_file_share-12.22.0.tar.gz", hash = "sha256:e583c8e086ee7d6ec50cc23daf42839c18dce11b36b3d1364c13714c08b53452"}, @@ -663,7 +683,7 @@ version = "1.0.5" description = "Python bindings for the Rust blake3 crate" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "blake3-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1ba833ff7dee08bbf56b1e9d0479fda74f867b90fbe12c85078f8fbf2b505d6f"}, {file = "blake3-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:606676dbb974b66afea2240741dfd4afafd8ed6697454eff0e1e0c4dc130e5b0"}, @@ -770,7 +790,7 @@ version = "2025.7.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2"}, {file = "certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995"}, @@ -782,8 +802,7 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\"" +groups = ["main", "clients"] files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -870,6 +889,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +markers = {main = "platform_python_implementation != \"PyPy\"", clients = "platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\""} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -880,7 +900,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -982,7 +1002,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -997,7 +1017,7 @@ version = "3.1.1" description = "Pickler class to extend the standard pickle.Pickler functionality" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e"}, {file = "cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64"}, @@ -1009,11 +1029,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] +groups = ["main", "clients", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {dev = "sys_platform == \"win32\""} [[package]] name = "compressed-tensors" @@ -1021,7 +1042,7 @@ version = "0.9.1" description = "Library for utilization of compressed safetensors of neural network models" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "compressed-tensors-0.9.1.tar.gz", hash = "sha256:3cf5cd637f0186c184dd5bbbbf941356b1225199b49c6a45bf0909d65907f686"}, {file = "compressed_tensors-0.9.1-py3-none-any.whl", hash = "sha256:77385f879c5c092db777a7880851cd9f801bf2f9bb46bb4402f052d9e002975c"}, @@ -1130,62 +1151,74 @@ test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist" [[package]] name = "cryptography" -version = "44.0.3" +version = "46.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = "!=3.9.0,!=3.9.1,>=3.7" -groups = ["main"] -files = [ - {file = "cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01"}, - {file = "cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d"}, - {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904"}, - {file = "cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44"}, - {file = "cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d"}, - {file = "cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d"}, - {file = "cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c"}, - {file = "cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f"}, - {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5"}, - {file = "cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b"}, - {file = "cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028"}, - {file = "cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06"}, - {file = "cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5"}, - {file = "cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c"}, - {file = "cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main", "clients"] +files = [ + {file = "cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b"}, + {file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85"}, + {file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e"}, + {file = "cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457"}, + {file = "cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b"}, + {file = "cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2"}, + {file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e"}, + {file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee"}, + {file = "cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298"}, + {file = "cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb"}, + {file = "cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0"}, + {file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85"}, + {file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e"}, + {file = "cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246"}, + {file = "cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4"}, + {file = "cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5"}, +] + +[package.dependencies] +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==44.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.7)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1194,7 +1227,7 @@ version = "13.5.1" description = "CuPy: NumPy & SciPy for GPU" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] markers = "sys_platform != \"darwin\"" files = [ {file = "cupy_cuda12x-13.5.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a4a5e1a232edeed19efef1d1def3ab94bd31a2b849699ba764ea0d1ad07d63c4"}, @@ -1267,7 +1300,7 @@ version = "0.18.0" description = "Decompile python functions, from bytecode to source code!" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "depyf-0.18.0-py3-none-any.whl", hash = "sha256:007294d5bac19a38a0767d747be0f49b9ffdcea0394a822644142df22b33a3e1"}, {file = "depyf-0.18.0.tar.gz", hash = "sha256:b99f0c383be949ae45d5d606fe444c71f375b55a57b8d6b20e7856670d52130d"}, @@ -1286,7 +1319,7 @@ version = "0.4.0" description = "serialize all of Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, @@ -1302,7 +1335,7 @@ version = "5.6.3" description = "Disk Cache -- Disk and file backed persistent cache." optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] files = [ {file = "diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19"}, {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, @@ -1314,7 +1347,7 @@ version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -1326,7 +1359,7 @@ version = "2.7.0" description = "DNS toolkit" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, @@ -1347,7 +1380,7 @@ version = "7.1.0" description = "A Python library for the Docker Engine API." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, @@ -1385,7 +1418,7 @@ version = "0.8.1" description = "A new flavour of deep learning operations" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737"}, {file = "einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84"}, @@ -1442,7 +1475,7 @@ version = "2.2.0" description = "A robust email address syntax and deliverability validation library." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, @@ -1458,7 +1491,7 @@ version = "0.115.14" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"}, {file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"}, @@ -1485,7 +1518,7 @@ version = "0.0.8" description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "fastapi_cli-0.0.8-py3-none-any.whl", hash = "sha256:0ea95d882c85b9219a75a65ab27e8da17dac02873e456850fa0a726e96e985eb"}, {file = "fastapi_cli-0.0.8.tar.gz", hash = "sha256:2360f2989b1ab4a3d7fc8b3a0b20e8288680d8af2e31de7c38309934d7f8a0ee"}, @@ -1507,7 +1540,7 @@ version = "0.1.5" description = "Deploy and manage FastAPI Cloud apps from the command line 🚀" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "fastapi_cloud_cli-0.1.5-py3-none-any.whl", hash = "sha256:d80525fb9c0e8af122370891f9fa83cf5d496e4ad47a8dd26c0496a6c85a012a"}, {file = "fastapi_cloud_cli-0.1.5.tar.gz", hash = "sha256:341ee585eb731a6d3c3656cb91ad38e5f39809bf1a16d41de1333e38635a7937"}, @@ -1531,7 +1564,7 @@ version = "0.8.3" description = "Fast, re-entrant optimistic lock implemented in Cython" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] markers = "sys_platform != \"darwin\"" files = [ {file = "fastrlock-0.8.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bbbe31cb60ec32672969651bf68333680dacaebe1a1ec7952b8f5e6e23a70aa5"}, @@ -1611,7 +1644,7 @@ version = "3.20.3" description = "A platform independent file lock." optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["clients"] files = [ {file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, {file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, @@ -1623,7 +1656,7 @@ version = "0.1.6" description = "simple fixed-width integers" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "fixedint-0.1.6-py2-none-any.whl", hash = "sha256:41953193f08cbe984f584d8513c38fe5eea5fbd392257433b2210391c8a21ead"}, {file = "fixedint-0.1.6-py3-none-any.whl", hash = "sha256:b8cf9f913735d2904deadda7a6daa9f57100599da1de57a7448ea1be75ae8c9c"}, @@ -1636,7 +1669,7 @@ version = "2.3.5" description = "A fast library for automated machine learning and tuning" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "FLAML-2.3.5-py3-none-any.whl", hash = "sha256:e738c1b6c50feedfcc8709fb42c7a23b96fb9bfe83d138a6b29bcfb48b4c15c9"}, {file = "FLAML-2.3.5.tar.gz", hash = "sha256:78dc9de830411f1635d7f97b460dacf31cc2d7000b1a8a613d8e722a897d5669"}, @@ -1670,19 +1703,20 @@ vw = ["scikit-learn", "vowpalwabbit (>=8.10.0,<9.0.0)"] [[package]] name = "flwr" -version = "1.24.0" +version = "1.27.0" description = "Flower: A Friendly Federated AI Framework" optional = false python-versions = "<4.0,>=3.10" -groups = ["main"] +groups = ["clients"] files = [ - {file = "flwr-1.24.0-py3-none-any.whl", hash = "sha256:6331df132d5d9b488dd5276cfff92f61e7217eea566815b3f0b16bf132dc0909"}, - {file = "flwr-1.24.0.tar.gz", hash = "sha256:fe5eba73deb0421b9c1719574f84b90c3d1492aa448225db0cb32137416da269"}, + {file = "flwr-1.27.0-py3-none-any.whl", hash = "sha256:833c7981af3444f54a7f12cf47572e87939dd0babc8f5267eeac0a16c8387fa8"}, + {file = "flwr-1.27.0.tar.gz", hash = "sha256:a715d5121f60701cc8faca2c379a2f9fa38f72467e6a84e7630b5fcf104c0f62"}, ] [package.dependencies] -click = "<8.2.0" -cryptography = ">=44.0.1,<45.0.0" +alembic = ">=1.18.1,<2.0.0" +click = ">=8.0.0,<9.0.0" +cryptography = ">=46.0.5,<47.0.0" grpcio = ">=1.70.0,<2.0.0" grpcio-health-checking = ">=1.70.0,<2.0.0" iterators = ">=0.0.2,<0.0.3" @@ -1693,13 +1727,14 @@ pycryptodome = ">=3.18.0,<4.0.0" pyyaml = ">=6.0.2,<7.0.0" requests = ">=2.31.0,<3.0.0" rich = ">=13.5.0,<14.0.0" +SQLAlchemy = ">=2.0.45,<3.0.0" tomli = ">=2.0.1,<3.0.0" tomli-w = ">=1.0.0,<2.0.0" typer = ">=0.12.5,<0.21.0" [package.extras] -rest = ["starlette (>=0.45.2,<0.46.0)", "uvicorn[standard] (>=0.34.0,<0.35.0)"] -simulation = ["ray (==2.51.1) ; python_version >= \"3.10\" and python_version < \"3.13\"", "ray (==2.51.1) ; sys_platform != \"win32\" and python_version == \"3.13\""] +rest = ["starlette (>=0.50.0,<0.51.0)", "uvicorn[standard] (>=0.40.0,<0.41.0)"] +simulation = ["ray (==2.51.1) ; python_version == \"3.13\" and sys_platform != \"win32\"", "ray (==2.51.1) ; python_version >= \"3.10\" and python_version < \"3.13\""] [[package]] name = "fonttools" @@ -1780,7 +1815,7 @@ version = "1.7.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, @@ -1894,7 +1929,7 @@ version = "2025.7.0" description = "File-system specification" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21"}, {file = "fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58"}, @@ -1934,7 +1969,7 @@ version = "0.10.0" description = "Read and write ML models in GGUF for GGML" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "gguf-0.10.0-py3-none-any.whl", hash = "sha256:706089fba756a06913227841b4a6c8398360fa991569fd974e663a92b224e33f"}, {file = "gguf-0.10.0.tar.gz", hash = "sha256:52a30ef26328b419ffc47d9269fc580c238edf1c8a19b5ea143c323e04a038c1"}, @@ -1962,22 +1997,22 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.45" +version = "3.1.58" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77"}, - {file = "gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c"}, + {file = "gitpython-3.1.58-py3-none-any.whl", hash = "sha256:d331e722577f0fd7fc1f857419b3ecc07af66282b933d2a4d95f84a042fdd50f"}, + {file = "gitpython-3.1.58.tar.gz", hash = "sha256:621416df10ef3fd0e19fabf9172ddeed0fa704d353d04f194eec56a625a95b22"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] +doc = ["sphinx (>=7.4.7,<8)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +test = ["basedpyright (==1.39.9) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy (==1.18.2) ; python_version >= \"3.9\"", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] [[package]] name = "google-auth" @@ -2006,13 +2041,81 @@ requests = ["requests (>=2.20.0,<3.0.0)"] testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] urllib3 = ["packaging", "urllib3"] +[[package]] +name = "greenlet" +version = "3.3.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.10" +groups = ["clients"] +markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\"" +files = [ + {file = "greenlet-3.3.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9bc885b89709d901859cf95179ec9f6bb67a3d2bb1f0e88456461bd4b7f8fd0d"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b568183cf65b94919be4438dc28416b234b678c608cafac8874dfeeb2a9bbe13"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:527fec58dc9f90efd594b9b700662ed3fb2493c2122067ac9c740d98080a620e"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508c7f01f1791fbc8e011bd508f6794cb95397fdb198a46cb6635eb5b78d85a7"}, + {file = "greenlet-3.3.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad0c8917dd42a819fe77e6bdfcb84e3379c0de956469301d9fd36427a1ca501f"}, + {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:97245cc10e5515dbc8c3104b2928f7f02b6813002770cfaffaf9a6e0fc2b94ef"}, + {file = "greenlet-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8c1fdd7d1b309ff0da81d60a9688a8bd044ac4e18b250320a96fc68d31c209ca"}, + {file = "greenlet-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:5d0e35379f93a6d0222de929a25ab47b5eb35b5ef4721c2b9cbcc4036129ff1f"}, + {file = "greenlet-3.3.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c56692189a7d1c7606cb794be0a8381470d95c57ce5be03fb3d0ef57c7853b86"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ebd458fa8285960f382841da585e02201b53a5ec2bac6b156fc623b5ce4499f"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a443358b33c4ec7b05b79a7c8b466f5d275025e750298be7340f8fc63dff2a55"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4375a58e49522698d3e70cc0b801c19433021b5c37686f7ce9c65b0d5c8677d2"}, + {file = "greenlet-3.3.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e2cd90d413acbf5e77ae41e5d3c9b3ac1d011a756d7284d7f3f2b806bbd6358"}, + {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:442b6057453c8cb29b4fb36a2ac689382fc71112273726e2423f7f17dc73bf99"}, + {file = "greenlet-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45abe8eb6339518180d5a7fa47fa01945414d7cca5ecb745346fc6a87d2750be"}, + {file = "greenlet-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e692b2dae4cc7077cbb11b47d258533b48c8fde69a33d0d8a82e2fe8d8531d5"}, + {file = "greenlet-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:02b0a8682aecd4d3c6c18edf52bc8e51eacdd75c8eac52a790a210b06aa295fd"}, + {file = "greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb"}, + {file = "greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070"}, + {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79"}, + {file = "greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395"}, + {file = "greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f"}, + {file = "greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643"}, + {file = "greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd"}, + {file = "greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab"}, + {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a"}, + {file = "greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b"}, + {file = "greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124"}, + {file = "greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327"}, + {file = "greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9"}, + {file = "greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506"}, + {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce"}, + {file = "greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5"}, + {file = "greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492"}, + {file = "greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71"}, + {file = "greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf"}, + {file = "greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4"}, + {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727"}, + {file = "greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e"}, + {file = "greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a"}, + {file = "greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil", "setuptools"] + [[package]] name = "groq" version = "0.28.0" description = "The official Python library for the groq API" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "groq-0.28.0-py3-none-any.whl", hash = "sha256:c6f86638371c2cba2ca337232e76c8d412e75965ed7e3058d30c9aa5dfe84303"}, {file = "groq-0.28.0.tar.gz", hash = "sha256:65e1cab9184cbb32380d62eca50d6162269c7ec0c77e4cc868069cfe93450f9f"}, @@ -2032,7 +2135,7 @@ version = "1.76.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc"}, {file = "grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde"}, @@ -2109,7 +2212,7 @@ version = "1.76.0" description = "Standard Health Checking Service for gRPC" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "grpcio_health_checking-1.76.0-py3-none-any.whl", hash = "sha256:9743f345a855ba030cc7c381361606870b79d33bb71d7756efa47b6faa970f81"}, {file = "grpcio_health_checking-1.76.0.tar.gz", hash = "sha256:b7a99d74096b3ab3a59987fc02374068e1c180a352e8d1f79f10e5a23727098d"}, @@ -2125,7 +2228,7 @@ version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, @@ -2137,7 +2240,7 @@ version = "1.1.5" description = "Fast transfer of large files with the Hugging Face Hub." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" files = [ {file = "hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23"}, @@ -2174,7 +2277,7 @@ version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, @@ -2196,7 +2299,7 @@ version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" -groups = ["main"] +groups = ["clients"] files = [ {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, @@ -2252,7 +2355,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -2277,7 +2380,7 @@ version = "0.34.3" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" -groups = ["main"] +groups = ["clients"] files = [ {file = "huggingface_hub-0.34.3-py3-none-any.whl", hash = "sha256:5444550099e2d86e68b2898b09e85878fbd788fc2957b506c6a79ce060e39492"}, {file = "huggingface_hub-0.34.3.tar.gz", hash = "sha256:d58130fd5aa7408480681475491c0abd7e835442082fbc3ef4d45b6c39f83853"}, @@ -2312,18 +2415,18 @@ typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "t [[package]] name = "idna" -version = "3.10" +version = "3.15" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" -groups = ["main"] +python-versions = ">=3.8" +groups = ["main", "clients", "dev"] files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, + {file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"}, + {file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"}, ] [package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "importlib" @@ -2342,7 +2445,7 @@ version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, @@ -2366,12 +2469,12 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "platform_machine == \"x86_64\"" +groups = ["clients", "dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] +markers = {clients = "platform_machine == \"x86_64\""} [[package]] name = "interegular" @@ -2379,7 +2482,7 @@ version = "0.3.3" description = "a regex intersection checker" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "interegular-0.3.3-py37-none-any.whl", hash = "sha256:b0c07007d48c89d6d19f7204972d369b2a77222722e126b6aa63aa721dc3b19c"}, {file = "interegular-0.3.3.tar.gz", hash = "sha256:d9b697b21b34884711399ba0f0376914b81899ce670032486d0d048344a76600"}, @@ -2391,7 +2494,7 @@ version = "0.7.2" description = "An ISO 8601 date/time/duration parser and formatter" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15"}, {file = "isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6"}, @@ -2403,7 +2506,7 @@ version = "0.0.2" description = "Iterator utility classes and functions" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["clients"] files = [ {file = "iterators-0.0.2-py3-none-any.whl", hash = "sha256:ac2a9d8af1dd9eed051ccab4a1905a1343d66bbc9f451567d94f6e2744f30fce"}, {file = "iterators-0.0.2.tar.gz", hash = "sha256:4f6a5b39c3c724edd5c7231a589d463ad50357cdc35494a3c71730795b78eb50"}, @@ -2415,7 +2518,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -2433,7 +2536,7 @@ version = "0.10.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, @@ -2520,7 +2623,7 @@ version = "4.25.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716"}, {file = "jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f"}, @@ -2528,7 +2631,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -2542,7 +2645,7 @@ version = "2025.4.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, @@ -2654,7 +2757,7 @@ files = [ ] [package.dependencies] -certifi = ">=14.05.14" +certifi = ">=14.5.14" google-auth = ">=1.0.1" oauthlib = ">=3.2.2" python-dateutil = ">=2.5.3" @@ -2674,7 +2777,7 @@ version = "1.2.2" description = "a modern parsing library" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c"}, {file = "lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80"}, @@ -2692,7 +2795,7 @@ version = "0.43.0" description = "lightweight wrapper around basic LLVM functionality" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761"}, {file = "llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc"}, @@ -2723,7 +2826,7 @@ version = "0.10.11" description = "Enforce the output format (JSON Schema, Regex etc) of a language model" optional = false python-versions = "<4.0,>=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "lm_format_enforcer-0.10.11-py3-none-any.whl", hash = "sha256:563e0dbc930a6d50fb687951506c5de098c6e962601be0ce723f3b7d0b916a1b"}, {file = "lm_format_enforcer-0.10.11.tar.gz", hash = "sha256:8ab371924e166a1df68f243aca73a8a647bea5909f37edd6a53a694e7e7c3274"}, @@ -2735,13 +2838,33 @@ packaging = "*" pydantic = ">=1.10.8" pyyaml = "*" +[[package]] +name = "mako" +version = "1.3.12" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +groups = ["clients"] +files = [ + {file = "mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9"}, + {file = "mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -2766,7 +2889,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -2837,7 +2960,7 @@ version = "3.26.2" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73"}, {file = "marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57"}, @@ -2936,7 +3059,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -2948,7 +3071,7 @@ version = "1.8.3" description = "Mistral-common is a library of common utilities for Mistral AI." optional = false python-versions = "<3.14,>=3.9.0" -groups = ["main"] +groups = ["clients"] files = [ {file = "mistral_common-1.8.3-py3-none-any.whl", hash = "sha256:846b6e4bbe016dc2e64fd3169fa704a548f6c74467e0cb18dc165b7a7669abd6"}, {file = "mistral_common-1.8.3.tar.gz", hash = "sha256:0d1979d82227b625f6d71b3c828176f059da8d0f5a3307cdf53b48409a3970a4"}, @@ -2981,7 +3104,7 @@ version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -2999,7 +3122,7 @@ version = "1.33.0" description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "msal-1.33.0-py3-none-any.whl", hash = "sha256:c0cd41cecf8eaed733ee7e3be9e040291eba53b0f262d3ae9c58f38b04244273"}, {file = "msal-1.33.0.tar.gz", hash = "sha256:836ad80faa3e25a7d71015c990ce61f704a87328b1e73bcbb0623a18cbf17510"}, @@ -3019,7 +3142,7 @@ version = "1.3.1" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca"}, {file = "msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4"}, @@ -3033,71 +3156,78 @@ portalocker = ["portalocker (>=1.4,<4)"] [[package]] name = "msgpack" -version = "1.1.1" +version = "1.2.1" description = "MessagePack serializer" optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, - {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, - {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2"}, - {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4"}, - {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0"}, - {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26"}, - {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75"}, - {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338"}, - {file = "msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd"}, - {file = "msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8"}, - {file = "msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558"}, - {file = "msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d"}, - {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0"}, - {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f"}, - {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704"}, - {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2"}, - {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2"}, - {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752"}, - {file = "msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295"}, - {file = "msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458"}, - {file = "msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238"}, - {file = "msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157"}, - {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce"}, - {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a"}, - {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c"}, - {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b"}, - {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef"}, - {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a"}, - {file = "msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c"}, - {file = "msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4"}, - {file = "msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0"}, - {file = "msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9"}, - {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8"}, - {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a"}, - {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac"}, - {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b"}, - {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7"}, - {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5"}, - {file = "msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323"}, - {file = "msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69"}, - {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285"}, - {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600"}, - {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9"}, - {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78"}, - {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a"}, - {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6"}, - {file = "msgpack-1.1.1-cp38-cp38-win32.whl", hash = "sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142"}, - {file = "msgpack-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad"}, - {file = "msgpack-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b"}, - {file = "msgpack-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232"}, - {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf"}, - {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf"}, - {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90"}, - {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1"}, - {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88"}, - {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478"}, - {file = "msgpack-1.1.1-cp39-cp39-win32.whl", hash = "sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57"}, - {file = "msgpack-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084"}, - {file = "msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd"}, +python-versions = ">=3.10" +groups = ["clients", "dev"] +files = [ + {file = "msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c"}, + {file = "msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895"}, + {file = "msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203"}, + {file = "msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73"}, + {file = "msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833"}, + {file = "msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8"}, + {file = "msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7"}, + {file = "msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce"}, + {file = "msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74"}, + {file = "msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb"}, + {file = "msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22"}, + {file = "msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5"}, + {file = "msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06"}, + {file = "msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4"}, + {file = "msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8"}, + {file = "msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b"}, + {file = "msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e"}, + {file = "msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f"}, + {file = "msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d"}, + {file = "msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8"}, + {file = "msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66"}, + {file = "msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35"}, + {file = "msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c"}, + {file = "msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0"}, + {file = "msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a"}, + {file = "msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6"}, + {file = "msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a"}, + {file = "msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1"}, + {file = "msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64"}, + {file = "msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac"}, + {file = "msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24"}, + {file = "msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07"}, + {file = "msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064"}, + {file = "msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056"}, + {file = "msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc"}, + {file = "msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d"}, + {file = "msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155"}, + {file = "msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402"}, + {file = "msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c"}, + {file = "msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6"}, + {file = "msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707"}, + {file = "msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9"}, + {file = "msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a"}, + {file = "msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d"}, + {file = "msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7"}, + {file = "msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889"}, + {file = "msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720"}, + {file = "msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190"}, + {file = "msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d"}, + {file = "msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24"}, + {file = "msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7"}, + {file = "msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb"}, + {file = "msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b"}, + {file = "msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7"}, + {file = "msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273"}, + {file = "msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1"}, + {file = "msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc"}, + {file = "msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde"}, + {file = "msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4"}, + {file = "msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d"}, + {file = "msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355"}, + {file = "msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c"}, + {file = "msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1"}, + {file = "msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2"}, + {file = "msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107"}, + {file = "msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647"}, ] [[package]] @@ -3106,7 +3236,7 @@ version = "0.19.0" description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259"}, {file = "msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36"}, @@ -3159,7 +3289,7 @@ version = "0.7.1" description = "AutoRest swagger generator Python client runtime." optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["clients"] files = [ {file = "msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32"}, {file = "msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9"}, @@ -3181,7 +3311,7 @@ version = "6.6.3" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817"}, {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140"}, @@ -3313,7 +3443,7 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" -groups = ["main"] +groups = ["clients"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -3325,7 +3455,7 @@ version = "3.5" description = "Python package for creating and manipulating graphs and networks" optional = false python-versions = ">=3.11" -groups = ["main"] +groups = ["clients"] files = [ {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}, {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}, @@ -3358,7 +3488,7 @@ version = "0.60.0" description = "compiling Python code using LLVM" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651"}, {file = "numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b"}, @@ -3393,7 +3523,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -3439,7 +3569,7 @@ version = "12.4.5.8" description = "CUBLAS native runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3"}, @@ -3453,7 +3583,7 @@ version = "12.4.127" description = "CUDA profiling tools runtime libs." optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a"}, @@ -3467,7 +3597,7 @@ version = "12.4.127" description = "NVRTC native runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198"}, @@ -3481,7 +3611,7 @@ version = "12.4.127" description = "CUDA Runtime native Libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3"}, @@ -3495,7 +3625,7 @@ version = "9.1.0.70" description = "cuDNN runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f"}, @@ -3511,7 +3641,7 @@ version = "11.2.1.3" description = "CUFFT native runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399"}, @@ -3528,7 +3658,7 @@ version = "10.3.5.147" description = "CURAND native runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9"}, @@ -3542,7 +3672,7 @@ version = "11.6.1.9" description = "CUDA solver native runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e"}, @@ -3561,7 +3691,7 @@ version = "12.3.1.170" description = "CUSPARSE native runtime libraries" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3"}, @@ -3578,7 +3708,7 @@ version = "2.21.5" description = "NVIDIA Collective Communication Library (NCCL) Runtime" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0"}, @@ -3590,7 +3720,7 @@ version = "12.4.127" description = "Nvidia JIT LTO Library" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83"}, @@ -3604,7 +3734,7 @@ version = "12.4.127" description = "NVIDIA Tools Extension" optional = false python-versions = ">=3" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3"}, @@ -3618,7 +3748,7 @@ version = "3.3.1" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, @@ -3635,7 +3765,7 @@ version = "1.98.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "openai-1.98.0-py3-none-any.whl", hash = "sha256:b99b794ef92196829120e2df37647722104772d2a74d08305df9ced5f26eae34"}, {file = "openai-1.98.0.tar.gz", hash = "sha256:3ee0fcc50ae95267fd22bd1ad095ba5402098f3df2162592e68109999f685427"}, @@ -3663,7 +3793,7 @@ version = "4.11.0.86" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["clients"] files = [ {file = "opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798"}, {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca"}, @@ -3676,8 +3806,8 @@ files = [ [package.dependencies] numpy = [ + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.5", markers = "python_version == \"3.11\""}, ] [[package]] @@ -3686,7 +3816,7 @@ version = "1.36.0" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, @@ -3702,7 +3832,7 @@ version = "0.57b0" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation-0.57b0-py3-none-any.whl", hash = "sha256:9109280f44882e07cec2850db28210b90600ae9110b42824d196de357cbddf7e"}, {file = "opentelemetry_instrumentation-0.57b0.tar.gz", hash = "sha256:f2a30135ba77cdea2b0e1df272f4163c154e978f57214795d72f40befd4fcf05"}, @@ -3720,7 +3850,7 @@ version = "0.57b0" description = "ASGI instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_asgi-0.57b0-py3-none-any.whl", hash = "sha256:47debbde6af066a7e8e911f7193730d5e40d62effc1ac2e1119908347790a3ea"}, {file = "opentelemetry_instrumentation_asgi-0.57b0.tar.gz", hash = "sha256:a6f880b5d1838f65688fc992c65fbb1d3571f319d370990c32e759d3160e510b"}, @@ -3742,7 +3872,7 @@ version = "0.57b0" description = "OpenTelemetry Database API instrumentation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_dbapi-0.57b0-py3-none-any.whl", hash = "sha256:c1b110a5e86ec9b52b970460917523f47afa0c73f131e7f03c6a7c1921822dc4"}, {file = "opentelemetry_instrumentation_dbapi-0.57b0.tar.gz", hash = "sha256:7ad9e39c91f6212f118435fd6fab842a1f78b2cbad1167f228c025bba2a8fc2d"}, @@ -3760,7 +3890,7 @@ version = "0.57b0" description = "OpenTelemetry Instrumentation for Django" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_django-0.57b0-py3-none-any.whl", hash = "sha256:3d702d79a9ec0c836ccf733becf34630c6afb3c86c25c330c5b7601debe1e7c5"}, {file = "opentelemetry_instrumentation_django-0.57b0.tar.gz", hash = "sha256:df4116d2ea2c6bbbbf8853b843deb74d66bd0d573ddd372ec84fd60adaf977c6"}, @@ -3783,7 +3913,7 @@ version = "0.57b0" description = "OpenTelemetry FastAPI Instrumentation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_fastapi-0.57b0-py3-none-any.whl", hash = "sha256:61e6402749ffe0bfec582e58155e0d81dd38723cd9bc4562bca1acca80334006"}, {file = "opentelemetry_instrumentation_fastapi-0.57b0.tar.gz", hash = "sha256:73ac22f3c472a8f9cb21d1fbe5a4bf2797690c295fff4a1c040e9b1b1688a105"}, @@ -3805,7 +3935,7 @@ version = "0.57b0" description = "Flask instrumentation for OpenTelemetry" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_flask-0.57b0-py3-none-any.whl", hash = "sha256:5ecd614f194825725b61ee9ba8e37dcd4d3f9b5d40fef759df8650d6a91b1cb9"}, {file = "opentelemetry_instrumentation_flask-0.57b0.tar.gz", hash = "sha256:c5244a40b03664db966d844a32f43c900181431b77929be62a68d4907e86ed25"}, @@ -3828,7 +3958,7 @@ version = "0.57b0" description = "OpenTelemetry psycopg2 instrumentation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_psycopg2-0.57b0-py3-none-any.whl", hash = "sha256:94fdde02b7451c8e85d43b4b9dd13a34fee96ffd43324d1b3567f47d2903b99f"}, {file = "opentelemetry_instrumentation_psycopg2-0.57b0.tar.gz", hash = "sha256:4e9d05d661c50985f0a5d7f090a7f399d453b467c9912c7611fcef693d15b038"}, @@ -3848,7 +3978,7 @@ version = "0.57b0" description = "OpenTelemetry requests instrumentation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_requests-0.57b0-py3-none-any.whl", hash = "sha256:66a576ac8080724ddc8a14c39d16bb5f430991bd504fdbea844c7a063f555971"}, {file = "opentelemetry_instrumentation_requests-0.57b0.tar.gz", hash = "sha256:193bd3fd1f14737721876fb1952dffc7d43795586118df633a91ecd9057446ff"}, @@ -3869,7 +3999,7 @@ version = "0.57b0" description = "OpenTelemetry urllib instrumentation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_urllib-0.57b0-py3-none-any.whl", hash = "sha256:bb3a01172109a6f56bfcc38ea83b9d4a61c4c2cac6b9a190e757063daadf545c"}, {file = "opentelemetry_instrumentation_urllib-0.57b0.tar.gz", hash = "sha256:657225ceae8bb52b67bd5c26dcb8a33f0efb041f1baea4c59dbd1adbc63a4162"}, @@ -3887,7 +4017,7 @@ version = "0.57b0" description = "OpenTelemetry urllib3 instrumentation" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_urllib3-0.57b0-py3-none-any.whl", hash = "sha256:337ecac6df3ff92026b51c64df7dd4a3fff52f2dc96036ea9371670243bf83c6"}, {file = "opentelemetry_instrumentation_urllib3-0.57b0.tar.gz", hash = "sha256:f49d8c3d1d81ae56304a08b14a7f564d250733ed75cd2210ccef815b5af2eea1"}, @@ -3909,7 +4039,7 @@ version = "0.57b0" description = "WSGI Middleware for OpenTelemetry" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_instrumentation_wsgi-0.57b0-py3-none-any.whl", hash = "sha256:b9cf0c6e61489f7503fc17ef04d169bd214e7a825650ee492f5d2b4d73b17b54"}, {file = "opentelemetry_instrumentation_wsgi-0.57b0.tar.gz", hash = "sha256:d7e16b3b87930c30fc4c1bbc8b58c5dd6eefade493a3a5e7343bc24d572bc5b7"}, @@ -3927,7 +4057,7 @@ version = "0.1.5" description = "Azure Resource Detector for OpenTelemetry" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_resource_detector_azure-0.1.5-py3-none-any.whl", hash = "sha256:4dcc5d54ab5c3b11226af39509bc98979a8b9e0f8a24c1b888783755d3bf00eb"}, {file = "opentelemetry_resource_detector_azure-0.1.5.tar.gz", hash = "sha256:e0ba658a87c69eebc806e75398cd0e9f68a8898ea62de99bc1b7083136403710"}, @@ -3942,7 +4072,7 @@ version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, @@ -3959,7 +4089,7 @@ version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, @@ -3975,7 +4105,7 @@ version = "0.57b0" description = "Web util for OpenTelemetry" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "opentelemetry_util_http-0.57b0-py3-none-any.whl", hash = "sha256:e54c0df5543951e471c3d694f85474977cd5765a3b7654398c83bab3d2ffb8e9"}, {file = "opentelemetry_util_http-0.57b0.tar.gz", hash = "sha256:f7417595ead0eb42ed1863ec9b2f839fc740368cd7bbbfc1d0a47bc1ab0aba11"}, @@ -3987,7 +4117,7 @@ version = "0.1.11" description = "Probabilistic Generative Model Programming" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "outlines-0.1.11-py3-none-any.whl", hash = "sha256:f5a5f2242ed9802d3aab7a92789bf4008d734c576be9258cc0a297f690124727"}, {file = "outlines-0.1.11.tar.gz", hash = "sha256:0997bd9da1cc050e430bd08995dc7d4bd855918bafa4531e49d3f37110a23aba"}, @@ -4028,7 +4158,7 @@ version = "0.1.26" description = "Structured Text Generation in Rust" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "outlines_core-0.1.26-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6a962a7452e7ac170fa04d405342cadae2d28fafa5b1830cef7aa610257ed32f"}, {file = "outlines_core-0.1.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15a3684fa29564da2db03934cf0097bef3e871f70d3af0ef2b52fdb886da2e09"}, @@ -4070,7 +4200,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients", "dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -4130,8 +4260,8 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -4190,7 +4320,7 @@ version = "0.2.1.1.post6" description = "Parse partial JSON generated by LLM" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["clients"] files = [ {file = "partial_json_parser-0.2.1.1.post6-py3-none-any.whl", hash = "sha256:abc332f09b13ef5233384dbfe7128a0e9ea3fa4b8f8be9b37ac1b433c810e99e"}, {file = "partial_json_parser-0.2.1.1.post6.tar.gz", hash = "sha256:43896b68929678224cbbe4884a6a5fe9251ded4b30b8b7d7eb569e5feea93afc"}, @@ -4205,7 +4335,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -4213,127 +4343,107 @@ files = [ [[package]] name = "pillow" -version = "11.3.0" -description = "Python Imaging Library (Fork)" +version = "12.3.0" +description = "Python Imaging Library (fork)" optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, - {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, - {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, - {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, - {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, - {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, - {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, - {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, - {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, - {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, - {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, - {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, - {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, - {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, - {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, - {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, - {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, - {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, - {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, - {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, - {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, - {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, - {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, - {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, - {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, - {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, - {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, - {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, - {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, - {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, - {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, - {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, - {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, - {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, - {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, - {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, - {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, - {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, - {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, - {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, - {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, - {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, - {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, - {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, - {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, - {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, - {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, - {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, - {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, - {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, - {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, - {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, - {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, +python-versions = ">=3.10" +groups = ["main", "clients"] +files = [ + {file = "pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a"}, + {file = "pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7"}, + {file = "pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f"}, + {file = "pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec"}, + {file = "pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468"}, + {file = "pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed"}, + {file = "pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1"}, + {file = "pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb"}, + {file = "pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f"}, + {file = "pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756"}, + {file = "pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6"}, + {file = "pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd"}, + {file = "pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd"}, + {file = "pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c"}, + {file = "pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5"}, + {file = "pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b"}, + {file = "pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a"}, + {file = "pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26"}, + {file = "pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965"}, + {file = "pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7"}, + {file = "pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9"}, + {file = "pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91"}, + {file = "pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c"}, + {file = "pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df"}, + {file = "pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f"}, + {file = "pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09"}, + {file = "pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510"}, + {file = "pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89"}, + {file = "pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace"}, + {file = "pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec"}, + {file = "pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66"}, + {file = "pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35"}, + {file = "pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65"}, + {file = "pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3"}, + {file = "pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a"}, + {file = "pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e"}, + {file = "pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f"}, + {file = "pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8"}, + {file = "pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b"}, + {file = "pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330"}, + {file = "pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217"}, + {file = "pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930"}, + {file = "pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8"}, + {file = "pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0"}, + {file = "pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321"}, + {file = "pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b"}, + {file = "pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198"}, + {file = "pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130"}, + {file = "pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a"}, + {file = "pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d"}, + {file = "pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838"}, + {file = "pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e"}, + {file = "pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17"}, + {file = "pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385"}, + {file = "pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c"}, + {file = "pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d"}, + {file = "pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931"}, + {file = "pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7"}, + {file = "pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c"}, + {file = "pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45"}, + {file = "pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139"}, + {file = "pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402"}, + {file = "pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c"}, + {file = "pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f"}, + {file = "pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701"}, + {file = "pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace"}, + {file = "pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4"}, + {file = "pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39"}, + {file = "pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71"}, + {file = "pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827"}, + {file = "pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5"}, + {file = "pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658"}, + {file = "pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf"}, + {file = "pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64"}, + {file = "pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e"}, + {file = "pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777"}, + {file = "pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1"}, + {file = "pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9"}, + {file = "pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8"}, + {file = "pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418"}, + {file = "pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3"}, + {file = "pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a"}, + {file = "pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] -test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "setuptools", "trove-classifiers (>=2024.10.12)"] xmp = ["defusedxml"] [[package]] @@ -4359,12 +4469,12 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "platform_machine == \"x86_64\"" +groups = ["clients", "dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] +markers = {clients = "platform_machine == \"x86_64\""} [package.extras] dev = ["pre-commit", "tox"] @@ -4396,7 +4506,7 @@ version = "0.22.1" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094"}, {file = "prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28"}, @@ -4411,7 +4521,7 @@ version = "7.1.0" description = "Instrument your FastAPI app with Prometheus metrics" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9"}, {file = "prometheus_fastapi_instrumentator-7.1.0.tar.gz", hash = "sha256:be7cd61eeea4e5912aeccb4261c6631b3f227d8924542d79eaf5af3f439cbe5e"}, @@ -4442,7 +4552,7 @@ version = "0.3.2" description = "Accelerated property cache" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, @@ -4546,21 +4656,22 @@ files = [ [[package]] name = "protobuf" -version = "6.31.1" +version = "6.33.5" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ - {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, - {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, - {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, - {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, - {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, - {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, - {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, - {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, - {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, + {file = "protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b"}, + {file = "protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c"}, + {file = "protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5"}, + {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190"}, + {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd"}, + {file = "protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0"}, + {file = "protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c"}, + {file = "protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a"}, + {file = "protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02"}, + {file = "protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c"}, ] [[package]] @@ -4569,7 +4680,7 @@ version = "7.0.0" description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, @@ -4593,7 +4704,7 @@ version = "9.0.0" description = "Get CPU info with pure Python" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"}, {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"}, @@ -4601,14 +4712,14 @@ files = [ [[package]] name = "pyasn1" -version = "0.6.1" +version = "0.6.4" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, - {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, + {file = "pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b"}, + {file = "pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81"}, ] [[package]] @@ -4632,7 +4743,7 @@ version = "3.0.0" description = "Seamless operability between C++11 and Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] markers = "platform_machine == \"x86_64\"" files = [ {file = "pybind11-3.0.0-py3-none-any.whl", hash = "sha256:7c5cac504da5a701b5163f0e6a7ba736c713a096a5378383c5b4b064b753f607"}, @@ -4648,7 +4759,7 @@ version = "24.6.1" description = "ISO country, subdivision, language, currency and script definitions and their translations" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "pycountry-24.6.1-py3-none-any.whl", hash = "sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f"}, {file = "pycountry-24.6.1.tar.gz", hash = "sha256:b61b3faccea67f87d10c1f2b0fc0be714409e8fcdcc1315613174f6466c10221"}, @@ -4660,12 +4771,12 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["main"] -markers = "(platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\") and implementation_name != \"PyPy\"" +groups = ["main", "clients"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", clients = "(platform_python_implementation != \"PyPy\" or implementation_name == \"pypy\") and implementation_name != \"PyPy\""} [[package]] name = "pycryptodome" @@ -4673,7 +4784,7 @@ version = "3.23.0" description = "Cryptographic library for Python" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "pycryptodome-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a176b79c49af27d7f6c12e4b178b0824626f40a7b9fed08f712291b6d54bf566"}, {file = "pycryptodome-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:573a0b3017e06f2cffd27d92ef22e46aa3be87a2d317a5abf7cc0e84e321bd75"}, @@ -4724,7 +4835,7 @@ version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, @@ -4747,7 +4858,7 @@ version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, @@ -4859,7 +4970,7 @@ version = "2.10.5" description = "Extra Pydantic types." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "pydantic_extra_types-2.10.5-py3-none-any.whl", hash = "sha256:b60c4e23d573a69a4f1a16dd92888ecc0ef34fb0e655b4f305530377fa70e7a8"}, {file = "pydantic_extra_types-2.10.5.tar.gz", hash = "sha256:1dcfa2c0cf741a422f088e0dbb4690e7bfadaaf050da3d6f80d6c3cf58a2bad8"}, @@ -4884,7 +4995,7 @@ version = "8.0.5" description = "The kitchen sink of Python utility libraries for doing \"stuff\" in a functional way. Based on the Lo-Dash Javascript library." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "pydash-8.0.5-py3-none-any.whl", hash = "sha256:b2625f8981862e19911daa07f80ed47b315ce20d9b5eb57aaf97aaf570c3892f"}, {file = "pydash-8.0.5.tar.gz", hash = "sha256:7cc44ebfe5d362f4f5f06c74c8684143c5ac481376b059ff02570705523f9e2e"}, @@ -4898,14 +5009,14 @@ dev = ["build", "coverage", "furo", "invoke", "mypy", "pytest", "pytest-cov", "p [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["main", "clients", "dev"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] @@ -4913,14 +5024,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.13.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ - {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, - {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, + {file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"}, + {file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"}, ] [package.dependencies] @@ -4928,9 +5039,6 @@ cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"cryp [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pynacl" @@ -5016,12 +5124,12 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "platform_machine == \"x86_64\"" +groups = ["clients", "dev"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] +markers = {clients = "platform_machine == \"x86_64\""} [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} @@ -5033,13 +5141,32 @@ pygments = ">=2.7.2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.25.3" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, + {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -5050,14 +5177,14 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.2" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.10" +groups = ["main", "clients"] files = [ - {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, - {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, + {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, + {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, ] [package.extras] @@ -5065,26 +5192,26 @@ cli = ["click (>=5.0)"] [[package]] name = "python-multipart" -version = "0.0.20" +version = "0.0.31" description = "A streaming multipart parser for Python" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.10" +groups = ["clients"] files = [ - {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, - {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, + {file = "python_multipart-0.0.31-py3-none-any.whl", hash = "sha256:8408153d68a9773291fc1da39a8b85a50044bddbabd2dd72e9229776b7b15e28"}, + {file = "python_multipart-0.0.31.tar.gz", hash = "sha256:fc631183bb13e56db3158a4909908dfb2e23565286744e798241e63750e5d680"}, ] [[package]] name = "pytz" -version = "2025.2" +version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, - {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] @@ -5093,7 +5220,7 @@ version = "311" description = "Python for Window Extensions" optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "clients"] markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, @@ -5124,7 +5251,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -5187,7 +5314,7 @@ version = "27.0.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "pyzmq-27.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:b973ee650e8f442ce482c1d99ca7ab537c69098d53a3d046676a484fd710c87a"}, {file = "pyzmq-27.0.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:661942bc7cd0223d569d808f2e5696d9cc120acc73bf3e88a1f1be7ab648a7e4"}, @@ -5279,7 +5406,7 @@ version = "2.40.0" description = "Ray provides a simple, universal API for building distributed applications." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "ray-2.40.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:064af8bc52cc988c82470b8e76e5df417737fa7c1d87f597a892c69eb4ec3caa"}, {file = "ray-2.40.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45beb4019cd20b6cb10572d8012c771bccd623f544a669da6797ccf993c4bb33"}, @@ -5338,7 +5465,7 @@ version = "0.36.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -5355,7 +5482,7 @@ version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, @@ -5448,25 +5575,26 @@ files = [ [[package]] name = "requests" -version = "2.32.4" +version = "2.33.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.10" +groups = ["main", "clients"] files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, + {file = "requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b"}, + {file = "requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652"}, ] [package.dependencies] -certifi = ">=2017.4.17" +certifi = ">=2023.5.7" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +test = ["PySocks (>=1.5.6,!=1.5.7)", "pytest (>=3)", "pytest-cov", "pytest-httpbin (==2.1.0)", "pytest-mock", "pytest-xdist"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] [[package]] name = "requests-oauthlib" @@ -5474,7 +5602,7 @@ version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=3.4" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, @@ -5493,7 +5621,7 @@ version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -5512,7 +5640,7 @@ version = "0.14.9" description = "Rich toolkit for building command-line applications" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "rich_toolkit-0.14.9-py3-none-any.whl", hash = "sha256:e2404f1f088286f2f9d7f3a1a7591c8057792db466f6fecabfae283fa64126e2"}, {file = "rich_toolkit-0.14.9.tar.gz", hash = "sha256:090b6c3f87261bc1ca4fe7fc9b0d3625b5af917ccdbcd316a26719e5d3ab20b9"}, @@ -5529,7 +5657,7 @@ version = "0.6.4" description = "Python Bindings for the ignore crate" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "rignore-0.6.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c201375cfe76e56e61fcdfe50d0882aafb49544b424bfc828e0508dc9fbc431b"}, {file = "rignore-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4962d537e377394292c4828e1e9c620618dd8daa49ba746abe533733a89f8644"}, @@ -5662,7 +5790,7 @@ version = "0.26.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37"}, {file = "rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0"}, @@ -5831,7 +5959,7 @@ version = "0.5.3" description = "" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073"}, {file = "safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7"}, @@ -5865,74 +5993,90 @@ torch = ["safetensors[numpy]", "torch (>=1.10)"] [[package]] name = "sentencepiece" -version = "0.2.0" -description = "SentencePiece python wrapper" +version = "0.2.1" +description = "Unsupervised text tokenizer and detokenizer." optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "sentencepiece-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:188779e1298a1c8b8253c7d3ad729cb0a9891e5cef5e5d07ce4592c54869e227"}, - {file = "sentencepiece-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bed9cf85b296fa2b76fc2547b9cbb691a523864cebaee86304c43a7b4cb1b452"}, - {file = "sentencepiece-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7b67e724bead13f18db6e1d10b6bbdc454af574d70efbb36f27d90387be1ca3"}, - {file = "sentencepiece-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fde4b08cfe237be4484c6c7c2e2c75fb862cfeab6bd5449ce4caeafd97b767a"}, - {file = "sentencepiece-0.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c378492056202d1c48a4979650981635fd97875a00eabb1f00c6a236b013b5e"}, - {file = "sentencepiece-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1380ce6540a368de2ef6d7e6ba14ba8f3258df650d39ba7d833b79ee68a52040"}, - {file = "sentencepiece-0.2.0-cp310-cp310-win32.whl", hash = "sha256:a1151d6a6dd4b43e552394aed0edfe9292820272f0194bd56c7c1660a0c06c3d"}, - {file = "sentencepiece-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:d490142b0521ef22bc1085f061d922a2a6666175bb6b42e588ff95c0db6819b2"}, - {file = "sentencepiece-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17982700c4f6dbb55fa3594f3d7e5dd1c8659a274af3738e33c987d2a27c9d5c"}, - {file = "sentencepiece-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7c867012c0e8bcd5bdad0f791609101cb5c66acb303ab3270218d6debc68a65e"}, - {file = "sentencepiece-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd6071249c74f779c5b27183295b9202f8dedb68034e716784364443879eaa6"}, - {file = "sentencepiece-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f90c55a65013cbb8f4d7aab0599bf925cde4adc67ae43a0d323677b5a1c6cb"}, - {file = "sentencepiece-0.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b293734059ef656dcd65be62ff771507bea8fed0a711b6733976e1ed3add4553"}, - {file = "sentencepiece-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e58b47f933aca74c6a60a79dcb21d5b9e47416256c795c2d58d55cec27f9551d"}, - {file = "sentencepiece-0.2.0-cp311-cp311-win32.whl", hash = "sha256:c581258cf346b327c62c4f1cebd32691826306f6a41d8c4bec43b010dee08e75"}, - {file = "sentencepiece-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:0993dbc665f4113017892f1b87c3904a44d0640eda510abcacdfb07f74286d36"}, - {file = "sentencepiece-0.2.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ea5f536e32ea8ec96086ee00d7a4a131ce583a1b18d130711707c10e69601cb2"}, - {file = "sentencepiece-0.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d0cb51f53b6aae3c36bafe41e86167c71af8370a039f542c43b0cce5ef24a68c"}, - {file = "sentencepiece-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3212121805afc58d8b00ab4e7dd1f8f76c203ddb9dc94aa4079618a31cf5da0f"}, - {file = "sentencepiece-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a3149e3066c2a75e0d68a43eb632d7ae728c7925b517f4c05c40f6f7280ce08"}, - {file = "sentencepiece-0.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:632f3594d3e7ac8b367bca204cb3fd05a01d5b21455acd097ea4c0e30e2f63d7"}, - {file = "sentencepiece-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f295105c6bdbb05bd5e1b0cafbd78ff95036f5d3641e7949455a3f4e5e7c3109"}, - {file = "sentencepiece-0.2.0-cp312-cp312-win32.whl", hash = "sha256:fb89f811e5efd18bab141afc3fea3de141c3f69f3fe9e898f710ae7fe3aab251"}, - {file = "sentencepiece-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a673a72aab81fef5ebe755c6e0cc60087d1f3a4700835d40537183c1703a45f"}, - {file = "sentencepiece-0.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4547683f330289ec4f093027bfeb87f9ef023b2eb6f879fdc4a8187c7e0ffb90"}, - {file = "sentencepiece-0.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd6175f7eaec7142d2bf6f6597ce7db4c9ac89acf93fcdb17410c3a8b781eeb"}, - {file = "sentencepiece-0.2.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:859ba1acde782609a0910a26a60e16c191a82bf39b5621107552c0cd79fad00f"}, - {file = "sentencepiece-0.2.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbbef6cc277f8f18f36959e305f10b1c620442d75addc79c21d7073ae581b50"}, - {file = "sentencepiece-0.2.0-cp36-cp36m-win32.whl", hash = "sha256:536b934e244829e3fe6c4f198652cd82da48adb9aa145c9f00889542726dee3d"}, - {file = "sentencepiece-0.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:0a91aaa3c769b52440df56fafda683b3aa48e3f2169cf7ee5b8c8454a7f3ae9b"}, - {file = "sentencepiece-0.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:787e480ca4c1d08c9985a7eb1eae4345c107729c99e9b5a9a00f2575fc7d4b4b"}, - {file = "sentencepiece-0.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4d158189eb2ecffea3a51edf6d25e110b3678ec47f1a40f2d541eafbd8f6250"}, - {file = "sentencepiece-0.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e5ca43013e8935f25457a4fca47e315780172c3e821b4b13a890668911c792"}, - {file = "sentencepiece-0.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7140d9e5a74a0908493bb4a13f1f16a401297bd755ada4c707e842fbf6f0f5bf"}, - {file = "sentencepiece-0.2.0-cp37-cp37m-win32.whl", hash = "sha256:6cf333625234f247ab357b0bd9836638405ea9082e1543d5b8408f014979dcbf"}, - {file = "sentencepiece-0.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff88712338b01031910e8e61e7239aff3ce8869ee31a47df63cb38aadd591bea"}, - {file = "sentencepiece-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20813a68d4c221b1849c62c30e1281ea81687894d894b8d4a0f4677d9311e0f5"}, - {file = "sentencepiece-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:926ef920ae2e8182db31d3f5d081ada57804e3e1d3a8c4ef8b117f9d9fb5a945"}, - {file = "sentencepiece-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:89f65f69636b7e9c015b79dff9c9985a9bc7d19ded6f79ef9f1ec920fdd73ecf"}, - {file = "sentencepiece-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f67eae0dbe6f2d7d6ba50a354623d787c99965f068b81e145d53240198021b0"}, - {file = "sentencepiece-0.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98501e075f35dd1a1d5a20f65be26839fcb1938752ec61539af008a5aa6f510b"}, - {file = "sentencepiece-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d1d2cc4882e8d6a1adf9d5927d7716f80617fc693385661caff21888972269"}, - {file = "sentencepiece-0.2.0-cp38-cp38-win32.whl", hash = "sha256:b99a308a2e5e569031ab164b74e6fab0b6f37dfb493c32f7816225f4d411a6dd"}, - {file = "sentencepiece-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:cdb701eec783d3ec86b7cd4c763adad8eaf6b46db37ee1c36e5e6c44b3fe1b5f"}, - {file = "sentencepiece-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1e0f9c4d0a6b0af59b613175f019916e28ade076e21242fd5be24340d8a2f64a"}, - {file = "sentencepiece-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:298f21cc1366eb60311aedba3169d30f885c363ddbf44214b0a587d2908141ad"}, - {file = "sentencepiece-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f1ec95aa1e5dab11f37ac7eff190493fd87770f7a8b81ebc9dd768d1a3c8704"}, - {file = "sentencepiece-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b06b70af54daa4b4904cbb90b4eb6d35c9f3252fdc86c9c32d5afd4d30118d8"}, - {file = "sentencepiece-0.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e37bac44dd6603388cb598c64ff7a76e41ca774646f21c23aadfbf5a2228ab"}, - {file = "sentencepiece-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0461324897735512a32d222e3d886e24ad6a499761952b6bda2a9ee6e4313ea5"}, - {file = "sentencepiece-0.2.0-cp39-cp39-win32.whl", hash = "sha256:38aed822fb76435fa1f12185f10465a94ab9e51d5e8a9159e9a540ce926f0ffd"}, - {file = "sentencepiece-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:d8cf876516548b5a1d6ac4745d8b554f5c07891d55da557925e5c13ff0b4e6ad"}, - {file = "sentencepiece-0.2.0.tar.gz", hash = "sha256:a52c19171daaf2e697dc6cbe67684e0fa341b1248966f6aebb541de654d15843"}, +python-versions = ">=3.9" +groups = ["clients"] +files = [ + {file = "sentencepiece-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e10fa50bdbaa5e2445dbd387979980d391760faf0ec99a09bd7780ff37eaec44"}, + {file = "sentencepiece-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f27ae6deea72efdb6f361750c92f6c21fd0ad087445082770cc34015213c526"}, + {file = "sentencepiece-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60937c959e6f44159fdd9f56fbdd302501f96114a5ba436829496d5f32d8de3f"}, + {file = "sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8b1d91545578852f128650b8cce4ec20f93d39b378ff554ebe66290f2dabb92"}, + {file = "sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27e38eee653abc3d387862e67bc5c8b6f428cd604e688b85d29170b7e725c26c"}, + {file = "sentencepiece-0.2.1-cp310-cp310-win32.whl", hash = "sha256:251874d720ac7f28024a168501f3c7bb15d1802245f6e66de565f18bbb9b5eaa"}, + {file = "sentencepiece-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:e52144670738b4b477fade6c2a9b6af71a8d0094514c9853ac9f6fc1fcfabae7"}, + {file = "sentencepiece-0.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:9076430ac25dfa7147d9d05751dbc66a04bc1aaac371c07f84952979ea59f0d0"}, + {file = "sentencepiece-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6356d0986b8b8dc351b943150fcd81a1c6e6e4d439772e8584c64230e58ca987"}, + {file = "sentencepiece-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f8ba89a3acb3dc1ae90f65ec1894b0b9596fdb98ab003ff38e058f898b39bc7"}, + {file = "sentencepiece-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02593eca45440ef39247cee8c47322a34bdcc1d8ae83ad28ba5a899a2cf8d79a"}, + {file = "sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a0d15781a171d188b661ae4bde1d998c303f6bd8621498c50c671bd45a4798e"}, + {file = "sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f5a3e0d9f445ed9d66c0fec47d4b23d12cfc858b407a03c194c1b26c2ac2a63"}, + {file = "sentencepiece-0.2.1-cp311-cp311-win32.whl", hash = "sha256:6d297a1748d429ba8534eebe5535448d78b8acc32d00a29b49acf28102eeb094"}, + {file = "sentencepiece-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:82d9ead6591015f009cb1be1cb1c015d5e6f04046dbb8c9588b931e869a29728"}, + {file = "sentencepiece-0.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:39f8651bd10974eafb9834ce30d9bcf5b73e1fc798a7f7d2528f9820ca86e119"}, + {file = "sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133"}, + {file = "sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6"}, + {file = "sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76"}, + {file = "sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167"}, + {file = "sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b"}, + {file = "sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068"}, + {file = "sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de"}, + {file = "sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4"}, + {file = "sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706"}, + {file = "sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062"}, + {file = "sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff"}, + {file = "sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820"}, + {file = "sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47"}, + {file = "sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f"}, + {file = "sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b"}, + {file = "sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484"}, + {file = "sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0"}, + {file = "sentencepiece-0.2.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5d0350b686c320068702116276cfb26c066dc7e65cfef173980b11bb4d606719"}, + {file = "sentencepiece-0.2.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7f54a31cde6fa5cb030370566f68152a742f433f8d2be458463d06c208aef33"}, + {file = "sentencepiece-0.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c83b85ab2d6576607f31df77ff86f28182be4a8de6d175d2c33ca609925f5da1"}, + {file = "sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1855f57db07b51fb51ed6c9c452f570624d2b169b36f0f79ef71a6e6c618cd8b"}, + {file = "sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01e6912125cb45d3792f530a4d38f8e21bf884d6b4d4ade1b2de5cf7a8d2a52b"}, + {file = "sentencepiece-0.2.1-cp314-cp314-win32.whl", hash = "sha256:c415c9de1447e0a74ae3fdb2e52f967cb544113a3a5ce3a194df185cbc1f962f"}, + {file = "sentencepiece-0.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:881b2e44b14fc19feade3cbed314be37de639fc415375cefaa5bc81a4be137fd"}, + {file = "sentencepiece-0.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:2005242a16d2dc3ac5fe18aa7667549134d37854823df4c4db244752453b78a8"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a19adcec27c524cb7069a1c741060add95f942d1cbf7ad0d104dffa0a7d28a2b"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e37e4b4c4a11662b5db521def4e44d4d30ae69a1743241412a93ae40fdcab4bb"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:477c81505db072b3ab627e7eab972ea1025331bd3a92bacbf798df2b75ea86ec"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:010f025a544ef770bb395091d57cb94deb9652d8972e0d09f71d85d5a0816c8c"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:733e59ff1794d26db706cd41fc2d7ca5f6c64a820709cb801dc0ea31780d64ab"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-win32.whl", hash = "sha256:d3233770f78e637dc8b1fda2cd7c3b99ec77e7505041934188a4e7fe751de3b0"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e4366c97b68218fd30ea72d70c525e6e78a6c0a88650f57ac4c43c63b234a9d"}, + {file = "sentencepiece-0.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:105e36e75cbac1292642045458e8da677b2342dcd33df503e640f0b457cb6751"}, + {file = "sentencepiece-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:afefe50a0cdcb4f2fd9733cb52001a2c164181ee2d82c32d38f5b1b326a8528c"}, + {file = "sentencepiece-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:891ade6503dd93d418c03993f7d6a8aa20260c422cefff5096b9068185e67642"}, + {file = "sentencepiece-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:814978ac05130dd5812b4b03215c766bc6abaef13e7bd72bc534e4d1e12e9a4c"}, + {file = "sentencepiece-0.2.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:017f97b274d4b0baa84b2dc743bf4517be81156f413bb24f12aacacde378e5ab"}, + {file = "sentencepiece-0.2.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c4ebcb3c6ab1496ab1c37c79ef7bb563b8726f29548c30773b7a4cb152df1a"}, + {file = "sentencepiece-0.2.1-cp39-cp39-win32.whl", hash = "sha256:caa4e560c72c151da80036aecc2159e51a7fd8ae9efebefd96860460ce6bd025"}, + {file = "sentencepiece-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:2af5a1fb05013332ad94343b8b5f3973e006a2dde2dfba55a819549e054e2f0f"}, + {file = "sentencepiece-0.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:3d165fbb9bf8fba35f1946ba2617c3f9995679f07438325f07c026d53f33e746"}, + {file = "sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad"}, ] +[package.extras] +test = ["pytest"] +testpaths = ["test"] + [[package]] name = "sentry-sdk" version = "2.34.1" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "sentry_sdk-2.34.1-py2.py3-none-any.whl", hash = "sha256:b7a072e1cdc5abc48101d5146e1ae680fa81fe886d8d95aaa25a0b450c818d32"}, {file = "sentry_sdk-2.34.1.tar.gz", hash = "sha256:69274eb8c5c38562a544c3e9f68b5be0a43be4b697f5fd385bf98e4fbe672687"}, @@ -6099,11 +6243,12 @@ version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] +markers = {clients = "python_version == \"3.12\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] @@ -6120,7 +6265,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -6132,7 +6277,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -6156,19 +6301,121 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "sqlalchemy" +version = "2.0.48" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +groups = ["clients"] +files = [ + {file = "sqlalchemy-2.0.48-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7001dc9d5f6bb4deb756d5928eaefe1930f6f4179da3924cbd95ee0e9f4dce89"}, + {file = "sqlalchemy-2.0.48-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a89ce07ad2d4b8cfc30bd5889ec40613e028ed80ef47da7d9dd2ce969ad30e0"}, + {file = "sqlalchemy-2.0.48-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10853a53a4a00417a00913d270dddda75815fcb80675874285f41051c094d7dd"}, + {file = "sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fac0fa4e4f55f118fd87177dacb1c6522fe39c28d498d259014020fec9164c29"}, + {file = "sqlalchemy-2.0.48-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3713e21ea67bca727eecd4a24bf68bcd414c403faae4989442be60994301ded0"}, + {file = "sqlalchemy-2.0.48-cp310-cp310-win32.whl", hash = "sha256:d404dc897ce10e565d647795861762aa2d06ca3f4a728c5e9a835096c7059018"}, + {file = "sqlalchemy-2.0.48-cp310-cp310-win_amd64.whl", hash = "sha256:841a94c66577661c1f088ac958cd767d7c9bf507698f45afffe7a4017049de76"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b4c575df7368b3b13e0cebf01d4679f9a28ed2ae6c1cd0b1d5beffb6b2007dc"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e83e3f959aaa1c9df95c22c528096d94848a1bc819f5d0ebf7ee3df0ca63db6c"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f7b7243850edd0b8b97043f04748f31de50cf426e939def5c16bedb540698f7"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82745b03b4043e04600a6b665cb98697c4339b24e34d74b0a2ac0a2488b6f94d"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5e088bf43f6ee6fec7dbf1ef7ff7774a616c236b5c0cb3e00662dd71a56b571"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-win32.whl", hash = "sha256:9c7d0a77e36b5f4b01ca398482230ab792061d243d715299b44a0b55c89fe617"}, + {file = "sqlalchemy-2.0.48-cp311-cp311-win_amd64.whl", hash = "sha256:583849c743e0e3c9bb7446f5b5addeacedc168d657a69b418063dfdb2d90081c"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99"}, + {file = "sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131"}, + {file = "sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2"}, + {file = "sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae"}, + {file = "sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb"}, + {file = "sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b"}, + {file = "sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121"}, + {file = "sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485"}, + {file = "sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6"}, + {file = "sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0"}, + {file = "sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241"}, + {file = "sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0"}, + {file = "sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3"}, + {file = "sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b"}, + {file = "sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f"}, + {file = "sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8649a14caa5f8a243628b1d61cf530ad9ae4578814ba726816adb1121fc493e"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bb85c546591569558571aa1b06aba711b26ae62f111e15e56136d69920e1616"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6b764fb312bd35e47797ad2e63f0d323792837a6ac785a4ca967019357d2bc7"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:7c998f2ace8bf76b453b75dbcca500d4f4b9dd3908c13e89b86289b37784848b"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d64177f443594c8697369c10e4bbcac70ef558e0f7921a1de7e4a3d1734bcf67"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-win32.whl", hash = "sha256:01f6bbd4308b23240cf7d3ef117557c8fd097ec9549d5d8a52977544e35b40ad"}, + {file = "sqlalchemy-2.0.48-cp38-cp38-win_amd64.whl", hash = "sha256:858e433f12b0e5b3ed2f8da917433b634f4937d0e8793e5cb33c54a1a01df565"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4599a95f9430ae0de82b52ff0d27304fe898c17cb5f4099f7438a51b9998ac77"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f27f9da0a7d22b9f981108fd4b62f8b5743423388915a563e651c20d06c1f457"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fcccbbc0c13c13702c471da398b8cd72ba740dca5859f148ae8e0e8e0d3e7e"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a5b429eb84339f9f05e06083f119ad814e6d85e27ecbdf9c551dfdbb128eaf8a"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bcb8ebbf2e2c36cfe01a94f2438012c6a9d494cf80f129d9753bcdf33bfc35a6"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-win32.whl", hash = "sha256:e214d546c8ecb5fc22d6e6011746082abf13a9cf46eefb45769c7b31407c97b5"}, + {file = "sqlalchemy-2.0.48-cp39-cp39-win_amd64.whl", hash = "sha256:b8fc3454b4f3bd0a368001d0e968852dad45a873f8b4babd41bc302ec851a099"}, + {file = "sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096"}, + {file = "sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7"}, +] + +[package.dependencies] +greenlet = {version = ">=1", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"] +aioodbc = ["aioodbc", "greenlet (>=1)"] +aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (>=1)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "starlette" version = "0.46.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, @@ -6186,7 +6433,7 @@ version = "1.7.3" description = "Strict, typed YAML parser" optional = false python-versions = ">=3.7.0" -groups = ["main"] +groups = ["clients"] files = [ {file = "strictyaml-1.7.3-py3-none-any.whl", hash = "sha256:fb5c8a4edb43bebb765959e420f9b3978d7f1af88c80606c03fb420888f5d1c7"}, {file = "strictyaml-1.7.3.tar.gz", hash = "sha256:22f854a5fcab42b5ddba8030a0e4be51ca89af0267961c8d6cfa86395586c407"}, @@ -6201,7 +6448,7 @@ version = "1.13.1" description = "Computer algebra system (CAS) in Python" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, @@ -6219,7 +6466,7 @@ version = "3.1.0" description = "ANSI color formatting for output in terminal" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"}, {file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"}, @@ -6234,7 +6481,7 @@ version = "0.7.0" description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "tiktoken-0.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485f3cc6aba7c6b6ce388ba634fbba656d9ee27f766216f45146beb4ac18b25f"}, {file = "tiktoken-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e54be9a2cd2f6d6ffa3517b064983fb695c9a9d8aa7d574d1ef3c3f931a99225"}, @@ -6287,7 +6534,7 @@ version = "0.21.4" description = "" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133"}, {file = "tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60"}, @@ -6320,7 +6567,7 @@ version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -6372,7 +6619,7 @@ version = "1.2.0" description = "A lil' TOML writer" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"}, {file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"}, @@ -6384,7 +6631,7 @@ version = "2.5.1" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.8.0" -groups = ["main"] +groups = ["clients"] files = [ {file = "torch-2.5.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:71328e1bbe39d213b8721678f9dcac30dfc452a46d586f1d514a6aa0a99d4744"}, {file = "torch-2.5.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:34bfa1a852e5714cbfa17f27c49d8ce35e1b7af5608c4bc6e81392c352dbc601"}, @@ -6437,7 +6684,7 @@ version = "2.5.1" description = "An audio package for PyTorch" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] files = [ {file = "torchaudio-2.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:901291d770aeeb1f51920bb5aa73ff82e9b7f26354a3c7b90d80ff0b4e9a5044"}, {file = "torchaudio-2.5.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:abacbec3b6d695cf99ada8b1db55db933181c8ff7d283e246e2bbefdde674235"}, @@ -6466,7 +6713,7 @@ version = "0.20.1" description = "image and video datasets and models for torch deep learning" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "torchvision-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4878fefb96ef293d06c27210918adc83c399d9faaf34cda5a63e129f772328f1"}, {file = "torchvision-0.20.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8ffbdf8bf5b30eade22d459f5a313329eeadb20dc75efa142987b53c007098c3"}, @@ -6501,7 +6748,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -6523,7 +6770,7 @@ version = "4.54.1" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.9.0" -groups = ["main"] +groups = ["clients"] files = [ {file = "transformers-4.54.1-py3-none-any.whl", hash = "sha256:c89965a4f62a0d07009d45927a9c6372848a02ab9ead9c318c3d082708bab529"}, {file = "transformers-4.54.1.tar.gz", hash = "sha256:b2551bb97903f13bd90c9467d0a144d41ca4d142defc044a99502bb77c5c1052"}, @@ -6597,7 +6844,7 @@ version = "3.1.0" description = "A language and compiler for custom Deep Learning operations" optional = false python-versions = "*" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8"}, @@ -6621,7 +6868,7 @@ version = "0.16.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["clients"] files = [ {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, @@ -6639,7 +6886,7 @@ version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients", "dev"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, @@ -6651,7 +6898,7 @@ version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "clients"] files = [ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, @@ -6692,14 +6939,14 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.10" +groups = ["main", "clients"] files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, + {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, + {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] @@ -6714,7 +6961,7 @@ version = "0.35.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"}, {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"}, @@ -6740,7 +6987,7 @@ version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" -groups = ["main"] +groups = ["clients"] markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" files = [ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, @@ -6793,7 +7040,7 @@ version = "0.7.3" description = "A high-throughput and memory-efficient inference and serving engine for LLMs" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "vllm-0.7.3-cp38-abi3-manylinux1_x86_64.whl", hash = "sha256:b8a593711ee0d798c3b95068988440cf4aa3d5d30dfc2ee9c2276acb6ecf8277"}, {file = "vllm-0.7.3.tar.gz", hash = "sha256:841cc30bd4ffbc037e8b8bf6dbacbedc60cc0b2abb60366873b185159a1204e6"}, @@ -6905,7 +7152,7 @@ version = "1.1.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc"}, {file = "watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df"}, @@ -7053,7 +7300,7 @@ version = "15.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, @@ -7132,7 +7379,7 @@ version = "1.17.2" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["clients"] files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -7221,7 +7468,7 @@ version = "0.0.28.post3" description = "XFormers: A collection of composable Transformer building blocks." optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ {file = "xformers-0.0.28.post3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:648483325366fb3c6a42246f99646101d3cfd678725b3ffc50a4708a222ae973"}, @@ -7241,7 +7488,7 @@ version = "0.1.11" description = "Efficient, Flexible and Portable Structured Generation" optional = false python-versions = "<4,>=3.8" -groups = ["main"] +groups = ["clients"] markers = "platform_machine == \"x86_64\"" files = [ {file = "xgrammar-0.1.11-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:5ed31db2669dc499d9d29bb16f30b3395332ff9d0fb80b759697190a5ef5258b"}, @@ -7273,7 +7520,7 @@ version = "1.20.1" description = "Yet another URL library" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients", "dev"] files = [ {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, @@ -7392,7 +7639,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["clients"] files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, @@ -7409,4 +7656,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "5dfe89314c8dacdfdb06090c7397dec9111e252927e0b83358699bfd0f2d4248" +content-hash = "320e2327e75e1ec22ad5ba6dfcceb10a9f4835cab4a749397f5eddb3f3748503" diff --git a/pyproject.toml b/pyproject.toml index eeb5f7e6..8e103aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,22 +15,42 @@ pydantic = "^2.7.4" kubernetes = "^30.1.0" colorama = "^0.4.6" rich = "^13.7.1" -tiktoken = "^0.7.0" prompt-toolkit = "^3.0.47" prometheus-api-client = "^0.5.5" -autogen-agentchat = "^0.2.40" +tiktoken = "^0.7.0" elasticsearch = "^8.16.0" azure-identity = "^1.19.0" -azure-ai-ml = "^1.22.1" paramiko = "^3.5.0" wandb = "^0.19.7" python-dotenv = "^1.0.1" +fastapi = "^0.115.12" +docker = "^7.0" +pandas = "^2.0" +pytz = "^2024.0" +requests = "^2.33" +pyyaml = "^6.0" + +# Client/agent packages that require CUDA or heavy ML runtimes. +# These are installed by default (`poetry install`) but can be excluded in +# environments that only need the core framework, e.g. CI: +# poetry install --without clients +[tool.poetry.group.clients.dependencies] +autogen-agentchat = "^0.2.40" +azure-ai-ml = "^1.22.1" vllm = "^0.7.3" transformers = "^4.49.0" -fastapi = "^0.115.12" groq = "^0.28.0" flwr = "^1.19.0" +# Developer tooling (testing, etc.) +[tool.poetry.group.dev.dependencies] +pytest = "^8.3" +pytest-asyncio = "^0.25" + +[tool.pytest.ini_options] +markers = [ + "integration: marks tests that require a live Kubernetes cluster (deselect with '-m not integration')", +] [build-system] requires = ["poetry-core"] diff --git a/scripts/ansible/README.md b/scripts/ansible/README.md index 9f8432cf..e40552bc 100644 --- a/scripts/ansible/README.md +++ b/scripts/ansible/README.md @@ -2,11 +2,13 @@ This is the instruction to use Ansible to build a remote cluster for AIOpsLab. We currently use [CloudLab](https://www.cloudlab.us/) but we believe this will work on any servers you have access to. -There are two ways to setup AIOpsLab for a remote cluster, please choose either approach (A) or (B) to run AIOpsLab. +There are two ways to setup AIOpsLab for a remote cluster, please choose either Mode A or Mode B to run AIOpsLab. -## (A) Run AIOpsLab inside the cluster +## Mode A: Run AIOpsLab inside the cluster If you want to run your agent and AIOpsLab **inside** the cluster, please upload the _whole AIOpsLab codebase_ to the control node, and follow steps 1), 2), 3), 4). +> **Tip**: For Azure VMs, `deploy.py --mode A` automates this entire process. See [Terraform README](../terraform/README.md). + ### 1) Exchange SSH keys **Do this on your own machine that is controlling the cluster.** Your nodes need to be able to ssh into each other to setup the cluster. If your nodes have different keys then the device you're running AIOpsLab on, or if you're unsure, proceed with the following steps to exchange ssh keys between the nodes. @@ -41,9 +43,11 @@ ansible --version Once those commands have completed, you can exit the SSH session. -## (B) Run AIOpsLab outside of the cluster +## Mode B: Run AIOpsLab outside of the cluster If you want to run your agent and AIOpsLab **outside** of the cluster (e.g., on your own workstation), please follow steps 3), 4) (1 and 2 are no longer needed). +> **Tip**: For Azure VMs, `deploy.py --mode B` automates this entire process. See [Terraform README](../terraform/README.md). + ### 3) Modify the inventory file ```bash diff --git a/scripts/ansible/inventory.yml.example b/scripts/ansible/inventory.yml.example index e21e24c7..75e124b2 100644 --- a/scripts/ansible/inventory.yml.example +++ b/scripts/ansible/inventory.yml.example @@ -1,19 +1,31 @@ - +# Ansible Inventory for AIOpsLab Kubernetes Cluster +# +# Copy this file to inventory.yml and update the values. +# For Terraform deployments, use generate_inventory.py instead. +# +# user_home_base: +# - Use "/home" for cloud VMs (Azure, AWS, GCP) - standard Linux +# - Use "/users" for Emulab testbed +# all: vars: k8s_user: - k8s_user2: + user_home_base: "/home" # Change to "/users" for Emulab + ansible_ssh_private_key_file: ~/.ssh/id_rsa children: control_nodes: hosts: control_node: - ansible_host: + ansible_host: ansible_user: "{{ k8s_user }}" + private_ip: # Required for K8s API server worker_nodes: hosts: worker_node_1: - ansible_host: - ansible_user: "{{ k8s_user2 }}" + ansible_host: + ansible_user: "{{ k8s_user }}" + private_ip: worker_node_2: - ansible_host: - ansible_user: "{{ k8s_user2 }}" \ No newline at end of file + ansible_host: + ansible_user: "{{ k8s_user }}" + private_ip: diff --git a/scripts/ansible/remote_setup_controller_worker.yml b/scripts/ansible/remote_setup_controller_worker.yml index 0dad75c1..f3488dac 100644 --- a/scripts/ansible/remote_setup_controller_worker.yml +++ b/scripts/ansible/remote_setup_controller_worker.yml @@ -1,32 +1,57 @@ --- # Control Node Setup -- hosts: control_node # Control plane tasks +- hosts: control_node become: true vars: - kubeconfig_path: "/users/{{ k8s_user }}/.kube/config" + # user_home_base: Set in inventory. Change based on environment. E.g., Use "/home" for cloud VMs, "/users" for Emulab + user_home: "{{ user_home_base }}/{{ k8s_user }}" + kubeconfig_path: "{{ user_home }}/.kube/config" tasks: - - name: Resolve control node hostname to IP + # For Emulab: resolve ansible_host hostname to IP + # For cloud VMs (Terraform): use private_ip for internal, ansible_host (public) for external + - name: Determine control plane IPs + set_fact: + control_plane_ip: "{{ hostvars['control_node'].private_ip | default('') }}" + control_plane_public_ip: "{{ hostvars['control_node'].ansible_host }}" + + - name: Resolve hostname if private_ip not set (Emulab) command: getent ahosts "{{ hostvars['control_node'].ansible_host }}" register: resolved_ip_output - - name: Parse resolved IP from output + when: control_plane_ip == '' + + - name: Use resolved IP for Emulab set_fact: - resolved_control_plane_ip: "{{ resolved_ip_output.stdout_lines[0].split(' ')[0] }}" - - name: Set resolved_control_plane_ip globally + control_plane_ip: "{{ resolved_ip_output.stdout_lines[0].split(' ')[0] }}" + when: control_plane_ip == '' + + - name: Display control plane IPs being used + debug: + msg: + - "Control plane private IP: {{ control_plane_ip }}" + - "Control plane public IP: {{ control_plane_public_ip }}" + + - name: Set control_plane_ip globally for workers add_host: name: "global" - resolved_control_plane_ip: "{{ resolved_control_plane_ip }}" + control_plane_ip: "{{ control_plane_ip }}" + control_plane_public_ip: "{{ control_plane_public_ip }}" + - name: Initialize Kubernetes control plane shell: | - kubeadm init --pod-network-cidr=10.244.0.0/16 --cri-socket /var/run/cri-dockerd.sock --apiserver-advertise-address={{ resolved_control_plane_ip }} + kubeadm init \ + --pod-network-cidr=10.244.0.0/16 \ + --cri-socket unix:///var/run/cri-dockerd.sock \ + --apiserver-advertise-address={{ control_plane_ip }} \ + --apiserver-cert-extra-sans={{ control_plane_public_ip }},{{ control_plane_ip }} args: creates: /etc/kubernetes/admin.conf - name: Ensure .kube directory exists file: - path: "/users/{{ k8s_user }}/.kube" + path: "{{ user_home }}/.kube" state: directory mode: '0755' owner: "{{ k8s_user }}" - # group: "{{ k8s_user }}" + group: "{{ k8s_user }}" become: true - name: Temporarily set permissions to read admin.conf file: @@ -37,28 +62,40 @@ - name: Set up kube config for kubectl on control plane copy: src: /etc/kubernetes/admin.conf - dest: "/users/{{ k8s_user }}/.kube/config" - mode: '0644' + dest: "{{ kubeconfig_path }}" + mode: '0600' remote_src: true + owner: "{{ k8s_user }}" + group: "{{ k8s_user }}" become: true - become_method: sudo - - name: Ensure ownership of kube config for kubectl + + - name: Restore admin.conf permissions file: - path: "/users/{{ k8s_user }}/.kube/config" - owner: "{{ k8s_user }}" - # group: "{{ k8s_user }}" - mode: '0644' + path: /etc/kubernetes/admin.conf + mode: '0600' become: true - - name: Display ansible_user_id - debug: - msg: "ansible_user_id is {{ ansible_user_id }}" - - - name: Fetch admin.conf to localhost for kubeconfig + + - name: Fetch admin.conf to localhost for kubeconfig (Mode B - remote access) fetch: src: /etc/kubernetes/admin.conf - dest: ~/.kube/config + dest: "{{ lookup('env', 'HOME') }}/.kube/config" flat: yes become: true + + - name: Set local kubeconfig permissions + delegate_to: localhost + become: false + file: + path: "{{ lookup('env', 'HOME') }}/.kube/config" + mode: '0600' + + - name: Update kubeconfig to use public IP for remote access + delegate_to: localhost + become: false + replace: + path: "{{ lookup('env', 'HOME') }}/.kube/config" + regexp: 'server: https://[^:]+:6443' + replace: 'server: https://{{ control_plane_public_ip }}:6443' - name: Generate kubeadm join command shell: kubeadm token create --print-join-command register: kubeadm_join_command @@ -70,34 +107,93 @@ - name: Display kube_token debug: msg: "kube_token is {{ kube_token }}" + no_log: true - name: Display cert_hash debug: msg: "cert_hash is {{ cert_hash }}" + no_log: true - name: Install Flannel network plugin shell: | kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml - args: - creates: /etc/kubernetes/kube-flannel.yml environment: KUBECONFIG: "{{ kubeconfig_path }}" - - name: Untaint the control plane to host pods - shell: kubectl taint nodes $(hostname) node-role.kubernetes.io/control-plane:NoSchedule- || true + register: flannel_result + changed_when: "'created' in flannel_result.stdout or 'configured' in flannel_result.stdout" + + - name: Wait for Flannel pods to be ready + shell: | + kubectl get pods -n kube-flannel -l app=flannel --no-headers 2>/dev/null | grep -v Running || true + environment: + KUBECONFIG: "{{ kubeconfig_path }}" + register: flannel_pods + until: flannel_pods.stdout == "" + retries: 30 + delay: 10 + changed_when: false + + - name: Untaint the control plane to allow pod scheduling + shell: kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true + environment: + KUBECONFIG: "{{ kubeconfig_path }}" + register: untaint_result + changed_when: "'untainted' in untaint_result.stdout" # Worker Node Setup - hosts: worker_nodes become: true + vars: + user_home: "{{ user_home_base }}/{{ k8s_user }}" tasks: + - name: Check if worker already joined cluster + stat: + path: /etc/kubernetes/kubelet.conf + register: kubelet_conf + - name: Join Kubernetes cluster shell: | - kubeadm join {{ hostvars['global'].resolved_control_plane_ip }}:6443 --token {{ hostvars['control_node'].kube_token }} --discovery-token-ca-cert-hash sha256:{{ hostvars['control_node'].cert_hash }} --cri-socket unix:///var/run/cri-dockerd.sock --v=5 - args: - creates: /var/lib/kubelet/kubeadm-flags.env - become: true - - name: Ensure .kube directory exists - file: - path: "/users/{{ ansible_user }}/.kube" - state: directory - mode: '0755' - become_user: "{{ ansible_user }}" # Ensure directory is created under the correct user - - name: Display ansible_user + kubeadm join {{ hostvars['global'].control_plane_ip }}:6443 \ + --token {{ hostvars['control_node'].kube_token }} \ + --discovery-token-ca-cert-hash sha256:{{ hostvars['control_node'].cert_hash }} \ + --cri-socket unix:///var/run/cri-dockerd.sock + when: not kubelet_conf.stat.exists + register: join_result + + - name: Display join result + debug: + msg: "{{ join_result.stdout_lines | default(['Already joined']) }}" + +# Final Verification +- hosts: control_node + become: true + vars: + user_home: "{{ user_home_base }}/{{ k8s_user }}" + kubeconfig_path: "{{ user_home }}/.kube/config" + tasks: + - name: Wait for all nodes to be Ready + shell: | + kubectl get nodes --no-headers | grep -v " Ready " || true + environment: + KUBECONFIG: "{{ kubeconfig_path }}" + register: nodes_not_ready + until: nodes_not_ready.stdout == "" + retries: 30 + delay: 10 + changed_when: false + + - name: Display final cluster status + shell: kubectl get nodes -o wide + environment: + KUBECONFIG: "{{ kubeconfig_path }}" + register: cluster_status + changed_when: false + + - name: Cluster setup complete debug: - msg: "ansible_user is {{ ansible_user }}" \ No newline at end of file + msg: + - "============================================" + - "Kubernetes Cluster Setup Complete!" + - "============================================" + - "" + - "{{ cluster_status.stdout_lines }}" + - "" + - "Kubeconfig has been copied to your local machine." + - "Run 'kubectl get nodes' to verify access." \ No newline at end of file diff --git a/scripts/ansible/setup_aiopslab.yml b/scripts/ansible/setup_aiopslab.yml new file mode 100644 index 00000000..83b034e8 --- /dev/null +++ b/scripts/ansible/setup_aiopslab.yml @@ -0,0 +1,194 @@ +--- +# Mode A: Set up AIOpsLab on the controller VM +# +# Required extra-vars (passed from deploy.py): +# dev_mode: bool - true=rsync local repo, false=git clone +# repo_url: string - git remote URL (clone mode only) +# repo_branch: string - git branch (clone mode only) +# local_repo_path: string - path on Ansible controller (dev mode only) +# repo_dest: string - destination on remote, e.g. /home/azureuser/AIOpsLab +# admin_username: string - k8s_user for config.yml +# +- hosts: control_nodes + become: true + vars: + user_home: "{{ user_home_base }}/{{ k8s_user }}" + kubeconfig_path: "{{ user_home }}/.kube/config" + poetry_bin: "{{ user_home }}/.local/bin/poetry" + tasks: + # ── Python 3.11 ────────────────────────────────────────────── + - name: Check if python3.11 is available + command: python3.11 --version + register: python311_check + ignore_errors: true + changed_when: false + + - name: Add deadsnakes PPA (Ubuntu) + apt_repository: + repo: ppa:deadsnakes/ppa + state: present + when: python311_check.rc != 0 + + - name: Install Python 3.11 + apt: + name: + - python3.11 + - python3.11-venv + - python3.11-dev + state: present + update_cache: true + when: python311_check.rc != 0 + + # ── Poetry ─────────────────────────────────────────────────── + - name: Check if Poetry is installed + stat: + path: "{{ poetry_bin }}" + register: poetry_stat + + - name: Install Poetry via official installer + become: true + become_user: "{{ k8s_user }}" + shell: | + curl -sSL https://install.python-poetry.org | python3 - + args: + creates: "{{ poetry_bin }}" + when: not poetry_stat.stat.exists + + # ── Helm ──────────────────────────────────────────────────── + - name: Check if Helm is installed + command: helm version --short + register: helm_check + ignore_errors: true + changed_when: false + + - name: Install Helm via get-helm-3 + shell: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + when: helm_check.rc != 0 + + # ── Docker group ───────────────────────────────────────────── + - name: Add user to docker group (required by VirtualizationFaultInjector) + user: + name: "{{ k8s_user }}" + groups: docker + append: true + + # ── Git ────────────────────────────────────────────────────── + - name: Ensure git is installed + apt: + name: git + state: present + + # ── Get code (clone mode) ──────────────────────────────────── + - name: Clone AIOpsLab repository + become: true + become_user: "{{ k8s_user }}" + git: + repo: "{{ repo_url }}" + dest: "{{ repo_dest }}" + version: "{{ repo_branch }}" + recursive: true + force: false + when: not dev_mode + + # ── Get code (dev mode — rsync) ───────────────────────────── + - name: Rsync local repo to controller + synchronize: + src: "{{ local_repo_path }}/" + dest: "{{ repo_dest }}/" + delete: true + rsync_opts: + - "--exclude=.venv/" + - "--exclude=__pycache__/" + - "--exclude=.terraform/" + - "--exclude=*.tfstate*" + - "--exclude=data/" + - "--exclude=.env" + - "--exclude=.git/" + - "--exclude=.claude/" + when: dev_mode + + - name: Check if aiopslab-applications has content (dev mode) + stat: + path: "{{ repo_dest }}/aiopslab-applications/README.md" + register: submodule_check + when: dev_mode + + - name: Clone aiopslab-applications submodule (dev mode, empty after rsync) + become: true + become_user: "{{ k8s_user }}" + git: + repo: "https://github.com/xlab-uiuc/aiopslab-applications.git" + dest: "{{ repo_dest }}/aiopslab-applications" + version: main + force: false + when: dev_mode and submodule_check.stat is defined and not submodule_check.stat.exists + + # ── Fix ownership ──────────────────────────────────────────── + - name: Set ownership on repo directory + file: + path: "{{ repo_dest }}" + owner: "{{ k8s_user }}" + group: "{{ k8s_user }}" + recurse: true + + # ── Generate config.yml ────────────────────────────────────── + - name: Generate aiopslab/config.yml + template: + src: templates/config.yml.j2 + dest: "{{ repo_dest }}/aiopslab/config.yml" + owner: "{{ k8s_user }}" + group: "{{ k8s_user }}" + mode: '0644' + + # ── Poetry setup ───────────────────────────────────────────── + - name: Run poetry env use python3.11 + become: true + become_user: "{{ k8s_user }}" + command: "{{ poetry_bin }} env use python3.11" + args: + chdir: "{{ repo_dest }}" + environment: + HOME: "{{ user_home }}" + + - name: Run poetry install + become: true + become_user: "{{ k8s_user }}" + command: "{{ poetry_bin }} install" + args: + chdir: "{{ repo_dest }}" + environment: + HOME: "{{ user_home }}" + async: 600 + poll: 15 + + # ── Verify cluster access ──────────────────────────────────── + - name: Verify kubectl can reach the cluster + become: true + become_user: "{{ k8s_user }}" + command: kubectl get nodes + environment: + KUBECONFIG: "{{ kubeconfig_path }}" + register: kubectl_nodes + changed_when: false + + - name: Display cluster status + debug: + msg: "{{ kubectl_nodes.stdout_lines }}" + + # ── Summary ────────────────────────────────────────────────── + - name: Print setup summary + debug: + msg: + - "============================================" + - "Mode A Setup Complete!" + - "============================================" + - "" + - "AIOpsLab location: {{ repo_dest }}" + - "Config: {{ repo_dest }}/aiopslab/config.yml" + - "Mode: {{ 'dev (rsync)' if dev_mode else 'clone (' + repo_url + ' @ ' + repo_branch + ')' }}" + - "" + - "To start AIOpsLab:" + - " ssh {{ k8s_user }}@{{ ansible_host }}" + - " cd {{ repo_dest }}" + - " eval $({{ poetry_bin }} env activate)" + - " python3 cli.py" diff --git a/scripts/ansible/setup_common.yml b/scripts/ansible/setup_common.yml index c6789bc9..76213989 100644 --- a/scripts/ansible/setup_common.yml +++ b/scripts/ansible/setup_common.yml @@ -44,6 +44,7 @@ - gnupg - lsb-release - socat + - conntrack - name: Install OpenSSL development libraries (the wrk build needs it) apt: diff --git a/scripts/ansible/templates/config.yml.j2 b/scripts/ansible/templates/config.yml.j2 new file mode 100644 index 00000000..3e18f349 --- /dev/null +++ b/scripts/ansible/templates/config.yml.j2 @@ -0,0 +1,7 @@ +# AIOpsLab configuration (generated by setup_aiopslab.yml) +k8s_host: localhost +k8s_user: {{ admin_username }} +ssh_key_path: ~/.ssh/id_rsa +data_dir: data +qualitative_eval: false +print_session: false diff --git a/scripts/terraform/.gitignore b/scripts/terraform/.gitignore index 3983ce08..60a23063 100644 --- a/scripts/terraform/.gitignore +++ b/scripts/terraform/.gitignore @@ -30,4 +30,9 @@ terraform.tfstate.lock.hcl # keys -*.pem \ No newline at end of file +*.pem +*.keys + +# Configuration files with sensitive data +config.yml +outputs.json \ No newline at end of file diff --git a/scripts/terraform/README.md b/scripts/terraform/README.md index ba6f45a0..4f719516 100644 --- a/scripts/terraform/README.md +++ b/scripts/terraform/README.md @@ -1,88 +1,510 @@ +# AIOpsLab Automated Deployment with Terraform + Ansible -## Setting up AIOpsLab using Terraform +**Fully automated deployment of production-ready Kubernetes clusters on Azure** -This guide outlines the steps for establishing a secure connection to your Azure environment using a VPN and then provisioning resources with Terraform. This will create a two-node Kubernetes cluster with one controller and one worker node. +> Tested on WSL2 (Ubuntu 22.04) + Windows 11 with Azure VMs (Ubuntu 22.04 LTS, amd64). Auto-install of tools (kubectl, helm, poetry) targets Linux/amd64. -**NOTE**: This will incur cloud costs as resources are created on Azure. +## Quick Start -**Prerequisites:** +**Mode B** (AIOpsLab on your laptop, remote kubectl): +```bash +python3 deploy.py --apply --resource-group --workers 2 --mode B +``` -- **Azure VPN Connection:** Set up a secure connection to your Azure environment using a VPN client. -- **Working directory:** AIOpsLab/scripts/terraform/ -- **Privileges:** The user should have the privileges to create resources (SSH keys, VM, network interface, network interface security group (if required), public IP, subnet, virtual network) in the selected resource group. -- **Azure CLI:** Follow the official [Microsoft documentation](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) for installing the Azure CLI for your operating system: -- **Install and initialize Terraform:** - - a. Download and install Terraform from the [official HashiCorp website](https://developer.hashicorp.com/terraform/install); - - b. To make the initial dependency selections that will initialize the dependency lock file, run: - - terraform init - -**Steps:** - -1. **Authenticate with Azure CLI** +**Mode A** (AIOpsLab on the controller VM): +```bash +python3 deploy.py --apply --resource-group --workers 2 --mode A +# With --dev to rsync local code instead of git clone: +python3 deploy.py --apply --resource-group --workers 2 --mode A --dev +``` - Open a terminal window and run the following command to log in to Azure: +**Destroy** when done: +```bash +python3 deploy.py --destroy --resource-group +``` - ```shell - az login - ``` +The script handles VM provisioning, K8s cluster setup, and AIOpsLab configuration. -2. **Select subscription** +> **Tip**: Add `--allowed-ips CorpNetPublic` (or a CIDR) to restrict SSH/K8s API access. Default is open to all (`*`). - The output of az login will have a list of subscriptions you have access to. Copy the value in the "id" column of the subscription you want to work with: - - ```shell - az account set --subscription "" - ``` -3. **Verify the plan** +--- - *Note*: The SSH port of the VMs is open to the public. Please update the NSG resource in the main.tf file to restrict incoming traffic. Use the source_address_prefix attribute to specify allowed sources (e.g., source_address_prefix = "CorpNetPublic"). +## ✨ What's New (v2.0) - Create and save the plan by passing the required variables +- ✅ **Fully Automated**: One command deploys everything +- ✅ **Dynamic Scaling**: Support for 1-N worker nodes +- ✅ **Ansible Integration**: Production-ready K8s setup +- ✅ **Smart Inventory**: Auto-generates Ansible inventory from Terraform +- ✅ **SSH Verification**: Waits for connectivity before proceeding +- ✅ **Graceful Destroy**: Safe teardown with confirmation +- ✅ **Better Outputs**: Structured VM information for automation - a) _resource_group_name_ (rg): the resource group where the resources would be created. +--- - b) _resource_prefix_name_ (prefix): a prefix for all the resources created using the Terraform script. +## 📋 Prerequisites - ```shell - terraform plan -out main.tfplan -var " resource_group_name=" -var "resource_name_prefix=" - ``` -5. **Apply the saved plan** +### 1. Software Requirements - Note: Verify the plan from the previous step before applying it. +| Tool | Version | Installation | +|------|---------|--------------| +| Python | 3.11+ | [python.org](https://python.org) | +| Terraform | 1.6+ | [Install](https://developer.hashicorp.com/terraform/install) | +| Ansible | Latest | [Install](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) | +| Azure CLI | Latest | [Install](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) | - ```shell - terraform apply "main.tfplan" - ``` - -6. **Setup AIOpsLab** - Run the below script to setup AIOpsLab on the newly provisioned resources +#### Quick Install (Ubuntu/Debian) +```bash +# Ansible +sudo apt update +sudo apt install software-properties-common +sudo add-apt-repository --yes --update ppa:ansible/ansible +sudo apt install ansible -y - ```shell - python deploy.py - ``` - On successful execution, the script outputs the SSH commands to login to the controller and worker node. Please save it. +# Python dependencies +pip install pyyaml +``` - Please activate virtual environment before running any scripts and add the path to `wrk2` executable to PATH: +### 2. Azure Setup +```bash +# Login to Azure +az login + +# Set subscription +az account set --subscription "" + +# Create resource group (if needed) +az group create --name aiopslab-rg --location eastus + +# Generate SSH key (if needed) +ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa +``` + +--- + +## 🎯 Usage + +### Option 1: Automated Deployment with deploy.py + +Deploy with default settings (2 workers, Standard_B2s): + +```bash +python deploy.py --apply +``` + +### Custom Deployment + +Specify worker count and VM size: + +```bash +python deploy.py --apply \ + --workers 5 \ + --vm-size Standard_D8s_v3 \ + --resource-group my-rg \ + --prefix myaiops \ + --ssh-key ~/.ssh/custom_key.pub +``` + +### Available Options + +``` +--plan Dry-run: show what would be created +--apply Deploy infrastructure and setup cluster +--destroy Destroy all infrastructure +--setup-only Re-run AIOpsLab setup without reprovisioning (uses + existing Terraform state). Useful for iterating on + code or config changes. +--workers N Number of worker nodes (default: 2) +--vm-size SIZE Azure VM size (default: Standard_B2s) +--resource-group RG Azure resource group (default: aiopslab-rg) +--prefix PREFIX Resource name prefix (default: aiopslab) +--ssh-key PATH SSH public key path (default: ~/.ssh/id_rsa.pub) +--allowed-ips ADDR NSG source address for SSH + K8s API. Use '*' for + open (default), a CIDR, or an Azure service tag + like 'CorpNetPublic'. +--mode {A,B} A: AIOpsLab on controller VM. B: AIOpsLab on + laptop with remote kubectl (default: B). +--dev Mode A only: rsync local repo to controller + instead of git clone. +--debug Enable debug logging +``` + +### Destroy Infrastructure + +```bash +python deploy.py --destroy \ + --resource-group aiopslab-rg \ + --ssh-key ~/.ssh/id_rsa.pub +``` + +You'll be prompted to confirm before deletion. + +--- + +### Option 2: Manual Step-by-Step Deployment + +For more control or debugging, you can run each step manually: + +#### Step 1: Provision Azure VMs with Terraform + +```bash +cd scripts/terraform +terraform init +terraform plan -var="resource_group_name=" -var="worker_vm_count=3" +terraform apply -var="resource_group_name=" -var="worker_vm_count=3" +``` + +#### Step 2: Generate Ansible Inventory + +```bash +python generate_inventory.py +# This creates ../ansible/inventory.yml with VM IPs and SSH config +``` + +#### Step 3: Run Ansible Playbooks + +```bash +cd ../ansible + +# Install Docker, Kubernetes packages on all nodes +ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i inventory.yml setup_common.yml + +# Initialize K8s cluster and join workers +ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i inventory.yml remote_setup_controller_worker.yml +``` + +#### Step 4: Verify Cluster + +```bash +# The playbook copies kubeconfig to your ~/.kube/config automatically +kubectl get nodes +``` + +#### Destroy Manually + +```bash +cd scripts/terraform +terraform destroy -var="resource_group_name=" +``` + +--- + +## 🖥️ Mode A vs Mode B Deployment + +### Mode A: AIOpsLab Inside Cluster (Recommended for full functionality) + +Run AIOpsLab directly on the controller VM. The setup is fully automated: + +```bash +# Clone mode (default): git clones the repo on the controller +python3 deploy.py --apply --resource-group --workers 2 --mode A + +# Dev mode: rsync your local repo to the controller instead of cloning +python3 deploy.py --apply --resource-group --workers 2 --mode A --dev +``` + +The `--mode A` setup (`scripts/ansible/setup_aiopslab.yml`) automatically: +- Installs Python 3.11, Poetry, Helm, and git on the controller +- Adds the user to the `docker` group (required by VirtualizationFaultInjector) +- Clones the repo with submodules (clone mode) or rsyncs local code (dev mode) +- Generates `aiopslab/config.yml` with `k8s_host: localhost` +- Runs `poetry env use python3.11 && poetry install` +- Verifies cluster access with `kubectl get nodes` + +After deploy, SSH to the controller to run experiments: +```bash +ssh -i ~/.ssh/id_rsa azureuser@ +cd ~/AIOpsLab +eval $(poetry env activate) +python3 cli.py +``` + +To iterate on code changes without reprovisioning VMs: +```bash +python3 deploy.py --setup-only --mode A --dev +``` + +**Pros**: All fault injectors work (Docker is on the same machine), no Docker required locally +**Cons**: Must SSH to controller to run experiments + +### Mode B: AIOpsLab on Your Laptop (Convenient for development) + +`deploy.py --mode B` (the default) handles everything automatically: + +```bash +python3 deploy.py --apply --resource-group --workers 2 --mode B +``` + +This automatically: +- Installs kubectl, helm, and poetry if missing +- Verifies kubeconfig and cluster connectivity +- Generates `aiopslab/config.yml` with the correct controller IP +- Runs `poetry env use python3.11 && poetry install` +- Prints a summary table showing what succeeded and what needs manual action + +After deploy, just: +```bash +eval $(poetry env activate) +python3 cli.py +``` + +**Pros**: Use local IDE, no SSH needed for running experiments +**Cons**: Some fault injectors (e.g., VirtualizationFaultInjector) require local Docker + +**Note**: If you see Docker connection errors in Mode B, either install Docker on your laptop or switch to Mode A. + +**Note**: If using a git worktree in WSL, `git submodule update` may fail due to cross-platform path issues. Run it from Git Bash instead. + +--- + +## 📊 VM Sizing Guide + +| VM Size | vCPUs | RAM | Use Case | Cost/Month* | +|---------|-------|-----|----------|-------------| +| Standard_B2s | 2 | 4 GB | Dev/Test | ~$30 | +| Standard_D4s_v3 | 4 | 16 GB | Small Prod | ~$120 | +| Standard_D8s_v3 | 8 | 32 GB | Medium Prod | ~$240 | +| Standard_D16s_v3 | 16 | 64 GB | Large Prod | ~$480 | + +*Approximate costs for East US region + +--- + +## 🔧 What Gets Deployed + +### Infrastructure (Terraform) +- 1 Controller VM (Kubernetes control plane) +- N Worker VMs (configurable, default 2) +- Virtual Network & Subnet (10.0.0.0/16) +- Network Security Group (SSH access) +- Public IPs for all VMs +- Network Interfaces + +### Software Stack (Ansible) +- Docker CE + cri-dockerd +- Kubernetes v1.31 (kubeadm, kubelet, kubectl) +- Flannel CNI plugin +- Fully configured K8s cluster + +--- + +## Deployment Workflow + +``` +1. Terraform Init → Initialize providers +2. Terraform Plan → Create execution plan +3. Terraform Apply → Provision VMs on Azure +4. Get Outputs → Retrieve VM IPs and config +5. Generate Inventory → Create Ansible inventory.yml +6. Wait for SSH → Ensure VMs are accessible +7. Run Ansible → Install Docker, K8s packages +8. Setup Cluster → Initialize K8s, join workers +9. AIOpsLab Setup → Mode-dependent: + Mode A → Run setup_aiopslab.yml on controller + Mode B → Install tools locally, generate config.yml +``` + +**Total Time:** 15-25 minutes + +--- + +## Post-Deployment + +Both modes print a summary table at the end showing what succeeded and what needs manual action. + +### Mode A + +AIOpsLab is already installed on the controller. SSH in and start: +```bash +ssh -i ~/.ssh/id_rsa azureuser@ +cd ~/AIOpsLab +eval $(poetry env activate) +python3 cli.py +``` + +### Mode B + +AIOpsLab is configured locally. Start directly: +```bash +eval $(poetry env activate) +python3 cli.py +``` + +### Verify Cluster + +```bash +kubectl get nodes +# NAME STATUS ROLES AGE VERSION +# aiopslab-controller Ready control-plane 5m v1.31.x +# aiopslab-worker-1 Ready 3m v1.31.x +# aiopslab-worker-2 Ready 3m v1.31.x +``` + +--- + +## 🐛 Troubleshooting + +### SSH Connection Timeout + +**Symptoms**: Deployment hangs at "Waiting for SSH" + +**Solutions**: +1. Check Network Security Group allows your IP +2. Verify SSH key path is correct +3. Wait longer (VMs may be slow to boot) + +```bash +# Test SSH manually +ssh -i ~/.ssh/id_rsa -v azureuser@ +``` + +### Ansible Playbook Fails + +**Solutions**: Re-run Ansible manually: + +```bash +cd scripts/ansible + +# Run common setup +ansible-playbook -i inventory.yml setup_common.yml + +# Run cluster setup +ansible-playbook -i inventory.yml remote_setup_controller_worker.yml +``` + +### Nodes Not Ready + +**Solution**: Check Flannel CNI: + +```bash +kubectl get pods -n kube-system | grep flannel + +# If not running, reapply: +kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml +``` + +### kubeadm init fails with "conntrack not found" + +**Cause**: Missing conntrack package (required for kube-proxy) + +**Solution**: The setup_common.yml playbook should install this. If running manually: + +```bash +sudo apt install conntrack -y +``` + +### kubectl from laptop shows certificate error + +**Symptom**: `Unable to connect to the server: x509: certificate is valid for X, not Y` + +**Cause**: K8s API server certificate doesn't include the public IP + +**Solution**: The Ansible playbook automatically adds `--apiserver-cert-extra-sans` with the public IP. If you need to reinitialize: + +```bash +# On controller, reset and reinit with SANs +sudo kubeadm reset -f +sudo kubeadm init \ + --pod-network-cidr=10.244.0.0/16 \ + --cri-socket unix:///var/run/cri-dockerd.sock \ + --apiserver-advertise-address= \ + --apiserver-cert-extra-sans=, +``` + +### Helm chart not found error + +**Symptom**: `FileNotFoundError: Helm chart not found at: ...` + +**Solution**: Clone with submodules: + +```bash +git submodule update --init --recursive +``` + +### Docker connection error in Mode B + +**Symptom**: `Error while fetching server API version: HTTPConnection.request() got an unexpected keyword argument 'chunked'` + +**Cause**: Some fault injectors try to connect to local Docker daemon + +**Solution**: +1. Install Docker Desktop on your laptop, OR +2. Use Mode A (run AIOpsLab on controller VM) + +--- + +## 🔐 Security Notes + +### Default Behavior (Secure) + +The deployment script is **secure by default**: +- Automatically adds SSH host keys via ssh-keyscan before running Ansible +- Host key verification is always enabled (no option to disable) + +### Quick Security Checklist + +- [ ] **NSG Rules:** SSH is open to 0.0.0.0/0 by default - restrict it! + ```bash + # Use --allowed-ips to restrict access (e.g. Microsoft CorpNet) + python deploy.py --apply --workers 2 --allowed-ips CorpNetPublic + + # Or add custom IP after deployment + az network nsg rule create -g aiopslab-rg --nsg-name aiopslab-nsg \ + --name SSH-MyIP --priority 100 --protocol TCP \ + --source-address-prefixes "YOUR_IP/32" --destination-port-ranges 22 + ``` + +- [ ] **SSH Keys:** Use 4096-bit RSA or Ed25519 with passphrases +- [ ] **Production:** Consider Azure Bastion for secure access +- [ ] **Environments:** Use separate resource groups for prod/dev/test + +--- + +## 💰 Cost Management + +### Estimated Costs + +**Small Dev Setup** (2 workers, B2s): ~$90/month +**Medium Prod** (3 workers, D4s_v3): ~$480/month +**Large Prod** (5 workers, D8s_v3): ~$1,440/month + +### Save Money + +1. Destroy when not in use: `python deploy.py --destroy` +2. Use B-series VMs for dev/test +3. Deallocate VMs instead of deleting: + ```bash + az vm deallocate --resource-group aiopslab-rg --name aiopslab-controller ``` - azureuser@kubeController:~/AIOpsLab$ source .venv/bin/activate - (.venv) azureuser@kubeController:~/AIOpsLab/clients$ export PATH="$PATH:/home/azureuser/AIOpsLab/TargetMicroservices/wrk2" - ``` -**How to destroy the resources using Terraform?** +--- + +## Files + +| Directory | Contents | +|-----------|----------| +| `scripts/terraform/` | `deploy.py` (main entry point), Terraform configs (`main.tf`, `variables.tf`, etc.), `generate_inventory.py` | +| `scripts/ansible/` | Playbooks for K8s setup (`setup_common.yml`, `remote_setup_controller_worker.yml`) and Mode A AIOpsLab setup (`setup_aiopslab.yml`), Jinja2 templates, inventory | + +--- + +## 🤝 Contributing + +Found a bug or have a suggestion? Please open an issue or submit a PR! + +Areas for improvement: +- Support for AWS, GCP +- Automated monitoring setup +- Cost optimization features +- Integration tests + +--- -1. Before deleting the resources, run the below command to create and save a plan (use the values previous used for resource_group_name and resource_name_prefix) - - ```shell - terraform plan -destroy -out main.destroy.tfplan -var "resource_group_name=" -var "resource_name_prefix=" - ``` +## 📄 License -2. Once the plan is verified, remove the resources using the below command: +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. - ```shell - terraform destroy main.destroy.tfplan - ``` +--- +**Need Help?** Open an issue on GitHub. diff --git a/scripts/terraform/data.tf b/scripts/terraform/data.tf deleted file mode 100644 index 641213c6..00000000 --- a/scripts/terraform/data.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "azurerm_resource_group" "rg" { - name = var.resource_group_name -} \ No newline at end of file diff --git a/scripts/terraform/deploy.py b/scripts/terraform/deploy.py index 069d8826..84c0b5ed 100644 --- a/scripts/terraform/deploy.py +++ b/scripts/terraform/deploy.py @@ -1,239 +1,1020 @@ -import subprocess -import os -import logging - -REPO = "/home/azureuser/AIOpsLab" - -# Configure logging -logging.basicConfig(level=logging.INFO) # Change to DEBUG for more detailed logs -logger = logging.getLogger(__name__) - - -def run_command(command, capture_output=False): - """Runs a shell command and handles errors.""" - try: - logger.debug(f"Running command: {' '.join(command)}") - result = subprocess.run( - command, capture_output=capture_output, text=True, check=True - ) - if capture_output: - logger.debug(f"Command output: {result.stdout.strip()}") - return result.stdout.strip() if capture_output else None - except subprocess.CalledProcessError as e: - logger.error( - f"Command '{' '.join(command)}' failed with error: {e.stderr.strip() if e.stderr else str(e)}" - ) - if capture_output: - return None - - -def setup_aiopslab(): - try: - run_command(["terraform", "plan", "-out", "main.tfplan"]) - output = run_command(["terraform", "apply", "main.tfplan"], capture_output=True) - if output: - logger.debug(f"Terraform apply output: {output}") - except Exception as e: - logger.error(f"Error in setup_aiopslab: {str(e)}") - - -def destroy_aiopslab(): - pass - - -def get_terraform_output(output_name): - """Retrieve Terraform output.""" - try: - result = run_command( - ["terraform", "output", "-raw", output_name], capture_output=True - ) - return result - except Exception as e: - logger.error(f"Failed to get Terraform output for {output_name}: {str(e)}") - return None - - -def save_private_key(key_data, filename): - """Save the private key to a file.""" - try: - with open(filename, "w") as key_file: - key_file.write(key_data) - os.chmod(filename, 0o600) - logger.info(f"Private key saved to {filename}") - except Exception as e: - logger.error(f"Failed to save private key to {filename}: {str(e)}") - - -def copy_and_execute_script(username, private_key, public_ip, script): - """Copy and execute the shell script on the remote VM.""" - remote_path = f"{username}@{public_ip}:/home/{username}" - try: - # Copy the shell script to the remote VM - run_command( - [ - "scp", - "-o", - "StrictHostKeyChecking=no", - "-i", - private_key, - script, - remote_path, - ] - ) - - # Execute the shell script on the remote VM - run_command( - [ - "ssh", - "-i", - private_key, - f"{username}@{public_ip}", - f"bash /home/{username}/{os.path.basename(script)}", - ] - ) - except Exception as e: - logger.error(f"Failed to copy or execute script on {public_ip}: {str(e)}") - - -def get_kubeadm_join_remote(username, private_key, public_ip): - """SSH into the remote machine and generate the kubeadm join command.""" - generate_join_command = [ - "ssh", - "-i", - private_key, - f"{username}@{public_ip}", - "sudo kubeadm token create --print-join-command", - ] - try: - print(generate_join_command) - result = run_command(generate_join_command, capture_output=True) - return result - except Exception as e: - logger.error( - f"Failed to retrieve kubeadm join command from {public_ip}: {str(e)}" - ) - return None - - -def run_kubeadm_join_on_worker(worker_username, private_key, worker_ip, join_command): - """SSH into the worker and run the kubeadm join command.""" - ssh_command = [ - "ssh", - "-i", - private_key, - f"{worker_username}@{worker_ip}", - f"sudo {join_command} --cri-socket /var/run/cri-dockerd.sock", - ] - try: - run_command(ssh_command) - except Exception as e: - logger.error(f"Failed to run kubeadm join on {worker_ip}: {str(e)}") - - -def add_ssh_key(host, port=22): - """Runs ssh-keyscan on a given host and appends the key to known_hosts.""" - try: - # Build the ssh-keyscan command - keyscan_cmd = ["ssh-keyscan", "-H", "-p", str(port), host] - - # Run the ssh-keyscan command and capture output - result = run_command(keyscan_cmd, capture_output=True) - - # Append the output (host key) to known_hosts - with open(os.path.expanduser("~/.ssh/known_hosts"), "a") as known_hosts_file: - known_hosts_file.write(result) - logger.info(f"SSH key for {host} added to known_hosts.") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to fetch SSH key for {host}: {e}") - except Exception as ex: - logger.error(f"An error occurred while adding SSH key for {host}: {ex}") - - -def deploy_prometheus(username, private_key_file_1, public_ip_1): - """Deploy Prometheus on the worker node.""" - try: - run_command( - [ - "ssh", - "-o", - "StrictHostKeyChecking=no", - "-i", - private_key_file_1, - f"{username}@{public_ip_1}", - f"bash {REPO}/scripts/setup.sh kubeworker1", - ] - ) - except Exception as e: - logger.error(f"Failed to deploy Prometheus: {str(e)}") - - -def main(): - # Retrieve private keys and public IPs for both VMs - private_key_1 = get_terraform_output("key_data_1") - private_key_2 = get_terraform_output("key_data_2") - public_ip_1 = get_terraform_output("public_ip_address_1") - public_ip_2 = get_terraform_output("public_ip_address_2") - username = "azureuser" # TODO: read from variables file - - if not private_key_1 or not private_key_2 or not public_ip_1 or not public_ip_2: - logger.error("Failed to retrieve required Terraform outputs.") - return - - # Save the private keys to files - private_key_file_1 = "vm_1_private_key.pem" - private_key_file_2 = "vm_2_private_key.pem" - save_private_key(private_key_1, private_key_file_1) - save_private_key(private_key_2, private_key_file_2) - - # Path to the shell script - kubeadm_shell_script = f"./scripts/kubeadm.sh" - controller_shell_script = f"./scripts/kube_controller.sh" - setup_aiopslab_script = f"./scripts/setup_aiopslab.sh" - prom_worker_setup_script = f"./scripts/prom_on_worker.sh" - - # Install kubeadm on all the VMs - copy_and_execute_script( - username, private_key_file_1, public_ip_1, kubeadm_shell_script - ) - copy_and_execute_script( - username, private_key_file_2, public_ip_2, kubeadm_shell_script - ) - - # Setup kube controller - copy_and_execute_script( - username, private_key_file_1, public_ip_1, controller_shell_script - ) - - # Get join command and run on the worker - join_command = get_kubeadm_join_remote(username, private_key_file_1, public_ip_1) - - if join_command: - logger.info(f"Join command retrieved: {join_command}") - run_kubeadm_join_on_worker( - username, private_key_file_2, public_ip_2, join_command - ) - - # Setup aiopslab - copy_and_execute_script( - username, private_key_file_1, public_ip_1, setup_aiopslab_script - ) - - # Deploy Prometheus on the worker node) - copy_and_execute_script( - username, private_key_file_2, public_ip_2, prom_worker_setup_script - ) - deploy_prometheus(username, private_key_file_1, public_ip_1) - - # print public ip of controller and worker and give ssh command to access it - logger.info(f"Controller Public IP: {public_ip_1}") - logger.info(f"Worker Public IP: {public_ip_2}") - logger.info( - f"SSH command to access controller: ssh -i {private_key_file_1} {username}@{public_ip_1}" - ) - logger.info( - f"SSH command to access worker: ssh -i {private_key_file_2} {username}@{public_ip_2}" - ) - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +""" +AIOpsLab Automated Deployment Script +Provisions Azure VMs with Terraform and sets up Kubernetes cluster with Ansible. + +Usage: + python deploy.py --plan --workers 3 --vm-size Standard_D4s_v3 + python deploy.py --apply --workers 3 --vm-size Standard_D4s_v3 + python deploy.py --destroy + python deploy.py --help +""" + +import subprocess +import shutil +import sys +import os +import re +import time +import argparse +import logging +import json +from pathlib import Path + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + + +class AIOpsLabDeployer: + """Main deployment orchestrator for AIOpsLab.""" + + def __init__(self, terraform_dir=None, ansible_dir=None): + """Initialize deployer with directory paths.""" + self.script_dir = Path(__file__).parent + self.terraform_dir = Path(terraform_dir) if terraform_dir else self.script_dir + self.ansible_dir = Path(ansible_dir) if ansible_dir else self.script_dir.parent / "ansible" + self.inventory_path = self.ansible_dir / "inventory.yml" + + def run_command(self, command, capture_output=False, cwd=None, check=True): + """Execute a shell command.""" + try: + logger.debug(f"Running: {' '.join(command)}") + result = subprocess.run( + command, + capture_output=capture_output, + text=True, + check=check, + cwd=cwd or self.terraform_dir + ) + if capture_output: + return result.stdout.strip() + return result.returncode == 0 + except FileNotFoundError: + logger.error(f"Command not found: {command[0]}") + logger.error(f"Please ensure '{command[0]}' is installed and in your PATH") + if check: + raise + return False + except subprocess.CalledProcessError as e: + logger.error(f"Command failed: {' '.join(command)}") + if e.stderr: + logger.error(f"Error: {e.stderr.strip()}") + if check: + raise + return False + + def terraform_init(self): + """Initialize Terraform.""" + logger.info("Initializing Terraform...") + return self.run_command(["terraform", "init"]) + + def terraform_plan(self, worker_count, vm_size, resource_group, prefix, ssh_key_path, allowed_ips="*"): + """Create Terraform plan.""" + logger.info("Creating Terraform plan...") + + plan_vars = [ + "-out=main.tfplan", + f"-var=worker_vm_count={worker_count}", + f"-var=vm_size={vm_size}", + f"-var=resource_group_name={resource_group}", + f"-var=prefix={prefix}", + f"-var=ssh_public_key_path={ssh_key_path}", + f"-var=nsg_allowed_source={allowed_ips}" + ] + + return self.run_command(["terraform", "plan"] + plan_vars) + + def terraform_plan_only(self, worker_count, vm_size, resource_group, prefix, ssh_key_path, allowed_ips="*"): + """Show Terraform plan without saving (dry-run).""" + logger.info("Creating Terraform plan (dry-run)...") + + plan_vars = [ + f"-var=worker_vm_count={worker_count}", + f"-var=vm_size={vm_size}", + f"-var=resource_group_name={resource_group}", + f"-var=prefix={prefix}", + f"-var=ssh_public_key_path={ssh_key_path}", + f"-var=nsg_allowed_source={allowed_ips}" + ] + + return self.run_command(["terraform", "plan"] + plan_vars) + + def terraform_apply(self): + """Apply Terraform plan.""" + logger.info("Applying Terraform plan...") + return self.run_command(["terraform", "apply", "main.tfplan"]) + + def terraform_destroy(self, resource_group, prefix, ssh_key_path, allowed_ips="*"): + """Destroy Terraform-managed infrastructure.""" + logger.info("Destroying Terraform infrastructure...") + + # Create destroy plan + destroy_vars = [ + "-destroy", + "-out=main.destroy.tfplan", + f"-var=resource_group_name={resource_group}", + f"-var=prefix={prefix}", + f"-var=ssh_public_key_path={ssh_key_path}", + f"-var=nsg_allowed_source={allowed_ips}" + ] + + logger.info("Creating destroy plan...") + if not self.run_command(["terraform", "plan"] + destroy_vars): + return False + + logger.info("Applying destroy plan...") + return self.run_command(["terraform", "apply", "main.destroy.tfplan"]) + + def get_terraform_outputs(self): + """Retrieve Terraform outputs as JSON.""" + logger.info("Retrieving Terraform outputs...") + output = self.run_command( + ["terraform", "output", "-json"], + capture_output=True + ) + + if not output: + logger.error("Failed to retrieve Terraform outputs") + return None + + try: + return json.loads(output) + except json.JSONDecodeError as e: + logger.error(f"Failed to parse Terraform outputs: {e}") + return None + + def generate_ansible_inventory(self): + """Generate Ansible inventory from Terraform outputs.""" + logger.info("Generating Ansible inventory...") + + inventory_script = self.terraform_dir / "generate_inventory.py" + if not inventory_script.exists(): + logger.error(f"Inventory generator not found: {inventory_script}") + return False + + return self.run_command([sys.executable, str(inventory_script)]) + + def wait_for_ssh(self, host, port=22, timeout=300, interval=10): + """Wait for SSH to become available on a host.""" + logger.info(f"Waiting for SSH on {host}...") + + import socket + start_time = time.time() + + while time.time() - start_time < timeout: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + try: + result = sock.connect_ex((host, port)) + finally: + sock.close() + + if result == 0: + logger.info(f"SSH available on {host}") + return True + + except socket.error: + pass + + time.sleep(interval) + + logger.error(f"SSH timeout on {host} after {timeout}s") + return False + + def wait_for_all_hosts(self, outputs): + """Wait for SSH on all hosts.""" + controller = outputs['controller']['value'] + workers = outputs['workers']['value'] + + logger.info("Waiting for SSH on all hosts...") + + # Wait for controller + if not self.wait_for_ssh(controller['public_ip']): + logger.error("Controller SSH not available") + return False + + # Wait for all workers + for idx, worker in enumerate(workers, start=1): + if not self.wait_for_ssh(worker['public_ip']): + logger.error(f"Worker {idx} SSH not available") + return False + + logger.info("All hosts are SSH-ready") + return True + + def add_ssh_host_keys(self, outputs): + """Add SSH host keys to known_hosts to avoid verification prompts.""" + controller = outputs['controller']['value'] + workers = outputs['workers']['value'] + + logger.info("Adding SSH host keys to known_hosts...") + + all_hosts = [controller['public_ip']] + [w['public_ip'] for w in workers] + + for host in all_hosts: + try: + # Run ssh-keyscan to get host keys + result = subprocess.run( + ['ssh-keyscan', '-H', host], + capture_output=True, + text=True, + timeout=30 + ) + + if result.returncode == 0 and result.stdout: + # Append to known_hosts + known_hosts_path = Path.home() / '.ssh' / 'known_hosts' + known_hosts_path.parent.mkdir(parents=True, exist_ok=True) + + with open(known_hosts_path, 'a') as f: + f.write(result.stdout) + + logger.debug(f"Added SSH host key for {host}") + else: + logger.warning(f"Could not get SSH host key for {host}") + + except subprocess.TimeoutExpired: + logger.warning(f"ssh-keyscan timeout for {host}") + except Exception as e: + logger.warning(f"Failed to add SSH host key for {host}: {e}") + + logger.info("SSH host keys added") + return True + + def run_ansible_playbook(self, playbook_name, extra_args=None): + """Run an Ansible playbook.""" + playbook_path = self.ansible_dir / playbook_name + + if not playbook_path.exists(): + logger.error(f"Playbook not found: {playbook_path}") + return False + + if not self.inventory_path.exists(): + logger.error(f"Inventory not found: {self.inventory_path}") + return False + + logger.info(f"Running Ansible playbook: {playbook_name}") + + command = [ + "ansible-playbook", + "-i", str(self.inventory_path), + str(playbook_path) + ] + + if extra_args: + command.extend(extra_args) + + env = os.environ.copy() + + try: + result = subprocess.run( + command, + cwd=self.ansible_dir, + env=env, + check=True + ) + return result.returncode == 0 + except subprocess.CalledProcessError as e: + logger.error(f"Ansible playbook failed with exit code {e.returncode}") + return False + + def _install_tool(self, name, install_cmd): + """Try to install a tool via a shell command. Returns True if tool is on PATH after.""" + logger.info(f"Installing {name}...") + try: + subprocess.run(install_cmd, shell=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError) as e: + logger.warning(f"Install failed for {name}: {e}") + return False + # Refresh PATH check (poetry installs to ~/.local/bin) + if name == "poetry": + local_bin = str(Path.home() / ".local" / "bin") + if local_bin not in os.environ.get("PATH", ""): + os.environ["PATH"] = f"{local_bin}:{os.environ.get('PATH', '')}" + return shutil.which(name) is not None + + def _find_python311_plus(self): + """Find a Python >= 3.11 binary. Returns (command_name, version_string) or (None, None).""" + for candidate in ["python3.11", "python3.12", "python3.13", "python3"]: + path = shutil.which(candidate) + if not path: + continue + try: + proc = subprocess.run( + [path, "--version"], capture_output=True, text=True, check=True + ) + ver = (proc.stdout or proc.stderr or "").strip() + match = re.search(r"Python\s+(\d+)\.(\d+)", ver) + if match and int(match.group(1)) == 3 and int(match.group(2)) >= 11: + return candidate, ver + except Exception: + continue + return None, None + + def setup_aiopslab_mode_b(self, outputs, ssh_key_path): + """Configure AIOpsLab for Mode B (laptop with remote kubectl). + + Returns: + bool: True if all critical steps succeeded, False otherwise. + """ + controller = outputs['controller']['value'] + controller_ip = controller['public_ip'] + admin_username = controller['username'] + repo_root = self.script_dir.parent.parent + kubeconfig_path = Path.home() / ".kube" / "config" + + # Derive private key path (strip .pub if needed) + ssh_private_key = ssh_key_path + if ssh_private_key.endswith('.pub'): + ssh_private_key = ssh_private_key[:-4] + + # Track results: (step_name, status, detail) + results = [] + + # --- kubectl --- + logger.info("Checking kubectl...") + if not shutil.which("kubectl"): + installed = self._install_tool("kubectl", + 'curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"' + ' && curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"' + ' && echo "$(cat kubectl.sha256) kubectl" | sha256sum --check' + ' && sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl' + ' && rm -f kubectl kubectl.sha256') + if installed: + results.append(("kubectl", "INSTALLED", shutil.which("kubectl"))) + else: + results.append(("kubectl", "FAILED", "Auto-install failed, install manually")) + else: + results.append(("kubectl", "OK", shutil.which("kubectl"))) + + # --- helm --- + logger.info("Checking helm...") + if not shutil.which("helm"): + # Pipe-to-bash is the official Helm install method; no checksum alternative provided + installed = self._install_tool("helm", + 'curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash') + if installed: + results.append(("helm", "INSTALLED", shutil.which("helm"))) + else: + results.append(("helm", "FAILED", "Auto-install failed, install manually")) + else: + results.append(("helm", "OK", shutil.which("helm"))) + + # --- kubeconfig --- + logger.info("Checking kubeconfig...") + kubeconfig_ok = kubeconfig_path.exists() + if kubeconfig_ok: + results.append(("kubeconfig", "OK", str(kubeconfig_path))) + else: + results.append(("kubeconfig", "MISSING", "Ansible should have copied it to ~/.kube/config")) + + # --- cluster access --- + has_kubectl = shutil.which("kubectl") is not None + if has_kubectl and kubeconfig_ok: + logger.info("Verifying kubectl connectivity...") + try: + self.run_command(["kubectl", "get", "nodes"], check=True) + results.append(("cluster access", "OK", "kubectl get nodes succeeded")) + except Exception: + results.append(("cluster access", "FAILED", "Check NSG rules and kubeconfig server IP")) + else: + results.append(("cluster access", "SKIPPED", + "No kubectl" if not has_kubectl else "No kubeconfig")) + + # --- config.yml --- + logger.info("Generating aiopslab/config.yml...") + config_example = repo_root / "aiopslab" / "config.yml.example" + config_dest = repo_root / "aiopslab" / "config.yml" + + if not config_example.exists() and not config_dest.exists(): + results.append(("config.yml", "FAILED", "config.yml.example not found")) + elif config_dest.exists(): + content = config_dest.read_text() + content = re.sub(r'k8s_host:.*', f'k8s_host: {controller_ip}', content) + content = re.sub(r'k8s_user:.*', f'k8s_user: {admin_username}', content) + content = re.sub(r'ssh_key_path:.*', f'ssh_key_path: {ssh_private_key}', content) + config_dest.write_text(content) + results.append(("config.yml", "UPDATED", f"k8s_host={controller_ip}")) + else: + content = config_example.read_text() + content = content.replace("k8s_host: control_node_hostname", f"k8s_host: {controller_ip}") + content = content.replace("k8s_user: your_username", f"k8s_user: {admin_username}") + content = content.replace("ssh_key_path: ~/.ssh/id_rsa", f"ssh_key_path: {ssh_private_key}") + config_dest.write_text(content) + results.append(("config.yml", "OK", f"Generated with k8s_host={controller_ip}")) + + # --- git submodules --- + logger.info("Checking git submodules...") + submodules_dir = repo_root / "aiopslab-applications" + has_content = (submodules_dir.exists() + and any(f for f in submodules_dir.iterdir() if f.name != '.git')) + if has_content: + results.append(("git submodules", "OK", "aiopslab-applications present")) + else: + try: + self.run_command( + ["git", "submodule", "update", "--init", "--recursive"], + cwd=str(repo_root), check=True) + results.append(("git submodules", "OK", "Initialized successfully")) + except Exception: + git_path = repo_root / ".git" + is_worktree = git_path.is_file() + if is_worktree: + results.append(("git submodules", "FAILED", + "Worktree detected -- run from Git Bash, not WSL")) + else: + results.append(("git submodules", "FAILED", + "Run: git submodule update --init --recursive")) + + # --- poetry --- + logger.info("Checking poetry...") + if not shutil.which("poetry"): + installed = self._install_tool("poetry", + 'curl -sSL https://install.python-poetry.org | python3 -') + if installed: + results.append(("poetry", "INSTALLED", shutil.which("poetry"))) + else: + results.append(("poetry", "FAILED", "Auto-install failed, install manually")) + else: + results.append(("poetry", "OK", shutil.which("poetry"))) + + # --- python 3.11+ --- + logger.info("Checking Python version...") + python_cmd, python_ver = self._find_python311_plus() + if python_cmd: + results.append(("python 3.11+", "OK", python_ver)) + else: + results.append(("python 3.11+", "MISSING", "Install python3.11 or newer")) + + # --- poetry env + install --- + has_poetry = shutil.which("poetry") is not None + if has_poetry and python_cmd: + logger.info("Running poetry env use + poetry install...") + try: + self.run_command( + ["poetry", "env", "use", python_cmd], + cwd=str(repo_root), check=True) + self.run_command( + ["poetry", "install"], + cwd=str(repo_root), check=True) + results.append(("poetry install", "OK", "Dependencies installed")) + except Exception: + results.append(("poetry install", "FAILED", + f"Run: poetry env use {python_cmd} && poetry install")) + elif has_poetry: + results.append(("poetry install", "SKIPPED", "No compatible Python found")) + else: + results.append(("poetry install", "SKIPPED", "Poetry not available")) + + # --- Summary table --- + ok_statuses = {"OK", "INSTALLED", "UPDATED", "SKIPPED"} + print("\n" + "="*70) + print("MODE B SETUP SUMMARY") + print("="*70) + print(f" {'Step':<20} {'Status':<16} {'Detail'}") + print(f" {'-'*18:<20} {'-'*14:<16} {'-'*30}") + needs_action = False + for name, status, detail in results: + print(f" {name:<20} {status:<16} {detail}") + if status not in ok_statuses: + needs_action = True + + if not needs_action: + print(f"\nAll steps completed. To start:") + print(f" cd {repo_root}") + print(f" eval $(poetry env activate)") + print(f" python3 cli.py") + else: + print(f"\nSome steps need manual action. See details above.") + print(f"After resolving, start with:") + print(f" cd {repo_root}") + print(f" poetry env use python3.11 && poetry install") + print(f" eval $(poetry env activate)") + print(f" python3 cli.py") + + print("="*70 + "\n") + + return not needs_action + + def _detect_git_remote(self): + """Detect the git remote URL of the current repo.""" + repo_root = self.script_dir.parent.parent + try: + url = self.run_command( + ["git", "remote", "get-url", "origin"], + capture_output=True, cwd=str(repo_root), check=True + ) + if url: + return url + except Exception: + pass + return "https://github.com/microsoft/AIOpsLab.git" + + def _detect_git_branch(self): + """Detect the current git branch.""" + repo_root = self.script_dir.parent.parent + try: + branch = self.run_command( + ["git", "branch", "--show-current"], + capture_output=True, cwd=str(repo_root), check=True + ) + if branch: + return branch + except Exception: + pass + return "main" + + def setup_aiopslab_mode_a(self, outputs, ssh_key_path, dev_mode=False): + """Configure AIOpsLab for Mode A (on controller VM). + + Runs the setup_aiopslab.yml Ansible playbook which installs Python 3.11, + Poetry, clones/rsyncs the repo, generates config.yml, and runs poetry install. + """ + controller = outputs['controller']['value'] + controller_ip = controller['public_ip'] + admin_username = controller['username'] + repo_root = self.script_dir.parent.parent + repo_dest = f"/home/{admin_username}/AIOpsLab" + + # Build extra-vars for Ansible + extra_vars = { + "dev_mode": dev_mode, + "repo_dest": repo_dest, + "admin_username": admin_username, + } + + if dev_mode: + extra_vars["local_repo_path"] = str(repo_root) + # synchronize module needs ansible.posix collection + logger.info("Ensuring ansible.posix collection is installed (needed for synchronize)...") + try: + self.run_command( + ["ansible-galaxy", "collection", "install", "ansible.posix"], + check=False + ) + except Exception: + logger.warning("Could not install ansible.posix; synchronize may fail") + # Set dummy values for unused vars so Ansible doesn't complain about undefined + extra_vars["repo_url"] = "" + extra_vars["repo_branch"] = "" + else: + extra_vars["repo_url"] = self._detect_git_remote() + extra_vars["repo_branch"] = self._detect_git_branch() + extra_vars["local_repo_path"] = "" + + if dev_mode: + mode_desc = "dev mode (rsync)" + else: + mode_desc = f"clone {extra_vars['repo_url']} @ {extra_vars['repo_branch']}" + logger.info(f"Setting up AIOpsLab on controller ({controller_ip}) — {mode_desc}") + + success = self.run_ansible_playbook( + "setup_aiopslab.yml", + extra_args=["--extra-vars", json.dumps(extra_vars)] + ) + + # Derive private key path + ssh_private_key = ssh_key_path + if ssh_private_key.endswith('.pub'): + ssh_private_key = ssh_private_key[:-4] + + print("\n" + "="*70) + if success: + print("MODE A SETUP COMPLETE") + print("="*70) + print(f"\n AIOpsLab is installed at: {repo_dest}") + print(f" Config: {repo_dest}/aiopslab/config.yml (k8s_host=localhost)") + print(f" Mode: {'dev (rsync)' if dev_mode else 'clone'}") + print(f"\n To start AIOpsLab:") + print(f" ssh -i {ssh_private_key} {admin_username}@{controller_ip}") + print(f" cd {repo_dest}") + print(f" eval $(~/.local/bin/poetry env activate)") + print(f" python3 cli.py") + else: + print("MODE A SETUP FAILED") + print("="*70) + print(f"\n The Ansible playbook failed. You can try manually:") + print(f" ssh -i {ssh_private_key} {admin_username}@{controller_ip}") + print(f" # Then follow the setup steps in CLAUDE.md") + print("\n" + "="*70 + "\n") + + return success + + def setup_only(self, resource_group, ssh_key_path, mode="A", dev_mode=False): + """Re-run AIOpsLab setup without reprovisioning infrastructure. + + Reads existing Terraform outputs and runs the Mode A or B setup directly. + Useful for iterating on code: edit locally, then re-sync to the controller. + """ + logger.info("="*70) + logger.info("AIOPSLAB SETUP-ONLY (no Terraform changes)") + logger.info("="*70) + logger.info(f"Resource Group: {resource_group}") + logger.info(f"Mode: {mode} ({'AIOpsLab on controller' if mode == 'A' else 'AIOpsLab on laptop'})") + if dev_mode: + logger.info("Dev mode: will rsync local repo") + + try: + # Read existing Terraform outputs (no init needed) + outputs = self.get_terraform_outputs() + if not outputs: + logger.error("Failed to read Terraform outputs. Has infrastructure been provisioned?") + logger.error("Run --apply first to provision, then use --setup-only to iterate.") + return False + + # Regenerate inventory in case IPs changed or inventory was deleted + if not self.generate_ansible_inventory(): + logger.error("Inventory generation failed") + return False + + # Ensure SSH host keys are in known_hosts (may be missing if + # known_hosts was cleared since the original --apply) + self.add_ssh_host_keys(outputs) + + # Run the appropriate setup + if mode == 'A': + return self.setup_aiopslab_mode_a(outputs, ssh_key_path, dev_mode) + else: + return self.setup_aiopslab_mode_b(outputs, ssh_key_path) + + except KeyboardInterrupt: + logger.warning("\nSetup interrupted by user") + return False + except Exception as e: + logger.error(f"Setup failed: {e}") + return False + + def print_access_info(self, outputs): + """Print SSH access information.""" + controller = outputs['controller']['value'] + workers = outputs['workers']['value'] + ssh_config = outputs['ssh_config']['value'] + + print("\n" + "="*70) + print("DEPLOYMENT COMPLETE!") + print("="*70) + + print(f"\nController Node:") + print(f" Public IP: {controller['public_ip']}") + print(f" Private IP: {controller['private_ip']}") + print(f" SSH: ssh -i {ssh_config.get('private_key_path', '~/.ssh/id_rsa')} {controller['username']}@{controller['public_ip']}") + + print(f"\nWorker Nodes ({len(workers)}):") + for idx, worker in enumerate(workers, start=1): + print(f" Worker {idx}:") + print(f" Public IP: {worker['public_ip']}") + print(f" Private IP: {worker['private_ip']}") + print(f" SSH: ssh -i {ssh_config.get('private_key_path', '~/.ssh/id_rsa')} {worker['username']}@{worker['public_ip']}") + + print("\n" + "="*70 + "\n") + + def deploy(self, worker_count, vm_size, resource_group, prefix, ssh_key_path, allowed_ips="*", mode="B", dev_mode=False): + """Execute full deployment workflow.""" + logger.info("="*70) + logger.info("STARTING AIOPSLAB DEPLOYMENT") + logger.info("="*70) + logger.info(f"Workers: {worker_count}") + logger.info(f"VM Size: {vm_size}") + logger.info(f"Resource Group: {resource_group}") + logger.info(f"Prefix: {prefix}") + logger.info(f"NSG Allowed Source: {allowed_ips}") + logger.info(f"Mode: {mode} ({'AIOpsLab on controller' if mode == 'A' else 'AIOpsLab on laptop'})") + + if allowed_ips == "*": + print("\n WARNING: SSH (22) and K8s API (6443) will be open to ALL IP addresses.") + print(" To restrict, abort and re-run with: --allowed-ips ") + print(" Example: --allowed-ips CorpNetPublic\n") + try: + input(" Press Enter to continue, or Ctrl+C to abort... ") + except KeyboardInterrupt: + print() + logger.warning("Deployment aborted") + return False + + try: + # Step 1: Initialize Terraform + if not self.terraform_init(): + logger.error("Terraform initialization failed") + return False + + # Step 2: Plan infrastructure + if not self.terraform_plan(worker_count, vm_size, resource_group, prefix, ssh_key_path, allowed_ips): + logger.error("Terraform planning failed") + return False + + # Step 3: Apply infrastructure + if not self.terraform_apply(): + logger.error("Terraform apply failed") + return False + + # Step 4: Get outputs + outputs = self.get_terraform_outputs() + if not outputs: + logger.error("Failed to retrieve Terraform outputs") + return False + + # Step 5: Generate Ansible inventory + if not self.generate_ansible_inventory(): + logger.error("Inventory generation failed") + return False + + # Step 6: Wait for SSH + if not self.wait_for_all_hosts(outputs): + logger.error("SSH connectivity check failed") + return False + + # Step 6.5: Add SSH host keys to known_hosts + self.add_ssh_host_keys(outputs) + + # Step 7: Run Ansible - setup common + logger.info("Setting up common dependencies on all nodes...") + if not self.run_ansible_playbook("setup_common.yml"): + logger.error("Ansible setup_common.yml failed") + return False + + # Step 8: Run Ansible - setup cluster + logger.info("Setting up Kubernetes cluster...") + if not self.run_ansible_playbook("remote_setup_controller_worker.yml"): + logger.error("Ansible remote_setup_controller_worker.yml failed") + return False + + # Step 9: Print access info + self.print_access_info(outputs) + + # Step 10: Set up AIOpsLab + if mode == 'B': + self.setup_aiopslab_mode_b(outputs, ssh_key_path) + elif mode == 'A': + self.setup_aiopslab_mode_a(outputs, ssh_key_path, dev_mode) + + logger.info("DEPLOYMENT SUCCESSFUL!") + return True + + except KeyboardInterrupt: + logger.warning("\nDeployment interrupted by user") + return False + except Exception as e: + logger.error(f"Deployment failed: {e}") + return False + + def plan(self, worker_count, vm_size, resource_group, prefix, ssh_key_path, allowed_ips="*", mode="B"): + """Show deployment plan without applying (dry-run).""" + logger.info("="*70) + logger.info("AIOPSLAB DEPLOYMENT PLAN (DRY-RUN)") + logger.info("="*70) + logger.info(f"Workers: {worker_count}") + logger.info(f"VM Size: {vm_size}") + logger.info(f"Resource Group: {resource_group}") + logger.info(f"Prefix: {prefix}") + logger.info(f"NSG Allowed Source: {allowed_ips}") + logger.info(f"Mode: {mode} ({'AIOpsLab on controller' if mode == 'A' else 'AIOpsLab on laptop'})") + logger.info("") + logger.info("This will show what resources would be created WITHOUT actually creating them.") + logger.info("="*70) + logger.info("") + + try: + # Step 1: Initialize Terraform + if not self.terraform_init(): + logger.error("Terraform initialization failed") + return False + + # Step 2: Show plan only (no apply) + if not self.terraform_plan_only(worker_count, vm_size, resource_group, prefix, ssh_key_path, allowed_ips): + logger.error("Terraform planning failed") + return False + + logger.info("") + logger.info("="*70) + logger.info("PLAN COMPLETE!") + logger.info("="*70) + logger.info("") + logger.info("Review the plan above to see what would be created.") + logger.info("") + logger.info("To actually deploy, run:") + apply_args = [a if a != "--plan" else "--apply" for a in sys.argv] + logger.info(f" {' '.join(apply_args)}") + logger.info("") + logger.info("Note: This will create billable Azure resources.") + logger.info("") + return True + + except KeyboardInterrupt: + logger.warning("\nPlan interrupted by user") + return False + except Exception as e: + logger.error(f"Plan failed: {e}") + return False + + def destroy(self, resource_group, prefix, ssh_key_path, allowed_ips="*"): + """Destroy deployed infrastructure.""" + logger.info("="*70) + logger.info("DESTROYING AIOPSLAB INFRASTRUCTURE") + logger.info("="*70) + + try: + # Confirm destruction + confirm = input("This will destroy all resources. Type 'yes' to confirm: ") + if confirm.lower() != 'yes': + logger.warning("Destruction cancelled") + return False + + # Destroy infrastructure + if self.terraform_destroy(resource_group, prefix, ssh_key_path, allowed_ips): + logger.info("Infrastructure destroyed successfully") + return True + else: + logger.error("Destruction failed") + return False + + except KeyboardInterrupt: + logger.warning("\nDestruction interrupted by user") + return False + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser( + description="AIOpsLab Automated Deployment", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + Show deployment plan (dry-run): + python deploy.py --plan --workers 3 --vm-size Standard_D4s_v3 + + Deploy with 3 workers: + python deploy.py --apply --workers 3 --vm-size Standard_D4s_v3 + + Deploy with SSH restricted to a specific CIDR: + python deploy.py --apply --workers 2 --allowed-ips 203.0.113.0/24 + + Deploy with SSH restricted to an Azure service tag: + python deploy.py --apply --workers 2 --allowed-ips CorpNetPublic + + Destroy infrastructure: + python deploy.py --destroy + + Re-run setup only (no Terraform changes): + python deploy.py --setup-only --mode A --dev --resource-group AIOpsBenchmark + + Deploy with custom settings: + python deploy.py --apply --workers 5 --vm-size Standard_D8s_v3 \\ + --resource-group my-rg --prefix myaiops --ssh-key ~/.ssh/id_rsa.pub + """ + ) + + parser.add_argument( + '--plan', + action='store_true', + help='Show deployment plan without applying (dry-run)' + ) + + parser.add_argument( + '--apply', + action='store_true', + help='Deploy infrastructure and setup cluster' + ) + + parser.add_argument( + '--destroy', + action='store_true', + help='Destroy all infrastructure' + ) + + parser.add_argument( + '--setup-only', + action='store_true', + help='Re-run AIOpsLab setup without reprovisioning infrastructure. Reads existing Terraform outputs and runs Mode A/B setup. Useful for iterating: edit code locally, then re-sync.' + ) + + parser.add_argument( + '--workers', + type=int, + default=2, + help='Number of worker nodes (default: 2)' + ) + + parser.add_argument( + '--vm-size', + default='Standard_B2s', + help='Azure VM size (default: Standard_B2s)' + ) + + parser.add_argument( + '--resource-group', + default='aiopslab-rg', + help='Azure resource group name (default: aiopslab-rg)' + ) + + parser.add_argument( + '--prefix', + default='aiopslab', + help='Resource name prefix (default: aiopslab)' + ) + + parser.add_argument( + '--ssh-key', + default='~/.ssh/id_rsa.pub', + help='Path to SSH public key (default: ~/.ssh/id_rsa.pub)' + ) + + parser.add_argument( + '--allowed-ips', + default='*', + help='Source address for NSG rules (SSH + K8s API). Use \'*\' for open access (default), a CIDR like \'203.0.113.0/24\', or an Azure service tag like \'CorpNetPublic\'' + ) + + parser.add_argument( + '--mode', + choices=['A', 'B'], + default='B', + help='Deployment mode. A: AIOpsLab runs on controller VM. B: AIOpsLab runs on laptop with remote kubectl (default: B)' + ) + + parser.add_argument( + '--dev', + action='store_true', + help='Developer mode for Mode A: rsync local repo instead of git clone' + ) + + parser.add_argument( + '--debug', + action='store_true', + help='Enable debug logging' + ) + + args = parser.parse_args() + + # Configure logging level + if args.debug: + logger.setLevel(logging.DEBUG) + + # Validate arguments + if not args.plan and not args.apply and not args.destroy and not args.setup_only: + parser.print_help() + sys.exit(1) + + # Check for conflicting options + action_count = sum([args.plan, args.apply, args.destroy, args.setup_only]) + if action_count > 1: + logger.error("Cannot use --plan, --apply, --destroy, and --setup-only together. Choose one.") + sys.exit(1) + + # Validate --dev only with --mode A + if args.dev and args.mode != 'A': + logger.error("--dev flag is only meaningful with --mode A") + sys.exit(1) + + # Expand SSH key path + ssh_key_path = os.path.expanduser(args.ssh_key) + + # Create deployer + deployer = AIOpsLabDeployer() + + # Execute action + if args.plan: + success = deployer.plan( + worker_count=args.workers, + vm_size=args.vm_size, + resource_group=args.resource_group, + prefix=args.prefix, + ssh_key_path=ssh_key_path, + allowed_ips=args.allowed_ips, + mode=args.mode + ) + elif args.apply: + success = deployer.deploy( + worker_count=args.workers, + vm_size=args.vm_size, + resource_group=args.resource_group, + prefix=args.prefix, + ssh_key_path=ssh_key_path, + allowed_ips=args.allowed_ips, + mode=args.mode, + dev_mode=args.dev + ) + elif args.destroy: + success = deployer.destroy( + resource_group=args.resource_group, + prefix=args.prefix, + ssh_key_path=ssh_key_path, + allowed_ips=args.allowed_ips + ) + elif args.setup_only: + success = deployer.setup_only( + resource_group=args.resource_group, + ssh_key_path=ssh_key_path, + mode=args.mode, + dev_mode=args.dev + ) + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() diff --git a/scripts/terraform/generate_inventory.py b/scripts/terraform/generate_inventory.py new file mode 100644 index 00000000..d0486c62 --- /dev/null +++ b/scripts/terraform/generate_inventory.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +""" +Ansible Inventory Generator for AIOpsLab +Generates inventory.yml from Terraform outputs for multi-VM Kubernetes cluster deployment. +""" + +import json +import subprocess +import sys +import logging +from pathlib import Path + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') +logger = logging.getLogger(__name__) + + +def run_command(command, capture_output=True, cwd=None): + """Execute a shell command and return output.""" + try: + result = subprocess.run( + command, + capture_output=capture_output, + text=True, + check=True, + cwd=cwd + ) + return result.stdout.strip() if capture_output else None + except subprocess.CalledProcessError as e: + logger.error(f"Command failed: {' '.join(command)}") + logger.error(f"Error: {e.stderr.strip() if e.stderr else str(e)}") + return None + + +def get_terraform_outputs(cwd=None): + """Retrieve all Terraform outputs as JSON.""" + logger.info("Retrieving Terraform outputs...") + output = run_command(["terraform", "output", "-json"], cwd=cwd) + + if not output: + logger.error("Failed to retrieve Terraform outputs") + logger.error("Make sure you've run 'terraform apply' successfully") + sys.exit(1) + + try: + outputs = json.loads(output) + logger.info("Successfully retrieved Terraform outputs") + return outputs + except json.JSONDecodeError as e: + logger.error(f"Failed to parse Terraform output JSON: {e}") + sys.exit(1) + + +def validate_outputs(outputs): + """Validate that required outputs exist.""" + required = ['controller', 'workers', 'ssh_config'] + missing = [key for key in required if key not in outputs] + + if missing: + logger.error(f"Missing required Terraform outputs: {', '.join(missing)}") + logger.error("Please update your Terraform configuration") + sys.exit(1) + + logger.info("All required outputs present") + + +def generate_inventory(outputs): + """Generate Ansible inventory YAML from Terraform outputs.""" + controller = outputs['controller']['value'] + workers = outputs['workers']['value'] + ssh_config = outputs['ssh_config']['value'] + + # Build inventory structure + # user_home_base: /home for cloud VMs (Linux standard), /users for Emulab + inventory = { + 'all': { + 'vars': { + 'k8s_user': controller['username'], + 'user_home_base': '/home', # Cloud VMs use /home, Emulab uses /users + 'ansible_ssh_private_key_file': ssh_config.get('private_key_path', '~/.ssh/id_rsa') + }, + 'children': { + 'control_nodes': { + 'hosts': { + 'control_node': { + 'ansible_host': controller['public_ip'], + 'ansible_user': controller['username'], + 'private_ip': controller['private_ip'] + } + } + }, + 'worker_nodes': { + 'hosts': {} + } + } + } + } + + # Add all workers dynamically + for idx, worker in enumerate(workers, start=1): + worker_name = f"worker_node_{idx}" + inventory['all']['children']['worker_nodes']['hosts'][worker_name] = { + 'ansible_host': worker['public_ip'], + 'ansible_user': worker['username'], + 'private_ip': worker['private_ip'] + } + + logger.info(f"Generated inventory for 1 controller + {len(workers)} worker(s)") + return inventory + + +def write_inventory_yaml(inventory, output_path): + """Write inventory to YAML file.""" + try: + import yaml + except ImportError: + logger.error("PyYAML is required but not installed.") + logger.error("Install it with: pip install pyyaml") + return False + + try: + with open(output_path, 'w') as f: + yaml.dump(inventory, f, default_flow_style=False, sort_keys=False) + logger.info(f"Inventory written to: {output_path}") + return True + except Exception as e: + logger.error(f"Failed to write inventory file: {e}") + return False + + +def print_inventory_summary(inventory): + """Print a summary of the generated inventory.""" + controller = inventory['all']['children']['control_nodes']['hosts']['control_node'] + workers = inventory['all']['children']['worker_nodes']['hosts'] + + print("\n" + "="*60) + print("ANSIBLE INVENTORY SUMMARY") + print("="*60) + print(f"\n Controller Node:") + print(f" Name: control_node") + print(f" IP: {controller['ansible_host']}") + print(f" User: {controller['ansible_user']}") + + print(f"\n Worker Nodes ({len(workers)}):") + for name, info in workers.items(): + print(f" {name}:") + print(f" IP: {info['ansible_host']}") + print(f" User: {info['ansible_user']}") + + print("\n" + "="*60) + + +def main(): + """Main execution function.""" + # Get current directory + script_dir = Path(__file__).parent + ansible_dir = script_dir.parent / "ansible" + inventory_path = ansible_dir / "inventory.yml" + + logger.info("Starting Ansible inventory generation...") + logger.info(f"Target inventory file: {inventory_path}") + + # Check if we're in the right directory + if not (script_dir / "main.tf").exists(): + logger.error("main.tf not found. Are you in the terraform directory?") + sys.exit(1) + + # Create ansible directory if it doesn't exist + ansible_dir.mkdir(exist_ok=True) + + # Get Terraform outputs (run terraform from the script's directory) + outputs = get_terraform_outputs(cwd=str(script_dir)) + + # Validate outputs + validate_outputs(outputs) + + # Generate inventory + inventory = generate_inventory(outputs) + + # Write inventory file + if write_inventory_yaml(inventory, inventory_path): + print_inventory_summary(inventory) + logger.info("Inventory generation completed successfully") + return 0 + else: + logger.error("Failed to generate inventory") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/terraform/main.tf b/scripts/terraform/main.tf index 77db20cd..0b632c49 100644 --- a/scripts/terraform/main.tf +++ b/scripts/terraform/main.tf @@ -1,197 +1,165 @@ -# Create virtual network -resource "azurerm_virtual_network" "aiopslab_network" { - name = "${var.resource_name_prefix}_aiopslabVnet" - address_space = ["10.0.0.0/16"] - location = var.resource_location - resource_group_name = var.resource_group_name -} - -# Create subnet -resource "azurerm_subnet" "aiopslab_subnet" { - name = "${var.resource_name_prefix}_aiopslabSubnet" - resource_group_name = var.resource_group_name - virtual_network_name = azurerm_virtual_network.aiopslab_network.name - address_prefixes = ["10.0.1.0/24"] +data "azurerm_resource_group" "rg" { + name = var.resource_group_name } -# Create public IPs -resource "azurerm_public_ip" "aiopslab_public_ip_1" { - name = "${var.resource_name_prefix}_aiopslabPublicIP_1" - location = var.resource_location - resource_group_name = var.resource_group_name - allocation_method = "Dynamic" +resource "azurerm_virtual_network" "vnet" { + name = "${var.prefix}-vnet" + address_space = ["10.0.0.0/16"] + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name } -resource "azurerm_public_ip" "aiopslab_public_ip_2" { - name = "${var.resource_name_prefix}_aiopslabPublicIP_2" - location = var.resource_location - resource_group_name = var.resource_group_name - allocation_method = "Dynamic" +resource "azurerm_subnet" "subnet" { + name = "${var.prefix}-subnet" + resource_group_name = data.azurerm_resource_group.rg.name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = ["10.0.1.0/24"] } -# Create Network Security Group and rule with only CorpNet access -resource "azurerm_network_security_group" "aiopslab_nsg_1" { - name = "${var.resource_name_prefix}_aiopslabNSG_1" - location = var.resource_location - resource_group_name = var.resource_group_name +resource "azurerm_network_security_group" "nsg" { + name = "${var.prefix}-nsg" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name + # SSH access - restrict via var.nsg_allowed_source or --allowed-ips in deploy.py security_rule { name = "SSH" - priority = 1001 + priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" - source_address_prefix = "*" + source_address_prefix = var.nsg_allowed_source destination_address_prefix = "*" } -} - -resource "azurerm_network_security_group" "aiopslab_nsg_2" { - name = "${var.resource_name_prefix}_aiopslabNSG_2" - location = var.resource_location - resource_group_name = var.resource_group_name + # Kubernetes API server - for remote kubectl access (Mode B) security_rule { - name = "SSH" - priority = 1001 + name = "KubernetesAPI" + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_range = "22" - source_address_prefix = "*" + destination_port_range = "6443" + source_address_prefix = var.nsg_allowed_source destination_address_prefix = "*" } } -# Create network interfaces -resource "azurerm_network_interface" "aiopslab_nic_1" { - name = "${var.resource_name_prefix}_aiopslabNIC_1" - location = var.resource_location - resource_group_name = var.resource_group_name - - ip_configuration { - name = "${var.resource_name_prefix}_aioplabNICConfiguration_1" - subnet_id = azurerm_subnet.aiopslab_subnet.id - private_ip_address_allocation = "Dynamic" - public_ip_address_id = azurerm_public_ip.aiopslab_public_ip_1.id - } -} - -resource "azurerm_network_interface" "aiopslab_nic_2" { - name = "${var.resource_name_prefix}_aiopslabNIC_2" - location = var.resource_location - resource_group_name = var.resource_group_name +resource "azurerm_network_interface" "controller" { + name = "${var.prefix}-controller-nic" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name ip_configuration { - name = "${var.resource_name_prefix}_aioplabNICConfiguration_2" - subnet_id = azurerm_subnet.aiopslab_subnet.id + name = "internal" private_ip_address_allocation = "Dynamic" - public_ip_address_id = azurerm_public_ip.aiopslab_public_ip_2.id + subnet_id = azurerm_subnet.subnet.id + public_ip_address_id = azurerm_public_ip.controller.id } } -# Connect the security groups to the network interfaces -resource "azurerm_network_interface_security_group_association" "aiopslab_nsg_association_1" { - network_interface_id = azurerm_network_interface.aiopslab_nic_1.id - network_security_group_id = azurerm_network_security_group.aiopslab_nsg_1.id +resource "azurerm_public_ip" "controller" { + name = "${var.prefix}-controller-pip" + resource_group_name = data.azurerm_resource_group.rg.name + location = data.azurerm_resource_group.rg.location + allocation_method = "Static" + ip_version = "IPv4" } -resource "azurerm_network_interface_security_group_association" "aiopslab_nsg_association_2" { - network_interface_id = azurerm_network_interface.aiopslab_nic_2.id - network_security_group_id = azurerm_network_security_group.aiopslab_nsg_2.id +resource "azurerm_network_interface_security_group_association" "controller" { + network_interface_id = azurerm_network_interface.controller.id + network_security_group_id = azurerm_network_security_group.nsg.id } -resource "random_id" "random_id" { - byte_length = 8 -} - - -# Create storage accounts for boot diagnostics -resource "azurerm_storage_account" "aiopslab_storage_account_1" { - # storage account names can only consist of lowercase letters and numbers - name = "diag${random_id.random_id.hex}1" - location = var.resource_location - resource_group_name = var.resource_group_name - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_storage_account" "aiopslab_storage_account_2" { - name = "diag${random_id.random_id.hex}2" - location = var.resource_location - resource_group_name = var.resource_group_name - account_tier = "Standard" - account_replication_type = "LRS" -} +resource "azurerm_linux_virtual_machine" "controller" { + name = "${var.prefix}-controller" + resource_group_name = data.azurerm_resource_group.rg.name + location = data.azurerm_resource_group.rg.location + size = var.vm_size + admin_username = var.admin_username + network_interface_ids = [ + azurerm_network_interface.controller.id, + ] + disable_password_authentication = true - - -# Create virtual machines -resource "azurerm_linux_virtual_machine" "aiopslab_vm_1" { - name = "${var.resource_name_prefix}_aiopslabVM_1" - location = var.resource_location - resource_group_name = var.resource_group_name - network_interface_ids = [azurerm_network_interface.aiopslab_nic_1.id] - size = "Standard_D4s_v3" + admin_ssh_key { + username = var.admin_username + public_key = file(var.ssh_public_key_path) + } os_disk { - name = "${var.resource_name_prefix}_OsDisk_1" caching = "ReadWrite" - storage_account_type = "Premium_LRS" + storage_account_type = var.os_disk_type + disk_size_gb = 64 } source_image_reference { - publisher = "Canonical" - offer = "0001-com-ubuntu-server-jammy" - sku = "22_04-lts-gen2" + publisher = var.os_publisher + offer = var.os_offer + sku = var.os_sku version = "latest" } +} - computer_name = "kubeController" - admin_username = var.username +resource "azurerm_network_interface" "workers" { + for_each = toset([for i in range(var.worker_vm_count) : "worker-${i+1}"]) + name = "${var.prefix}-${each.key}-nic" + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name - admin_ssh_key { - username = var.username - public_key = azapi_resource_action.aiopslab_ssh_public_key_gen_1.output.publicKey + ip_configuration { + name = "internal" + private_ip_address_allocation = "Dynamic" + subnet_id = azurerm_subnet.subnet.id + public_ip_address_id = azurerm_public_ip.workers[each.key].id } +} - boot_diagnostics { - storage_account_uri = azurerm_storage_account.aiopslab_storage_account_1.primary_blob_endpoint - } +resource "azurerm_public_ip" "workers" { + for_each = toset([for i in range(var.worker_vm_count) : "worker-${i+1}"]) + name = "${var.prefix}-${each.key}-pip" + resource_group_name = data.azurerm_resource_group.rg.name + location = data.azurerm_resource_group.rg.location + allocation_method = "Static" + ip_version = "IPv4" } -resource "azurerm_linux_virtual_machine" "aiopslab_vm_2" { - name = "${var.resource_name_prefix}_aiopslabVM_2" - location = var.resource_location - resource_group_name = var.resource_group_name - network_interface_ids = [azurerm_network_interface.aiopslab_nic_2.id] - size = "Standard_F16s_v2" +resource "azurerm_network_interface_security_group_association" "workers" { + for_each = azurerm_network_interface.workers + network_interface_id = each.value.id + network_security_group_id = azurerm_network_security_group.nsg.id +} + +resource "azurerm_linux_virtual_machine" "workers" { + for_each = toset([for i in range(var.worker_vm_count) : "worker-${i+1}"]) + name = "${var.prefix}-${each.key}" + resource_group_name = data.azurerm_resource_group.rg.name + location = data.azurerm_resource_group.rg.location + size = var.vm_size + admin_username = var.admin_username + network_interface_ids = [ + azurerm_network_interface.workers[each.key].id, + ] + disable_password_authentication = true + + admin_ssh_key { + username = var.admin_username + public_key = file(var.ssh_public_key_path) + } os_disk { - name = "${var.resource_name_prefix}_OsDisk_2" caching = "ReadWrite" - storage_account_type = "Premium_LRS" + storage_account_type = var.os_disk_type + disk_size_gb = 64 } source_image_reference { - publisher = "Canonical" - offer = "0001-com-ubuntu-server-jammy" - sku = "22_04-lts-gen2" + publisher = var.os_publisher + offer = var.os_offer + sku = var.os_sku version = "latest" } - - computer_name = "kubeWorker1" - admin_username = var.username - - admin_ssh_key { - username = var.username - public_key = azapi_resource_action.aiopslab_ssh_public_key_gen_2.output.publicKey - } - - boot_diagnostics { - storage_account_uri = azurerm_storage_account.aiopslab_storage_account_2.primary_blob_endpoint - } } diff --git a/scripts/terraform/outputs.tf b/scripts/terraform/outputs.tf index e612f0ad..7ca4aaa9 100644 --- a/scripts/terraform/outputs.tf +++ b/scripts/terraform/outputs.tf @@ -1,19 +1,42 @@ -output "public_ip_address_1" { - value = azurerm_linux_virtual_machine.aiopslab_vm_1.public_ip_address +output "controller" { + description = "Controller node details" + value = { + name = azurerm_linux_virtual_machine.controller.name + public_ip = azurerm_public_ip.controller.ip_address + private_ip = azurerm_network_interface.controller.ip_configuration[0].private_ip_address + username = var.admin_username + } } -output "public_ip_address_2" { - value = azurerm_linux_virtual_machine.aiopslab_vm_2.public_ip_address +output "workers" { + description = "Worker nodes details" + value = [ + for key, vm in azurerm_linux_virtual_machine.workers : { + name = vm.name + public_ip = azurerm_public_ip.workers[key].ip_address + private_ip = azurerm_network_interface.workers[key].ip_configuration[0].private_ip_address + username = var.admin_username + } + ] } -output "key_data_1" { - value = azapi_resource_action.aiopslab_ssh_public_key_gen_1.output.privateKey +output "cluster_info" { + description = "Complete cluster information" + value = { + resource_group = data.azurerm_resource_group.rg.name + location = data.azurerm_resource_group.rg.location + prefix = var.prefix + worker_count = var.worker_vm_count + vm_size = var.vm_size + } } -output "key_data_2" { - value = azapi_resource_action.aiopslab_ssh_public_key_gen_2.output.privateKey -} - -output "username" { - value = var.username +output "ssh_config" { + description = "SSH configuration" + value = { + public_key_path = var.ssh_public_key_path + private_key_path = replace(var.ssh_public_key_path, ".pub", "") + username = var.admin_username + } + sensitive = true } \ No newline at end of file diff --git a/scripts/terraform/providers.tf b/scripts/terraform/providers.tf index ca5acba6..eabe9e3a 100644 --- a/scripts/terraform/providers.tf +++ b/scripts/terraform/providers.tf @@ -1,17 +1,9 @@ terraform { - required_version = ">=0.12" + required_version = "~>1.6" required_providers { - azapi = { - source = "azure/azapi" - version = "~>1.5" - } azurerm = { source = "hashicorp/azurerm" - version = "~>2.0" - } - random = { - source = "hashicorp/random" version = "~>3.0" } } @@ -19,5 +11,11 @@ terraform { provider "azurerm" { features {} - skip_provider_registration = true + # SCENARIO GUIDE: + # - If all required providers are already registered, the setting below is fine. + # - If providers are not registered and you have permissions, remove the below line + # - If you lack permissions, leave the below line as it is and have your + # Azure admin manually register the necessary providers before running `terraform apply`. + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_provider_registration + skip_provider_registration = true } \ No newline at end of file diff --git a/scripts/terraform/ssh.tf b/scripts/terraform/ssh.tf deleted file mode 100644 index 19039b19..00000000 --- a/scripts/terraform/ssh.tf +++ /dev/null @@ -1,35 +0,0 @@ - -resource "azapi_resource" "aiopslab_ssh_public_key_1" { - type = "Microsoft.Compute/sshPublicKeys@2022-11-01" - name = "${var.resource_name_prefix}_ssh_public_key_1" - location = var.resource_location - parent_id = data.azurerm_resource_group.rg.id -} - -resource "azapi_resource_action" "aiopslab_ssh_public_key_gen_1" { - type = "Microsoft.Compute/sshPublicKeys@2022-11-01" - resource_id = azapi_resource.aiopslab_ssh_public_key_1.id - action = "generateKeyPair" - method = "POST" - - response_export_values = ["publicKey", "privateKey"] -} - - -resource "azapi_resource" "aiopslab_ssh_public_key_2" { - type = "Microsoft.Compute/sshPublicKeys@2022-11-01" - name = "${var.resource_name_prefix}_ssh_public_key_2" - location = var.resource_location - parent_id = data.azurerm_resource_group.rg.id -} - -resource "azapi_resource_action" "aiopslab_ssh_public_key_gen_2" { - type = "Microsoft.Compute/sshPublicKeys@2022-11-01" - resource_id = azapi_resource.aiopslab_ssh_public_key_2.id - action = "generateKeyPair" - method = "POST" - - response_export_values = ["publicKey", "privateKey"] -} - - diff --git a/scripts/terraform/terraform.tfvars.example b/scripts/terraform/terraform.tfvars.example new file mode 100644 index 00000000..e316ee7b --- /dev/null +++ b/scripts/terraform/terraform.tfvars.example @@ -0,0 +1,44 @@ +# AIOpsLab Terraform Configuration Example +# Copy this file to terraform.tfvars and customize for your deployment + +# Resource Configuration +prefix = "aiopslab" # Prefix for all resource names +resource_group_name = "aiopslab-rg" # Existing Azure resource group + +# VM Configuration +worker_vm_count = 2 # Number of worker nodes (1-10) +vm_size = "Standard_B2s" # Azure VM size + # Options: Standard_B2s, Standard_D4s_v3, Standard_D8s_v3, etc. + +# Operating System +os_disk_type = "Standard_LRS" # OS disk type: Standard_LRS, Premium_LRS, StandardSSD_LRS +os_publisher = "Canonical" # OS publisher +os_offer = "0001-com-ubuntu-server-jammy" # OS offer +os_sku = "22_04-lts" # Ubuntu 22.04 LTS + +# SSH Configuration +admin_username = "azureuser" # Admin username for VMs +ssh_public_key_path = "~/.ssh/id_rsa.pub" # Path to SSH public key + +# Network Security +# nsg_allowed_source = "*" # Open to all (default) +# nsg_allowed_source = "203.0.113.0/24" # Restrict to specific CIDR +# nsg_allowed_source = "CorpNetPublic" # Azure service tag (e.g. Microsoft corporate network) + +# Examples for different deployment scenarios: + +# Small Development Cluster (Low Cost) +# worker_vm_count = 1 +# vm_size = "Standard_B2s" + +# Medium Production Cluster (Balanced) +# worker_vm_count = 3 +# vm_size = "Standard_D4s_v3" + +# Large Production Cluster (High Performance) +# worker_vm_count = 5 +# vm_size = "Standard_D8s_v3" + +# GPU-Enabled Cluster (For ML Workloads) +# worker_vm_count = 2 +# vm_size = "Standard_NC6s_v3" diff --git a/scripts/terraform/variables.tf b/scripts/terraform/variables.tf index 0612f857..825a4fd7 100644 --- a/scripts/terraform/variables.tf +++ b/scripts/terraform/variables.tf @@ -1,31 +1,65 @@ -variable "resource_location" { +variable "prefix" { type = string - default = "westus2" - description = "Location of the resource." + description = "A unique prefix for the resource names." + default = "aiopslab" } -variable "username" { +variable "resource_group_name" { + type = string + description = "The name of the existing Azure Resource Group to deploy resources into." + default = "aiopslab-rg" +} + +variable "admin_username" { type = string - description = "The username for the local account that will be created on the new VM." + description = "The username for the VMs." default = "azureuser" } -variable "resource_name_prefix" { +variable "ssh_public_key_path" { type = string - description = "Prefix for all the resource names." + description = "The path to the SSH public key file." + default = "~/.ssh/id_rsa.pub" } -# TODO: Generate random text instead of taking prefix from user? Below will keep it unique for each resource group. -# resource "random_id" "resource_name_prefix" { -# keepers = { -# resource_group = var.resource_group_name -# } -# -# byte_length = 8 -#} +variable "vm_size" { + type = string + description = "The size of the virtual machines." + default = "Standard_B2s" +} +variable "os_disk_type" { + type = string + description = "The type of the OS disk. Allowed values: Standard_LRS, Premium_LRS, StandardSSD_LRS." + default = "Standard_LRS" +} -variable "resource_group_name" { +variable "os_publisher" { type = string - description = "The name of the resource group where the all the resources should be created." -} \ No newline at end of file + description = "The publisher of the OS image." + default = "Canonical" +} + +variable "os_offer" { + type = string + description = "The offer of the OS image." + default = "0001-com-ubuntu-server-jammy" +} + +variable "os_sku" { + type = string + description = "The SKU of the OS image. The default is Ubuntu 22.04 LTS (gen1). Use '22_04-lts-gen2' for gen2 VMs." + default = "22_04-lts" +} + +variable "worker_vm_count" { + type = number + description = "The number of worker nodes to create." + default = 2 +} + +variable "nsg_allowed_source" { + type = string + description = "Source address prefix for NSG rules (SSH + K8s API). Use '*' for open access, a CIDR like '203.0.113.0/24', or an Azure service tag like 'CorpNetPublic'." + default = "*" +} diff --git a/tests/integration/smoke_test.py b/tests/integration/smoke_test.py new file mode 100644 index 00000000..790eda08 --- /dev/null +++ b/tests/integration/smoke_test.py @@ -0,0 +1,73 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +"""Integration smoke test: full pipeline using a zero-cost dummy agent. + +Requires a live Kubernetes cluster (kind) with aiopslab/config.yml present. +Run via GitHub Actions CI or locally after `kind create cluster`: + + poetry run pytest tests/integration/smoke_test.py -v -s + +The test exercises the complete orchestrator path for the lightest problem in +the registry (noop_detection_hotel_reservation-1): + deploy app → inject no-op fault → run workload → dispatch submit() action + → evaluate → recover → cleanup + +No LLM is invoked; the DummyAgent immediately submits the correct answer. +""" + +import asyncio +import pytest + +from aiopslab.orchestrator import Orchestrator + + +# --------------------------------------------------------------------------- +# Dummy agent +# --------------------------------------------------------------------------- + +class DummyAgent: + """Zero-cost agent for CI smoke testing — makes no LLM or API calls. + + For a no-op detection task the correct answer is always "No" (no fault was + injected), so we submit that immediately on the first step. + """ + + async def get_action(self, observation: str) -> str: + return '```\nsubmit("No")\n```' + + +# --------------------------------------------------------------------------- +# Test +# --------------------------------------------------------------------------- + +PROBLEM_ID = "noop_detection_hotel_reservation-1" + + +@pytest.mark.integration +def test_noop_hotel_reservation_smoke(): + """Smoke test: run noop_detection_hotel_reservation-1 end-to-end.""" + agent = DummyAgent() + orchestrator = Orchestrator() + orchestrator.register_agent(agent, name="dummy") + + # --- init_problem: deploys HotelReservation, injects no-op, starts workload + problem_desc, instructions, apis = orchestrator.init_problem(PROBLEM_ID) + + assert problem_desc, "init_problem must return a non-empty problem description" + assert instructions, "init_problem must return non-empty instructions" + assert apis, "init_problem must return a non-empty actions dict" + assert "submit" in "\n".join(apis.keys()), ( + "available actions must include 'submit'" + ) + + # --- start_problem: agent loop (max 1 step — DummyAgent submits immediately) + output = asyncio.run(orchestrator.start_problem(max_steps=1)) + + assert output is not None, "start_problem must return a result dict" + + results = output.get("results", {}) + assert results, f"results dict must be non-empty; got: {output}" + assert results.get("Detection Accuracy") == "Correct", ( + f"Expected Detection Accuracy='Correct', got: {results}" + ) diff --git a/tests/service/test_kubectl.py b/tests/service/test_kubectl.py new file mode 100644 index 00000000..0e853e99 --- /dev/null +++ b/tests/service/test_kubectl.py @@ -0,0 +1,47 @@ +from types import SimpleNamespace +import unittest + +from aiopslab.service.kubectl import KubeCtl + + +def _pod(phase, ready_values=None): + container_statuses = None + if ready_values is not None: + container_statuses = [ + SimpleNamespace(ready=ready) for ready in ready_values + ] + + return SimpleNamespace( + status=SimpleNamespace( + phase=phase, + container_statuses=container_statuses, + ) + ) + + +class PodReadinessTest(unittest.TestCase): + + def test_running_pod_with_ready_containers_satisfies_readiness(self): + pod = _pod("Running", [True, True]) + + self.assertTrue(KubeCtl._pod_is_ready_or_succeeded(pod)) + + def test_succeeded_cleanup_pod_satisfies_readiness(self): + pod = _pod("Succeeded", [False]) + + self.assertTrue(KubeCtl._pod_is_ready_or_succeeded(pod)) + + def test_running_pod_with_unready_container_blocks_readiness(self): + pod = _pod("Running", [True, False]) + + self.assertFalse(KubeCtl._pod_is_ready_or_succeeded(pod)) + + def test_pending_pod_without_container_statuses_blocks_readiness(self): + pod = _pod("Pending") + + self.assertFalse(KubeCtl._pod_is_ready_or_succeeded(pod)) + + def test_failed_pod_with_unready_container_blocks_readiness(self): + pod = _pod("Failed", [False]) + + self.assertFalse(KubeCtl._pod_is_ready_or_succeeded(pod))