From 94ddcfeff6d6a0fe9943a42b2e7900a98ef94a2f Mon Sep 17 00:00:00 2001 From: Travis Vasceannie Date: Mon, 15 Sep 2025 12:35:42 -0400 Subject: [PATCH] init --- .claude/settings.local.json | 11 + .env | 51 + .env.example | 39 + CLAUDE.md | 100 + README.md | 150 + basedpyright.json | 24 + docs/elysia.md | 248 ++ docs/tagging.md | 108 + ingest_pipeline/.env | 38 + ingest_pipeline/__main__.py | 6 + .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 312 bytes ingest_pipeline/cli/__init__.py | 5 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 230 bytes .../cli/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 269 bytes .../cli/__pycache__/main.cpython-312.pyc | Bin 0 -> 25604 bytes .../cli/__pycache__/main.cpython-313.pyc | Bin 0 -> 16816 bytes .../cli/__pycache__/tui.cpython-312.pyc | Bin 0 -> 73288 bytes .../cli/__pycache__/tui.cpython-313.pyc | Bin 0 -> 72400 bytes ingest_pipeline/cli/main.py | 616 ++++ ingest_pipeline/cli/tui/__init__.py | 13 + .../tui/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 484 bytes .../cli/tui/__pycache__/app.cpython-312.pyc | Bin 0 -> 8153 bytes .../tui/__pycache__/models.cpython-312.pyc | Bin 0 -> 1061 bytes .../tui/__pycache__/styles.cpython-312.pyc | Bin 0 -> 4954 bytes ingest_pipeline/cli/tui/app.py | 181 ++ ingest_pipeline/cli/tui/models.py | 26 + ingest_pipeline/cli/tui/screens/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 628 bytes .../__pycache__/dashboard.cpython-312.pyc | Bin 0 -> 25240 bytes .../__pycache__/dialogs.cpython-312.pyc | Bin 0 -> 10519 bytes .../__pycache__/documents.cpython-312.pyc | Bin 0 -> 16520 bytes .../screens/__pycache__/help.cpython-312.pyc | Bin 0 -> 3028 bytes .../__pycache__/ingestion.cpython-312.pyc | Bin 0 -> 12968 bytes .../__pycache__/search.cpython-312.pyc | Bin 0 -> 10691 bytes ingest_pipeline/cli/tui/screens/dashboard.py | 542 ++++ ingest_pipeline/cli/tui/screens/dialogs.py | 189 ++ ingest_pipeline/cli/tui/screens/documents.py | 279 ++ ingest_pipeline/cli/tui/screens/help.py | 50 + ingest_pipeline/cli/tui/screens/ingestion.py | 253 ++ ingest_pipeline/cli/tui/screens/search.py | 190 ++ ingest_pipeline/cli/tui/styles.py | 346 ++ ingest_pipeline/cli/tui/utils/__init__.py | 5 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 317 bytes .../utils/__pycache__/runners.cpython-312.pyc | Bin 0 -> 3024 bytes ingest_pipeline/cli/tui/utils/runners.py | 64 + ingest_pipeline/cli/tui/widgets/__init__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 451 bytes .../widgets/__pycache__/cards.cpython-312.pyc | Bin 0 -> 1629 bytes .../__pycache__/indicators.cpython-312.pyc | Bin 0 -> 4886 bytes .../__pycache__/tables.cpython-312.pyc | Bin 0 -> 7694 bytes ingest_pipeline/cli/tui/widgets/cards.py | 28 + ingest_pipeline/cli/tui/widgets/indicators.py | 86 + ingest_pipeline/cli/tui/widgets/tables.py | 126 + ingest_pipeline/config/__init__.py | 5 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 262 bytes .../__pycache__/settings.cpython-312.pyc | Bin 0 -> 3840 bytes ingest_pipeline/config/settings.py | 103 + ingest_pipeline/core/__init__.py | 27 + .../core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 560 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 1215 bytes .../core/__pycache__/models.cpython-312.pyc | Bin 0 -> 8711 bytes ingest_pipeline/core/exceptions.py | 31 + ingest_pipeline/core/models.py | 149 + ingest_pipeline/flows/__init__.py | 9 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 326 bytes .../__pycache__/ingestion.cpython-312.pyc | Bin 0 -> 10116 bytes .../__pycache__/scheduler.cpython-312.pyc | Bin 0 -> 3039 bytes ingest_pipeline/flows/ingestion.py | 274 ++ ingest_pipeline/flows/scheduler.py | 89 + ingest_pipeline/ingestors/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 365 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 1824 bytes .../__pycache__/firecrawl.cpython-312.pyc | Bin 0 -> 8837 bytes .../__pycache__/repomix.cpython-312.pyc | Bin 0 -> 13308 bytes ingest_pipeline/ingestors/base.py | 50 + ingest_pipeline/ingestors/firecrawl.py | 229 ++ ingest_pipeline/ingestors/repomix.py | 339 ++ ingest_pipeline/storage/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 397 bytes .../storage/__pycache__/base.cpython-312.pyc | Bin 0 -> 3415 bytes .../__pycache__/openwebui.cpython-312.pyc | Bin 0 -> 12998 bytes .../__pycache__/weaviate.cpython-312.pyc | Bin 0 -> 29165 bytes ingest_pipeline/storage/base.py | 106 + ingest_pipeline/storage/openwebui.py | 296 ++ ingest_pipeline/storage/weaviate.py | 703 +++++ ingest_pipeline/utils/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 327 bytes .../metadata_tagger.cpython-312.pyc | Bin 0 -> 10607 bytes .../__pycache__/vectorizer.cpython-312.pyc | Bin 0 -> 8373 bytes ingest_pipeline/utils/metadata_tagger.py | 269 ++ ingest_pipeline/utils/vectorizer.py | 220 ++ pyproject.toml | 78 + tui | 3 + uv.lock | 2771 +++++++++++++++++ 94 files changed, 9583 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .env create mode 100644 .env.example create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 basedpyright.json create mode 100644 docs/elysia.md create mode 100644 docs/tagging.md create mode 100644 ingest_pipeline/.env create mode 100644 ingest_pipeline/__main__.py create mode 100644 ingest_pipeline/__pycache__/__main__.cpython-312.pyc create mode 100644 ingest_pipeline/cli/__init__.py create mode 100644 ingest_pipeline/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/cli/__pycache__/__init__.cpython-313.pyc create mode 100644 ingest_pipeline/cli/__pycache__/main.cpython-312.pyc create mode 100644 ingest_pipeline/cli/__pycache__/main.cpython-313.pyc create mode 100644 ingest_pipeline/cli/__pycache__/tui.cpython-312.pyc create mode 100644 ingest_pipeline/cli/__pycache__/tui.cpython-313.pyc create mode 100644 ingest_pipeline/cli/main.py create mode 100644 ingest_pipeline/cli/tui/__init__.py create mode 100644 ingest_pipeline/cli/tui/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/__pycache__/app.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/__pycache__/models.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/__pycache__/styles.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/app.py create mode 100644 ingest_pipeline/cli/tui/models.py create mode 100644 ingest_pipeline/cli/tui/screens/__init__.py create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/dashboard.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/dialogs.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/documents.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/help.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/ingestion.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/__pycache__/search.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/screens/dashboard.py create mode 100644 ingest_pipeline/cli/tui/screens/dialogs.py create mode 100644 ingest_pipeline/cli/tui/screens/documents.py create mode 100644 ingest_pipeline/cli/tui/screens/help.py create mode 100644 ingest_pipeline/cli/tui/screens/ingestion.py create mode 100644 ingest_pipeline/cli/tui/screens/search.py create mode 100644 ingest_pipeline/cli/tui/styles.py create mode 100644 ingest_pipeline/cli/tui/utils/__init__.py create mode 100644 ingest_pipeline/cli/tui/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/utils/__pycache__/runners.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/utils/runners.py create mode 100644 ingest_pipeline/cli/tui/widgets/__init__.py create mode 100644 ingest_pipeline/cli/tui/widgets/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/widgets/__pycache__/cards.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/widgets/__pycache__/indicators.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/widgets/__pycache__/tables.cpython-312.pyc create mode 100644 ingest_pipeline/cli/tui/widgets/cards.py create mode 100644 ingest_pipeline/cli/tui/widgets/indicators.py create mode 100644 ingest_pipeline/cli/tui/widgets/tables.py create mode 100644 ingest_pipeline/config/__init__.py create mode 100644 ingest_pipeline/config/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/config/__pycache__/settings.cpython-312.pyc create mode 100644 ingest_pipeline/config/settings.py create mode 100644 ingest_pipeline/core/__init__.py create mode 100644 ingest_pipeline/core/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/core/__pycache__/exceptions.cpython-312.pyc create mode 100644 ingest_pipeline/core/__pycache__/models.cpython-312.pyc create mode 100644 ingest_pipeline/core/exceptions.py create mode 100644 ingest_pipeline/core/models.py create mode 100644 ingest_pipeline/flows/__init__.py create mode 100644 ingest_pipeline/flows/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/flows/__pycache__/ingestion.cpython-312.pyc create mode 100644 ingest_pipeline/flows/__pycache__/scheduler.cpython-312.pyc create mode 100644 ingest_pipeline/flows/ingestion.py create mode 100644 ingest_pipeline/flows/scheduler.py create mode 100644 ingest_pipeline/ingestors/__init__.py create mode 100644 ingest_pipeline/ingestors/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/ingestors/__pycache__/base.cpython-312.pyc create mode 100644 ingest_pipeline/ingestors/__pycache__/firecrawl.cpython-312.pyc create mode 100644 ingest_pipeline/ingestors/__pycache__/repomix.cpython-312.pyc create mode 100644 ingest_pipeline/ingestors/base.py create mode 100644 ingest_pipeline/ingestors/firecrawl.py create mode 100644 ingest_pipeline/ingestors/repomix.py create mode 100644 ingest_pipeline/storage/__init__.py create mode 100644 ingest_pipeline/storage/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/storage/__pycache__/base.cpython-312.pyc create mode 100644 ingest_pipeline/storage/__pycache__/openwebui.cpython-312.pyc create mode 100644 ingest_pipeline/storage/__pycache__/weaviate.cpython-312.pyc create mode 100644 ingest_pipeline/storage/base.py create mode 100644 ingest_pipeline/storage/openwebui.py create mode 100644 ingest_pipeline/storage/weaviate.py create mode 100644 ingest_pipeline/utils/__init__.py create mode 100644 ingest_pipeline/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 ingest_pipeline/utils/__pycache__/metadata_tagger.cpython-312.pyc create mode 100644 ingest_pipeline/utils/__pycache__/vectorizer.cpython-312.pyc create mode 100644 ingest_pipeline/utils/metadata_tagger.py create mode 100644 ingest_pipeline/utils/vectorizer.py create mode 100644 pyproject.toml create mode 100755 tui create mode 100644 uv.lock diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..6f20ff2 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "allow": [ + "mcp__context7__resolve-library-id", + "mcp__context7__get-library-docs", + "mcp__sequential-thinking__sequentialthinking" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..0853bf0 --- /dev/null +++ b/.env @@ -0,0 +1,51 @@ +WEAVIATE_IS_LOCAL=True + +# URL can be just a host or full URL; defaults shown below +WCD_URL=http://weaviate.yo # or http://localhost:8080 +# LOCAL_WEAVIATE_PORT=8080 # optional override +# LOCAL_WEAVIATE_GRPC_PORT=50051 # optional override + +# No API key required for local unless you enabled local auth +# WCD_API_KEY= +# API Keys (only if not using local/self-hosted services) +FIRECRAWL_API_KEY=dummy-key +OPENWEBUI_API_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjlmNjEwODg2LWRhM2MtNDQ4YS05OWE0LTYyZGEyZjIyZjJiNiJ9.W-dqabcE4F-LQ--k2yrJM_KEBDB-wi1CmoahlN1tQbY +OPENWEBUI_API_URL=http://chat.lab +WEAVIATE_API_KEY= +OPENAI_API_KEY=sk-1234 +LLM_API_KEY=sk-1234 +# Endpoints +LLM_ENDPOINT=http://llm.lab +WEAVIATE_ENDPOINT=http://weaviate.yo +OPENWEBUI_ENDPOINT=http://chat.lab +FIRECRAWL_ENDPOINT=http://crawl.lab:30002 + +# Model Configuration +EMBEDDING_MODEL=ollama/bge-m3:latest +EMBEDDING_DIMENSION=1024 + +# Ingestion Settings +BATCH_SIZE=50 +MAX_FILE_SIZE=1000000 +MAX_CRAWL_DEPTH=5 +MAX_CRAWL_PAGES=100 + +# Storage Settings +DEFAULT_STORAGE_BACKEND=weaviate +COLLECTION_PREFIX=docs + +# Prefect Settings +PREFECT_API_URL=http://prefect.lab/api +PREFECT_API_KEY=0nR4WAkQ3q9MY1bjqATK6pVmolighvrS +PREFECT_WORK_POOL=default + +# Scheduling +DEFAULT_SCHEDULE_INTERVAL=60 + +# Performance +MAX_CONCURRENT_TASKS=5 +REQUEST_TIMEOUT=60 + +# Logging +LOG_LEVEL=INFO +FIRST_START_ELYSIA='1' diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9167b21 --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +# API Keys (only if not using local/self-hosted services) +FIRECRAWL_API_KEY= +OPENWEBUI_API_KEY= +WEAVIATE_API_KEY= + +# Endpoints +LLM_ENDPOINT=http://llm.lab +WEAVIATE_ENDPOINT=http://weaviate.yo +OPENWEBUI_ENDPOINT=http://chat.lab +FIRECRAWL_ENDPOINT=http://crawl.lab:30002 + +# Model Configuration +EMBEDDING_MODEL=ollama/bge-m3:latest +EMBEDDING_DIMENSION=1024 + +# Ingestion Settings +BATCH_SIZE=50 +MAX_FILE_SIZE=1000000 +MAX_CRAWL_DEPTH=5 +MAX_CRAWL_PAGES=100 + +# Storage Settings +DEFAULT_STORAGE_BACKEND=weaviate +COLLECTION_PREFIX=docs + +# Prefect Settings +PREFECT_API_URL= +PREFECT_API_KEY= +PREFECT_WORK_POOL=default + +# Scheduling +DEFAULT_SCHEDULE_INTERVAL=60 + +# Performance +MAX_CONCURRENT_TASKS=5 +REQUEST_TIMEOUT=60 + +# Logging +LOG_LEVEL=INFO diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8c7a882 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,100 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a modular document ingestion pipeline using Prefect for orchestrating ingestion from web/documentation sites (via Firecrawl) and Git repositories (via Repomix) into Weaviate vector database or Open WebUI knowledge endpoints. + +## Development Commands + +### Environment Setup +```bash +# Install dependencies using uv (required) +uv sync + +# Activate virtual environment +source .venv/bin/activate + +# Install repomix globally (required for repository ingestion) +npm install -g repomix + +# Configure environment +cp .env.example .env +# Edit .env with your settings +``` + +### Running the Application +```bash +# One-time ingestion +python -m ingest_pipeline ingest --type web --storage weaviate + +# Schedule recurring ingestion +python -m ingest_pipeline schedule --type web --storage weaviate --cron "0 2 * * *" + +# Start deployment server +python -m ingest_pipeline serve + +# View configuration +python -m ingest_pipeline config +``` + +### Code Quality +```bash +# Run linting +uv run ruff check . +uv run ruff format . + +# Type checking +uv run mypy ingest_pipeline + +# Install dev dependencies +uv sync --dev +``` + +## Architecture + +The pipeline follows a modular architecture with clear separation of concerns: + +- **Ingestors** (`ingest_pipeline/ingestors/`): Abstract base class pattern for different data sources (Firecrawl for web, Repomix for repositories) +- **Storage Adapters** (`ingest_pipeline/storage/`): Abstract base class for storage backends (Weaviate, Open WebUI) +- **Prefect Flows** (`ingest_pipeline/flows/`): Orchestration layer using Prefect for scheduling and task management +- **CLI** (`ingest_pipeline/cli/main.py`): Typer-based command interface with commands: `ingest`, `schedule`, `serve`, `config` + +## Key Implementation Details + +### Type Safety +- Strict typing enforced with no `Any` types allowed +- Modern typing syntax using `|` instead of `Union` +- Pydantic v2+ for all models and settings +- All models in `core/models.py` use TypedDict for metadata and strict Pydantic models + +### Configuration Management +- Settings loaded from `.env` file via Pydantic Settings +- Cached singleton pattern in `config/settings.py` using `@lru_cache` +- Environment-specific endpoints configured for local services (llm.lab, weaviate.yo, chat.lab) + +### Flow Orchestration +- Main ingestion flow in `flows/ingestion.py` with retry logic and task decorators +- Deployment scheduling in `flows/scheduler.py` supporting both cron and interval schedules +- Tasks use Prefect's `@task` decorator with retries and tags for monitoring + +### Storage Backends +- Weaviate: Uses batch ingestion with configurable batch size, automatic collection creation +- Open WebUI: Direct API integration for knowledge base management +- Both inherit from abstract `BaseStorage` class ensuring consistent interface + +## Service Endpoints + +- **LLM Proxy**: http://llm.lab (for embeddings and processing) +- **Weaviate**: http://weaviate.yo (vector database) +- **Open WebUI**: http://chat.lab (knowledge interface) +- **Firecrawl**: http://crawl.lab:30002 (web crawling service) + +## Important Constraints + +- Cyclomatic complexity must remain < 15 for all functions +- Maximum file size for ingestion: 1MB +- Batch size limits: 50-500 documents +- Concurrent task limit: 5 (configurable via MAX_CONCURRENT_TASKS) +- All async operations use proper async/await patterns \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9213285 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# Document Ingestion Pipeline + +A modular, type-safe Python application using Prefect for scheduling ingestion jobs from web/documentation sites (via Firecrawl) and Git repositories (via Repomix) into Weaviate or Open WebUI knowledge endpoints. + +## Features + +- **Multiple Data Sources**: + - Web/documentation sites via Firecrawl + - Git repositories via Repomix + +- **Multiple Storage Backends**: + - Weaviate vector database (self-hosted at http://weaviate.yo) + - Open WebUI knowledge endpoints (http://chat.lab) + +- **Scheduling & Orchestration**: + - Prefect-based workflow orchestration + - Cron and interval-based scheduling + - Concurrent task execution + +- **Type Safety**: + - Strict Python typing with no `Any` types + - Modern typing syntax (using `|` instead of `Union`) + - Pydantic models for validation + +- **Code Quality**: + - Modular architecture + - Cyclomatic complexity < 15 + - Clean separation of concerns + +## Installation + +```bash +# Install dependencies +pip install -r requirements.txt + +# Install repomix globally (required for repository ingestion) +npm install -g repomix + +# Copy and configure environment +cp .env.example .env +# Edit .env with your settings +``` + +## Usage + +### One-time Ingestion + +```bash +# Ingest a documentation site into Weaviate +python -m ingest_pipeline ingest https://docs.example.com --type web --storage weaviate + +# Ingest a repository into Open WebUI +python -m ingest_pipeline ingest https://github.com/user/repo --type repository --storage open_webui +``` + +### Scheduled Ingestion + +```bash +# Create a daily documentation crawl +python -m ingest_pipeline schedule daily-docs https://docs.example.com \ + --type documentation \ + --storage weaviate \ + --cron "0 2 * * *" + +# Create an hourly repository sync +python -m ingest_pipeline schedule repo-sync https://github.com/user/repo \ + --type repository \ + --storage open_webui \ + --interval 60 +``` + +### Serve Deployments + +```bash +# Start serving scheduled deployments +python -m ingest_pipeline serve +``` + +### Configuration + +```bash +# View current configuration +python -m ingest_pipeline config +``` + +## Architecture + +``` +ingest_pipeline/ +├── core/ # Core models and exceptions +│ ├── models.py # Pydantic models with strict typing +│ └── exceptions.py # Custom exceptions +├── ingestors/ # Data source ingestors +│ ├── base.py # Abstract base ingestor +│ ├── firecrawl.py # Web/docs ingestion via Firecrawl +│ └── repomix.py # Repository ingestion via Repomix +├── storage/ # Storage adapters +│ ├── base.py # Abstract base storage +│ ├── weaviate.py # Weaviate adapter +│ └── openwebui.py # Open WebUI adapter +├── flows/ # Prefect flows +│ ├── ingestion.py # Main ingestion flow +│ └── scheduler.py # Deployment scheduling +├── config/ # Configuration management +│ └── settings.py # Settings with Pydantic +├── utils/ # Utilities +│ └── vectorizer.py # Text vectorization +└── cli/ # CLI interface + └── main.py # Typer-based CLI +``` + +## Environment Variables + +- `FIRECRAWL_API_KEY`: API key for Firecrawl (optional) +- `LLM_ENDPOINT`: LLM proxy endpoint (default: http://llm.lab) +- `WEAVIATE_ENDPOINT`: Weaviate endpoint (default: http://weaviate.yo) +- `OPENWEBUI_ENDPOINT`: Open WebUI endpoint (default: http://chat.lab) +- `EMBEDDING_MODEL`: Model for embeddings (default: ollama/bge-m3:latest) + +## Vectorization + +The pipeline uses your LLM proxy at http://llm.lab with: +- Model: `ollama/gpt-oss:20b` for processing +- Embeddings: `ollama/bge-m3:latest` for vectorization + +## Storage Backends + +### Weaviate +- Endpoint: http://weaviate.yo +- Automatic collection creation +- Vector similarity search +- Batch ingestion support + +### Open WebUI +- Endpoint: http://chat.lab/docs +- Knowledge base integration +- Direct API access +- Document management + +## Development + +The codebase follows strict typing and quality standards: +- No use of `Any` type +- Modern Python typing syntax +- Cyclomatic complexity < 15 +- Modular, testable architecture + +## License + +MIT \ No newline at end of file diff --git a/basedpyright.json b/basedpyright.json new file mode 100644 index 0000000..97782ff --- /dev/null +++ b/basedpyright.json @@ -0,0 +1,24 @@ +{ + "include": [ + "ingest_pipeline" + ], + "exclude": [ + "**/__pycache__", + "**/.pytest_cache", + "**/node_modules", + ".venv" + ], + "reportCallInDefaultInitializer": "none", + "reportUnknownVariableType": "warning", + "reportUnknownMemberType": "warning", + "reportUnknownArgumentType": "warning", + "reportUnknownLambdaType": "warning", + "reportUnknownParameterType": "warning", + "reportMissingParameterType": "warning", + "reportUnannotatedClassAttribute": "warning", + "reportAny": "warning", + "reportUnusedCallResult": "none", + "reportUnnecessaryIsInstance": "none", + "reportImplicitOverride": "none", + "reportDeprecated": "warning" +} \ No newline at end of file diff --git a/docs/elysia.md b/docs/elysia.md new file mode 100644 index 0000000..d7c49d0 --- /dev/null +++ b/docs/elysia.md @@ -0,0 +1,248 @@ + 38 async def output_resources(): │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/services/use │ + │ r.py:223 in check_all_trees_timeout │ + │ │ + │ 220 │ │ Check all trees in all TreeManagers across all users and remove any │ + │ not been active in the last tree_timeout. │ + │ 221 │ │ """ │ + │ 222 │ │ for user_id in self.users: │ + │ ❱ 223 │ │ │ self.users[user_id]["tree_manager"].check_all_trees_timeout() │ + │ 224 │ │ + │ 225 │ def check_user_timeout(self, user_id: str): │ + │ 226 │ │ """ │ + ╰───────────────────────────────────────────────────────────────────────────────────╯ + KeyError: 'tree_manager' +[10:08:31] ERROR Job "check_timeouts (trigger: interval[0:00:29], next run at: 2025-09-15 10:09:00 base.py:195 + EDT)" raised an exception + ╭──────────────────────── Traceback (most recent call last) ────────────────────────╮ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/apscheduler/executors/b │ + │ ase.py:181 in run_coroutine_job │ + │ │ + │ 178 │ │ │ + │ 179 │ │ logger.info('Running job "%s" (scheduled at %s)', job, run_time) │ + │ 180 │ │ try: │ + │ ❱ 181 │ │ │ retval = await job.func(*job.args, **job.kwargs) │ + │ 182 │ │ except BaseException: │ + │ 183 │ │ │ exc, tb = sys.exc_info()[1:] │ + │ 184 │ │ │ formatted_tb = "".join(format_tb(tb)) │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/app.py:35 in │ + │ check_timeouts │ + │ │ + │ 32 │ + │ 33 async def check_timeouts(): │ + │ 34 │ user_manager = get_user_manager() │ + │ ❱ 35 │ await user_manager.check_all_trees_timeout() │ + │ 36 │ + │ 37 │ + │ 38 async def output_resources(): │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/services/use │ + │ r.py:223 in check_all_trees_timeout │ + │ │ + │ 220 │ │ Check all trees in all TreeManagers across all users and remove any │ + │ not been active in the last tree_timeout. │ + │ 221 │ │ """ │ + │ 222 │ │ for user_id in self.users: │ + │ ❱ 223 │ │ │ self.users[user_id]["tree_manager"].check_all_trees_timeout() │ + │ 224 │ │ + │ 225 │ def check_user_timeout(self, user_id: str): │ + │ 226 │ │ """ │ + ╰───────────────────────────────────────────────────────────────────────────────────╯ + KeyError: 'tree_manager' +[10:26:25] WARNING Run time of job "check_restart_clients (trigger: interval[0:00:31], next run at: base.py:176 + 2025-09-15 10:26:33 EDT)" was missed by 0:00:23.029499 + WARNING Run time of job "check_timeouts (trigger: interval[0:00:29], next run at: 2025-09-15 base.py:176 + 10:26:53 EDT)" was missed by 0:00:01.030848 + WARNING Run time of job "output_resources (trigger: interval[0:18:23], next run at: base.py:176 + 2025-09-15 10:33:44 EDT)" was missed by 0:11:04.063842 +[10:41:41] WARNING Run time of job "check_restart_clients (trigger: interval[0:00:31], next run at: base.py:176 + 2025-09-15 10:42:03 EDT)" was missed by 0:00:09.036380 + WARNING Run time of job "check_timeouts (trigger: interval[0:00:29], next run at: 2025-09-15 base.py:176 + 10:41:52 EDT)" was missed by 0:00:18.037363 + WARNING Run time of job "output_resources (trigger: interval[0:18:23], next run at: base.py:176 + 2025-09-15 10:52:07 EDT)" was missed by 0:07:57.071763 +[10:51:25] WARNING Run time of job "check_timeouts (trigger: interval[0:00:29], next run at: 2025-09-15 base.py:176 + 10:51:32 EDT)" was missed by 0:00:21.808772 + WARNING Run time of job "check_restart_clients (trigger: interval[0:00:31], next run at: base.py:176 + 2025-09-15 10:51:52 EDT)" was missed by 0:00:03.810823 +[10:51:32] ERROR Job "check_timeouts (trigger: interval[0:00:29], next run at: 2025-09-15 10:52:01 base.py:195 + EDT)" raised an exception + ╭──────────────────────── Traceback (most recent call last) ────────────────────────╮ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/apscheduler/executors/b │ + │ ase.py:181 in run_coroutine_job │ + │ │ + │ 178 │ │ │ + │ 179 │ │ logger.info('Running job "%s" (scheduled at %s)', job, run_time) │ + │ 180 │ │ try: │ + │ ❱ 181 │ │ │ retval = await job.func(*job.args, **job.kwargs) │ + │ 182 │ │ except BaseException: │ + │ 183 │ │ │ exc, tb = sys.exc_info()[1:] │ + │ 184 │ │ │ formatted_tb = "".join(format_tb(tb)) │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/app.py:35 in │ + │ check_timeouts │ + │ │ + │ 32 │ + │ 33 async def check_timeouts(): │ + │ 34 │ user_manager = get_user_manager() │ + │ ❱ 35 │ await user_manager.check_all_trees_timeout() │ + │ 36 │ + │ 37 │ + │ 38 async def output_resources(): │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/services/use │ + │ r.py:223 in check_all_trees_timeout │ + │ │ + │ 220 │ │ Check all trees in all TreeManagers across all users and remove any │ + │ not been active in the last tree_timeout. │ + │ 221 │ │ """ │ + │ 222 │ │ for user_id in self.users: │ + │ ❱ 223 │ │ │ self.users[user_id]["tree_manager"].check_all_trees_timeout() │ + │ 224 │ │ + │ 225 │ def check_user_timeout(self, user_id: str): │ + │ 226 │ │ """ │ + ╰───────────────────────────────────────────────────────────────────────────────────╯ + KeyError: 'tree_manager' +[10:51:43] ERROR Unexpected error: 'client_manager' error_handlers.py:32 +INFO: 127.0.0.1:50043 - "GET /feedback/metadata/b6c0f65db8197395b453a7777a5e4c44 HTTP/1.1" 500 Internal Server Error +ERROR: Exception in ASGI application +Traceback (most recent call last): + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi + result = await app( # type: ignore[func-returns-value] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__ + return await self.app(scope, receive, send) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__ + await super().__call__(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/applications.py", line 113, in __call__ + await self.middleware_stack(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 186, in __call__ + raise exc + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 164, in __call__ + await self.app(scope, receive, _send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 85, in __call__ + await self.app(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 63, in __call__ + await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app + raise exc + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app + await app(scope, receive, sender) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/routing.py", line 716, in __call__ + await self.middleware_stack(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/routing.py", line 736, in app + await route.handle(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/routing.py", line 290, in handle + await self.app(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/routing.py", line 78, in app + await wrap_app_handling_exceptions(app, request)(scope, receive, send) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app + raise exc + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app + await app(scope, receive, sender) + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/starlette/routing.py", line 75, in app + response = await f(request) + ^^^^^^^^^^^^^^^^ + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 302, in app + raw_response = await run_endpoint_function( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 213, in run_endpoint_function + return await dependant.call(**values) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/routes/feedback.py", line 81, in run_feedback_metadata + client_manager: ClientManager = user["client_manager"] + ~~~~^^^^^^^^^^^^^^^^^^ +KeyError: 'client_manager' + ERROR HTTP error occurred: Not Found error_handlers.py:14 +INFO: 127.0.0.1:50045 - "GET /icon.svg?d6c34577c7161f78 HTTP/1.1" 404 Not Found +INFO: 127.0.0.1:50045 - "GET /user/config/models HTTP/1.1" 200 OK +INFO: 127.0.0.1:50054 - "GET /user/config/models HTTP/1.1" 200 OK +[10:52:01] ERROR Job "check_timeouts (trigger: interval[0:00:29], next run at: 2025-09-15 10:52:30 base.py:195 + EDT)" raised an exception + ╭──────────────────────── Traceback (most recent call last) ────────────────────────╮ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/apscheduler/executors/b │ + │ ase.py:181 in run_coroutine_job │ + │ │ + │ 178 │ │ │ + │ 179 │ │ logger.info('Running job "%s" (scheduled at %s)', job, run_time) │ + │ 180 │ │ try: │ + │ ❱ 181 │ │ │ retval = await job.func(*job.args, **job.kwargs) │ + │ 182 │ │ except BaseException: │ + │ 183 │ │ │ exc, tb = sys.exc_info()[1:] │ + │ 184 │ │ │ formatted_tb = "".join(format_tb(tb)) │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/app.py:35 in │ + │ check_timeouts │ + │ │ + │ 32 │ + │ 33 async def check_timeouts(): │ + │ 34 │ user_manager = get_user_manager() │ + │ ❱ 35 │ await user_manager.check_all_trees_timeout() │ + │ 36 │ + │ 37 │ + │ 38 async def output_resources(): │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/services/use │ + │ r.py:223 in check_all_trees_timeout │ + │ │ + │ 220 │ │ Check all trees in all TreeManagers across all users and remove any │ + │ not been active in the last tree_timeout. │ + │ 221 │ │ """ │ + │ 222 │ │ for user_id in self.users: │ + │ ❱ 223 │ │ │ self.users[user_id]["tree_manager"].check_all_trees_timeout() │ + │ 224 │ │ + │ 225 │ def check_user_timeout(self, user_id: str): │ + │ 226 │ │ """ │ + ╰───────────────────────────────────────────────────────────────────────────────────╯ + KeyError: 'tree_manager' +^X[10:52:07] ERROR Job "output_resources (trigger: interval[0:18:23], next run at: 2025-09-15 11:10:30 base.py:195 + EDT)" raised an exception + ╭──────────────────────── Traceback (most recent call last) ────────────────────────╮ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/apscheduler/executors/b │ + │ ase.py:181 in run_coroutine_job │ + │ │ + │ 178 │ │ │ + │ 179 │ │ logger.info('Running job "%s" (scheduled at %s)', job, run_time) │ + │ 180 │ │ try: │ + │ ❱ 181 │ │ │ retval = await job.func(*job.args, **job.kwargs) │ + │ 182 │ │ except BaseException: │ + │ 183 │ │ │ exc, tb = sys.exc_info()[1:] │ + │ 184 │ │ │ formatted_tb = "".join(format_tb(tb)) │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/app.py:40 in │ + │ output_resources │ + │ │ + │ 37 │ + │ 38 async def output_resources(): │ + │ 39 │ user_manager = get_user_manager() │ + │ ❱ 40 │ await print_resources(user_manager, save_to_file=True) │ + │ 41 │ + │ 42 │ + │ 43 async def check_restart_clients(): │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/utils/resour │ + │ ces.py:59 in print_resources │ + │ │ + │ 56 │ user_manager: UserManager | None = None, save_to_file: bool = False │ + │ 57 ): │ + │ 58 │ if user_manager is not None: │ + │ ❱ 59 │ │ avg_user_memory, avg_tree_memory = await get_average_user_memory(us │ + │ 60 │ │ # avg_user_requests = await get_average_user_requests(user_manager) │ + │ 61 │ │ # num_users_db = await get_number_local_users_db(user_manager) │ + │ 62 │ + │ │ + │ /Users/trav/dev/elysia/.venv/lib/python3.12/site-packages/elysia/api/utils/resour │ + │ ces.py:37 in get_average_user_memory │ + │ │ + │ 34 │ avg_tree_memory = 0 │ + │ 35 │ for user in user_manager.users.values(): │ + │ 36 │ │ user_memory = 0 │ + │ ❱ 37 │ │ for tree in user["tree_manager"].trees.values(): │ + │ 38 │ │ │ user_memory += tree["tree"].detailed_memory_usage()["total"] / │ + │ 39 │ │ │ + │ 40 │ │ if len(user["tree_manager"].trees) > 0: │ + ╰───────────────────────────────────────────────────────────────────────────────────╯ + KeyError: 'tree_manager' \ No newline at end of file diff --git a/docs/tagging.md b/docs/tagging.md new file mode 100644 index 0000000..375d385 --- /dev/null +++ b/docs/tagging.md @@ -0,0 +1,108 @@ +Here are clear written examples of **metadata tagging** in both Open WebUI and Weaviate, showing how you can associate tags and structured metadata with knowledge objects for RAG and semantic search. + +*** + +### Example: Metadata Tagging in Open WebUI + +You send a document to the Open WebUI API endpoint, attaching metadata and tags in the content field as a JSON string: + +```json +POST http://localhost/api/v1/documents/create +Content-Type: application/json + +{ + "name": "policy_doc_2022", + "title": "2022 Policy Handbook", + "collection_name": "company_handbooks", + "filename": "policy_2022.pdf", + "content": "{\"tags\": [\"policy\", \"2022\", \"hr\"], \"source_url\": \"https://example.com/policy_2022.pdf\", \"author\": \"Jane Doe\"}" +} +``` +- The `"tags"` field is a list of labels for classification (policy, 2022, hr). +- The `"source_url"` and `"author"` fields provide additional metadata useful for retrieval, audit, and filtering.[1][2] + +For pipeline-based ingestion, you might design a function to extract and append metadata before vectorization: + +```python +metadata = { + "tags": ["policy", "2022"], + "source_url": document_url, + "author": document_author +} +embed_with_metadata(chunk, metadata) +``` +This metadata becomes part of your retrieval context in RAG workflows.[1] + +*** + +### Example: Metadata Tagging in Weaviate + +In Weaviate, metadata and tags are defined directly in the schema and attached to each object when added: + +**Schema definition:** + +```json +{ + "class": "Document", + "properties": [ + {"name": "title", "dataType": ["text"]}, + {"name": "tags", "dataType": ["text[]"]}, + {"name": "source_url", "dataType": ["text"]}, + {"name": "author", "dataType": ["text"]} + ] +} +``` + +**Object creation example:** + +```python +client.data_object.create( + data_object={ + "title": "2022 Policy Handbook", + "tags": ["policy", "2022", "hr"], + "source_url": "https://example.com/policy_2022.pdf", + "author": "Jane Doe" + }, + class_name="Document" +) +``` +- The `"tags"` field is a text array, ideal for semantic filtering and faceting. +- Other fields store provenance metadata, supporting advanced queries and data governance.[3][4][5] + +**Query with metadata filtering:** + +```python +result = ( + client.query + .get("Document", ["title", "tags", "author"]) + .with_filter({"path": ["tags"], "operator": "ContainsAny", "value": ["policy", "hr"]}) + .do() +) +``` +This retrieves documents classified with either "policy" or "hr" tags.[4][3] + +*** + +Both platforms support **metadata tagging** for documents, which enables powerful RAG scenarios, detailed filtering, and context-rich retrievals.[5][2][3][4][1] + +[1](https://www.reddit.com/r/OpenWebUI/comments/1hmmg9a/how_to_handle_metadata_during_vectorization/) +[2](https://github.com/open-webui/open-webui/discussions/4692) +[3](https://stackoverflow.com/questions/75006703/query-large-list-of-metadate-in-weaviate) +[4](https://weaviate.io/blog/enterprise-workflow-langchain-weaviate) +[5](https://docs.weaviate.io/academy/py/zero_to_mvp/schema_and_imports/schema) +[6](https://docs.weaviate.io/weaviate/api/graphql/additional-properties) +[7](https://weaviate.io/blog/sycamore-and-weaviate) +[8](https://docs.llamaindex.ai/en/stable/examples/vector_stores/WeaviateIndex_auto_retriever/) +[9](https://forum.weaviate.io/t/recommendations-for-metadata-or-knowledge-graphs/960) +[10](https://weaviate.io/blog/agent-workflow-automation-n8n-weaviate) +[11](https://github.com/open-webui/open-webui/discussions/9804) +[12](https://docs.quarkiverse.io/quarkus-langchain4j/dev/rag-weaviate.html) +[13](https://github.com/weaviate/weaviate-examples) +[14](https://docs.openwebui.com/getting-started/api-endpoints/) +[15](https://weaviate.io/blog/hybrid-search-for-web-developers) +[16](https://dev.to/stephenc222/how-to-use-weaviate-to-store-and-query-vector-embeddings-4b9b) +[17](https://helpdesk.egnyte.com/hc/en-us/articles/360035813612-Using-Metadata-in-the-WebUI) +[18](https://docs.datadoghq.com/integrations/weaviate/) +[19](https://docs.openwebui.com/features/) +[20](https://documentation.suse.com/suse-ai/1.0/html/openwebui-configuring/index.html) +[21](https://docs.openwebui.com/getting-started/env-configuration/) \ No newline at end of file diff --git a/ingest_pipeline/.env b/ingest_pipeline/.env new file mode 100644 index 0000000..0b3c055 --- /dev/null +++ b/ingest_pipeline/.env @@ -0,0 +1,38 @@ +# API Keys +FIRECRAWL_API_KEY=fc-your-api-key +OPENWEBUI_API_KEY= +WEAVIATE_API_KEY= + +# Endpoints +LLM_ENDPOINT=http://llm.lab +WEAVIATE_ENDPOINT=http://weaviate.yo +OPENWEBUI_ENDPOINT=http://chat.lab + +# Model Configuration +EMBEDDING_MODEL=ollama/bge-m3:latest +EMBEDDING_DIMENSION=1024 + +# Ingestion Settings +BATCH_SIZE=50 +MAX_FILE_SIZE=1000000 +MAX_CRAWL_DEPTH=5 +MAX_CRAWL_PAGES=100 + +# Storage Settings +DEFAULT_STORAGE_BACKEND=weaviate +COLLECTION_PREFIX=docs + +# Prefect Settings +PREFECT_API_URL=http://prefect.lab +PREFECT_API_KEY=0nR4WAkQ3q9MY1bjqATK6pVmolighvrS +PREFECT_WORK_POOL=default + +# Scheduling +DEFAULT_SCHEDULE_INTERVAL=60 + +# Performance +MAX_CONCURRENT_TASKS=5 +REQUEST_TIMEOUT=60 + +# Logging +LOG_LEVEL=INFO diff --git a/ingest_pipeline/__main__.py b/ingest_pipeline/__main__.py new file mode 100644 index 0000000..8084ff0 --- /dev/null +++ b/ingest_pipeline/__main__.py @@ -0,0 +1,6 @@ +"""Main entry point for the ingestion pipeline.""" + +from .cli.main import app + +if __name__ == "__main__": + app() diff --git a/ingest_pipeline/__pycache__/__main__.cpython-312.pyc b/ingest_pipeline/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efd775524c73e517eba461f142dd3e7cde0df19d GIT binary patch literal 312 zcmXv|y-EZz5S}C}w|d+IoFJrhK{jx;*jZ|4b4^$-qZi!GhHPBe>jirYpFw;O3p=|@ zCCC+a2iIAd#oH-nz8Sus`It)Jl1sC1E6`suxm*bW!TOzdIJWJ;@XmO8E-AKKgeiZW?rr@UCcu!d76}HM$W0 zX>y1XgL!Vdke*kydMNS2jiW{XLvs?_mJveMFx$ZNi;bUFpKQ8LMvs?U2FbBIjEp~x C7*)^! literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/__init__.py b/ingest_pipeline/cli/__init__.py new file mode 100644 index 0000000..606fdff --- /dev/null +++ b/ingest_pipeline/cli/__init__.py @@ -0,0 +1,5 @@ +"""CLI module for the ingestion pipeline.""" + +from .main import app + +__all__ = ["app"] diff --git a/ingest_pipeline/cli/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/cli/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..105e0faa5c7b4ccc541b2a3bc22a15b2daddc699 GIT binary patch literal 230 zcmX@j%ge<81QC;uXM_Xk#~=<2FhLogrGSj-3@Hpz3@MB$OgW6XOi@gX3@Oa%j8V*$ zESjuUYR*2M3c2|yr8%hzY57G8B^jv-nR)4{#U+{fc?t!Y1*tiid8v9Y8G#0AGTvfN zEGQ^q2D1G$S#Gh%$EV~c$H(7d$xY160}F%15_59m<5x0#2D#xEe{xQyetdjpUS>&r uyk0@&Ee;!qZFWT<=Yxzb76K9C%vZ!+jSWe|P9EzqIf$X>(&6b1k>qdFJ> literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/__pycache__/__init__.cpython-313.pyc b/ingest_pipeline/cli/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df91acfda06d842f2fee2b27c2e6a56bf834050d GIT binary patch literal 269 zcmey&%ge<81QC;uXM_Xk#~=<2FhLogrGSj748aUV48e@SOx}!MOhrrz48hF$j77}p zESjuUYR*2M3c2|yr8%hzY57G8B^jv-nR)4{#U+{fc?t!Y1*tiid8v9Y8G#0AGTvfN zEGQ^q2D1G$S#Gh%$EV~c$H(7d$xY160}F%15_59m<5x0#2D#yunSN+-YEiL%Nl{{% zeoAVYerir-ab}`E#Ey8F1^UT3nfmeZnR%Hd@$q^EmA5!-Ada#t0{IeTd$ACZ_`uA_ X$as@M=P85e18#v1^+xt04xlgqDP2g& literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/__pycache__/main.cpython-312.pyc b/ingest_pipeline/cli/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f862a1136ae7048cf2d2a37e832e186525787cbb GIT binary patch literal 25604 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 M$y+#U3zGx?7v@mSg8%>k literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/__pycache__/tui.cpython-312.pyc b/ingest_pipeline/cli/__pycache__/tui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18f3f7495dea2096e41b6d603ebb7fa1c80ba5d5 GIT binary patch literal 73288 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 None: + """ + 🚀 Modern Document Ingestion Pipeline + + [bold cyan]Advanced document processing and management platform[/bold cyan] + + Features: + • 🌐 Web scraping and crawling with Firecrawl + • 📦 Repository ingestion with Repomix + • 🗄️ Multiple storage backends (Weaviate, OpenWebUI) + • 📊 Modern TUI for collection management + • ⚡ Async processing with Prefect orchestration + • 🎨 Rich CLI with enhanced visuals + """ + if version: + console.print( + Panel( + "[bold magenta]Ingest Pipeline v0.1.0[/bold magenta]\n" + "[dim]Modern Document Ingestion & Management System[/dim]", + title="🚀 Version Info", + border_style="magenta" + ) + ) + raise typer.Exit() + + +@app.command() +def ingest( + source_url: str = typer.Argument(..., help="URL or path to ingest from"), + source_type: SourceType = typer.Option(SourceType.web, "--type", "-t", help="Type of source"), + storage: StorageBackend = typer.Option( + StorageBackend.weaviate, "--storage", "-s", help="Storage backend" + ), + collection: str = typer.Option( + None, "--collection", "-c", help="Target collection name (auto-generated if not specified)" + ), + validate: bool = typer.Option( + True, "--validate/--no-validate", help="Validate source before ingesting" + ), +) -> None: + """ + 🚀 Run a one-time ingestion job with enhanced progress tracking. + + This command processes documents from various sources and stores them in + your chosen backend with full progress visualization. + """ + # Enhanced startup message + console.print( + Panel( + f"[bold cyan]🚀 Starting Modern Ingestion[/bold cyan]\n\n" + f"[yellow]Source:[/yellow] {source_url}\n" + f"[yellow]Type:[/yellow] {source_type.value.title()}\n" + f"[yellow]Storage:[/yellow] {storage.value.replace('_', ' ').title()}\n" + f"[yellow]Collection:[/yellow] {collection or '[dim]Auto-generated[/dim]'}", + title="🔥 Ingestion Configuration", + border_style="cyan" + ) + ) + + async def run_with_progress() -> IngestionResult: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TaskProgressColumn(), + console=console, + ) as progress: + task = progress.add_task("🔄 Processing documents...", total=100) + + # Simulate progress updates during ingestion + progress.update(task, advance=20, description="🔗 Connecting to services...") + await asyncio.sleep(0.5) + + progress.update(task, advance=30, description="📄 Fetching documents...") + result = await run_ingestion( + url=source_url, + source_type=source_type.value, + storage_backend=storage.value, + collection_name=collection, + validate_first=validate, + ) + + progress.update(task, advance=50, description="✅ Ingestion complete!") + return result + + result = asyncio.run(run_with_progress()) + + # Enhanced results display + status_color = "green" if result.status.value == "completed" else "red" + + # Create results table with enhanced styling + table = Table( + title="📊 Ingestion Results", + title_style="bold magenta", + border_style="cyan", + header_style="bold blue" + ) + table.add_column("📋 Metric", style="cyan", no_wrap=True) + table.add_column("📈 Value", style=status_color, justify="right") + + # Add enhanced status icon + status_icon = "✅" if result.status.value == "completed" else "❌" + table.add_row("Status", f"{status_icon} {result.status.value.title()}") + + table.add_row("Documents Processed", f"📄 {result.documents_processed:,}") + table.add_row("Documents Failed", f"⚠️ {result.documents_failed:,}") + table.add_row("Duration", f"⏱️ {result.duration_seconds:.2f}s") + + if result.error_messages: + error_text = "\n".join(f"❌ {error}" for error in result.error_messages[:3]) + if len(result.error_messages) > 3: + error_text += f"\n... and {len(result.error_messages) - 3} more errors" + table.add_row("Errors", error_text) + + console.print(table) + + # Success celebration or error guidance + if result.status.value == "completed" and result.documents_processed > 0: + console.print( + Panel( + f"🎉 [bold green]Success![/bold green] {result.documents_processed} documents ingested\n\n" + f"💡 [dim]Try '[bold cyan]ingest modern[/bold cyan]' to explore your collections![/dim]", + title="✨ Ingestion Complete", + border_style="green" + ) + ) + elif result.error_messages: + console.print( + Panel( + "❌ [bold red]Ingestion encountered errors[/bold red]\n\n" + "💡 [dim]Check your configuration and try again[/dim]", + title="⚠️ Issues Detected", + border_style="red" + ) + ) + + +@app.command() +def schedule( + name: str = typer.Argument(..., help="Deployment name"), + source_url: str = typer.Argument(..., help="URL or path to ingest from"), + source_type: SourceType = typer.Option(SourceType.web, "--type", "-t", help="Type of source"), + storage: StorageBackend = typer.Option( + StorageBackend.weaviate, "--storage", "-s", help="Storage backend" + ), + cron: str | None = typer.Option(None, "--cron", "-c", help="Cron expression for scheduling"), + interval: int = typer.Option(60, "--interval", "-i", help="Interval in minutes"), + serve_now: bool = typer.Option(False, "--serve/--no-serve", help="Start serving immediately"), +) -> None: + """ + Create a scheduled deployment for recurring ingestion. + """ + console.print(f"[bold blue]Creating deployment: {name}[/bold blue]") + + deployment = create_scheduled_deployment( + name=name, + source_url=source_url, + source_type=source_type.value, + storage_backend=storage.value, + schedule_type="cron" if cron else "interval", + cron_expression=cron, + interval_minutes=interval, + ) + + console.print(f"[green]✓ Deployment '{name}' created[/green]") + + if serve_now: + console.print("[yellow]Starting deployment server...[/yellow]") + serve_deployments([deployment]) + + +@app.command() +def serve( + config_file: str | None = typer.Option( + None, "--config", "-c", help="Path to deployments config file" + ), + ui: str | None = typer.Option( + None, "--ui", help="Launch user interface (options: tui, web)" + ), +) -> None: + """ + 🚀 Serve configured deployments with optional UI interface. + + Launch the deployment server to run scheduled ingestion jobs, + optionally with a modern Terminal User Interface (TUI) or web interface. + """ + # Handle UI mode first + if ui == "tui": + console.print( + Panel( + "[bold cyan]🚀 Launching Enhanced TUI[/bold cyan]\n\n" + "[yellow]Features:[/yellow]\n" + "• 📊 Interactive collection management\n" + "• ⌨️ Enhanced keyboard navigation\n" + "• 🎨 Modern design with focus indicators\n" + "• 📄 Document browsing and search\n" + "• 🔄 Real-time status updates", + title="🎉 TUI Mode", + border_style="cyan" + ) + ) + from .tui import dashboard + dashboard() + return + elif ui == "web": + console.print("[red]Web UI not yet implemented. Use --ui tui for Terminal UI.[/red]") + return + elif ui: + console.print(f"[red]Unknown UI option: {ui}[/red]") + console.print("[yellow]Available options: tui, web[/yellow]") + return + + # Normal deployment server mode + if config_file: + # Load deployments from config + console.print(f"[yellow]Loading deployments from {config_file}[/yellow]") + # Implementation would load YAML/JSON config + else: + # Create example deployments + deployments = [ + create_scheduled_deployment( + name="docs-daily", + source_url="https://docs.example.com", + source_type="documentation", + storage_backend="weaviate", + schedule_type="cron", + cron_expression="0 2 * * *", # Daily at 2 AM + ), + create_scheduled_deployment( + name="repo-hourly", + source_url="https://github.com/example/repo", + source_type="repository", + storage_backend="open_webui", + schedule_type="interval", + interval_minutes=60, + ), + ] + + console.print( + "[bold green]Starting deployment server with example deployments[/bold green]" + ) + serve_deployments(deployments) + + +@app.command() +def tui() -> None: + """ + 🚀 Launch the enhanced Terminal User Interface. + + Quick shortcut for 'serve --ui tui' with modern keyboard navigation, + interactive collection management, and real-time status updates. + """ + console.print( + Panel( + "[bold cyan]🚀 Launching Enhanced TUI[/bold cyan]\n\n" + "[yellow]Features:[/yellow]\n" + "• 📊 Interactive collection management\n" + "• ⌨️ Enhanced keyboard navigation\n" + "• 🎨 Modern design with focus indicators\n" + "• 📄 Document browsing and search\n" + "• 🔄 Real-time status updates", + title="🎉 TUI Mode", + border_style="cyan" + ) + ) + from .tui import dashboard + dashboard() + + +@app.command() +def config() -> None: + """ + 📋 Display current configuration with enhanced formatting. + + Shows all configured endpoints, models, and settings in a beautiful + table format with status indicators. + """ + settings = get_settings() + + console.print( + Panel( + "[bold cyan]⚙️ System Configuration[/bold cyan]\n" + "[dim]Current pipeline settings and endpoints[/dim]", + title="🔧 Configuration", + border_style="cyan" + ) + ) + + # Enhanced configuration table + table = Table( + title="📊 Configuration Details", + title_style="bold magenta", + border_style="blue", + header_style="bold cyan", + show_lines=True + ) + table.add_column("🏷️ Setting", style="cyan", no_wrap=True, width=25) + table.add_column("🎯 Value", style="yellow", overflow="fold") + table.add_column("📊 Status", style="green", width=12, justify="center") + + # Add configuration rows with status indicators + def get_status_indicator(value: str | None) -> str: + return "✅ Set" if value else "❌ Missing" + + table.add_row( + "🤖 LLM Endpoint", + str(settings.llm_endpoint), + "✅ Active" + ) + table.add_row( + "🔥 Firecrawl Endpoint", + str(settings.firecrawl_endpoint), + "✅ Active" + ) + table.add_row( + "🗄️ Weaviate Endpoint", + str(settings.weaviate_endpoint), + get_status_indicator(str(settings.weaviate_api_key) if settings.weaviate_api_key else None) + ) + table.add_row( + "🌐 OpenWebUI Endpoint", + str(settings.openwebui_endpoint), + get_status_indicator(settings.openwebui_api_key) + ) + table.add_row( + "🧠 Embedding Model", + settings.embedding_model, + "✅ Set" + ) + table.add_row( + "💾 Default Storage", + settings.default_storage_backend.title(), + "✅ Set" + ) + table.add_row( + "📦 Default Batch Size", + f"{settings.default_batch_size:,}", + "✅ Set" + ) + table.add_row( + "⚡ Max Concurrent Tasks", + f"{settings.max_concurrent_tasks}", + "✅ Set" + ) + + console.print(table) + + # Additional helpful information + console.print( + Panel( + "💡 [bold cyan]Quick Tips[/bold cyan]\n\n" + "• Use '[bold]ingest list-collections[/bold]' to view all collections\n" + "• Use '[bold]ingest search[/bold]' to search content\n" + "• Configure API keys in your [yellow].env[/yellow] file\n" + "• Default collection names are auto-generated from URLs", + title="🚀 Usage Tips", + border_style="green" + ) + ) + + +@app.command() +def list_collections() -> None: + """ + 📋 List all collections across storage backends. + """ + console.print("[bold cyan]📚 Collection Overview[/bold cyan]") + asyncio.run(run_list_collections()) + + +@app.command() +def search( + query: str = typer.Argument(..., help="Search query"), + collection: str = typer.Option(None, "--collection", "-c", help="Target collection"), + backend: StorageBackend = typer.Option(StorageBackend.weaviate, "--backend", "-b", help="Storage backend"), + limit: int = typer.Option(10, "--limit", "-l", help="Result limit"), +) -> None: + """ + 🔍 Search across collections. + """ + console.print(f"[bold cyan]🔍 Searching for: {query}[/bold cyan]") + asyncio.run(run_search(query, collection, backend.value, limit)) + + +async def run_ingestion( + url: str, + source_type: str, + storage_backend: str, + collection_name: str | None = None, + validate_first: bool = True +) -> IngestionResult: + """ + Run ingestion with support for targeted collections. + """ + # Auto-generate collection name if not provided + if not collection_name: + from urllib.parse import urlparse + parsed = urlparse(url) + domain = parsed.netloc.replace(".", "_").replace("-", "_") + collection_name = f"{domain}_{source_type}" + + result = await create_ingestion_flow( + source_url=url, + source_type=source_type, + storage_backend=storage_backend, + collection_name=collection_name, + validate_first=validate_first, + ) + return result + + +async def run_list_collections() -> None: + """ + List collections across storage backends. + """ + from ..config import get_settings + from ..core.models import StorageBackend, StorageConfig + from ..storage.openwebui import OpenWebUIStorage + from ..storage.weaviate import WeaviateStorage + + settings = get_settings() + + console.print("🔍 [bold cyan]Scanning storage backends...[/bold cyan]") + + # Try to connect to Weaviate + weaviate_collections = [] + try: + weaviate_config = StorageConfig( + backend=StorageBackend.WEAVIATE, + endpoint=settings.weaviate_endpoint, + api_key=settings.weaviate_api_key, + collection_name="default", + ) + weaviate = WeaviateStorage(weaviate_config) + await weaviate.initialize() + + collections_list = weaviate.client.collections.list_all() if weaviate.client else [] + for collection in collections_list: + collection_obj = weaviate.client.collections.get(collection) if weaviate.client else None + if collection_obj: + count = collection_obj.aggregate.over_all(total_count=True).total_count or 0 + weaviate_collections.append((collection, count)) + except Exception as e: + console.print(f"❌ [red]Weaviate connection failed: {e}[/red]") + + # Try to connect to OpenWebUI + openwebui_collections = [] + try: + openwebui_config = StorageConfig( + backend=StorageBackend.OPEN_WEBUI, + endpoint=settings.openwebui_endpoint, + api_key=settings.openwebui_api_key, + collection_name="default", + ) + openwebui = OpenWebUIStorage(openwebui_config) + await openwebui.initialize() + + response = await openwebui.client.get("/api/v1/knowledge/") + response.raise_for_status() + knowledge_bases = response.json() + + for kb in knowledge_bases: + name = kb.get("name", "Unknown") + file_count = len(kb.get("files", [])) + openwebui_collections.append((name, file_count)) + except Exception as e: + console.print(f"❌ [red]OpenWebUI connection failed: {e}[/red]") + + # Display results + if weaviate_collections or openwebui_collections: + # Create results table + from rich.table import Table + table = Table( + title="📚 Collection Overview", + title_style="bold magenta", + border_style="cyan", + header_style="bold blue" + ) + table.add_column("🏷️ Collection", style="cyan", no_wrap=True) + table.add_column("📊 Backend", style="yellow") + table.add_column("📄 Documents", style="green", justify="right") + + # Add Weaviate collections + for name, count in weaviate_collections: + table.add_row(name, "🗄️ Weaviate", f"{count:,}") + + # Add OpenWebUI collections + for name, count in openwebui_collections: + table.add_row(name, "🌐 OpenWebUI", f"{count:,}") + + console.print(table) + else: + console.print("❌ [yellow]No collections found in any backend[/yellow]") + + +async def run_search(query: str, collection: str | None, backend: str, limit: int) -> None: + """ + Search across collections. + """ + from ..config import get_settings + from ..core.models import StorageBackend, StorageConfig + from ..storage.weaviate import WeaviateStorage + + settings = get_settings() + + console.print(f"🔍 Searching for: '[bold cyan]{query}[/bold cyan]'") + if collection: + console.print(f"📚 Target collection: [yellow]{collection}[/yellow]") + console.print(f"💾 Backend: [blue]{backend}[/blue]") + + results = [] + + try: + if backend == "weaviate": + weaviate_config = StorageConfig( + backend=StorageBackend.WEAVIATE, + endpoint=settings.weaviate_endpoint, + api_key=settings.weaviate_api_key, + collection_name=collection or "default", + ) + weaviate = WeaviateStorage(weaviate_config) + await weaviate.initialize() + + results_generator = weaviate.search(query, limit=limit) + async for doc in results_generator: + results.append({ + "title": getattr(doc, "title", "Untitled"), + "content": getattr(doc, "content", ""), + "score": getattr(doc, "score", 0.0), + "backend": "🗄️ Weaviate" + }) + + elif backend == "open_webui": + console.print("❌ [red]OpenWebUI search not yet implemented[/red]") + return + + except Exception as e: + console.print(f"❌ [red]Search failed: {e}[/red]") + return + + # Display results + if results: + from rich.table import Table + table = Table( + title=f"🔍 Search Results for '{query}'", + title_style="bold magenta", + border_style="green", + header_style="bold blue" + ) + table.add_column("📄 Title", style="cyan", max_width=40) + table.add_column("📝 Preview", style="white", max_width=60) + table.add_column("📊 Score", style="yellow", justify="right") + + for result in results[:limit]: + title = str(result["title"]) + title_display = title[:40] + "..." if len(title) > 40 else title + + content = str(result["content"]) + content_display = content[:60] + "..." if len(content) > 60 else content + + score = f"{result['score']:.3f}" + + table.add_row(title_display, content_display, score) + + console.print(table) + console.print(f"\n✅ [green]Found {len(results)} results[/green]") + else: + console.print("❌ [yellow]No results found[/yellow]") + + +if __name__ == "__main__": + app() diff --git a/ingest_pipeline/cli/tui/__init__.py b/ingest_pipeline/cli/tui/__init__.py new file mode 100644 index 0000000..c481b7c --- /dev/null +++ b/ingest_pipeline/cli/tui/__init__.py @@ -0,0 +1,13 @@ +"""Enhanced TUI package with keyboard navigation and modular architecture.""" + +from .app import CollectionManagementApp +from .models import CollectionInfo, DocumentInfo +from .utils import dashboard, run_textual_tui + +__all__ = [ + "CollectionManagementApp", + "CollectionInfo", + "DocumentInfo", + "dashboard", + "run_textual_tui", +] diff --git a/ingest_pipeline/cli/tui/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/cli/tui/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..910aca4eb5e164e9f9dd015de8a81b4015a5defc GIT binary patch literal 484 zcmYLFyH3O~5VZ5Wxhr`lI9cXix zyQPpFJy z!ap?<=art@ud>$Cwle&X;HKy|>P6WKr0A7uF-NLpu`gZYFFgysg}_2+;3%HaAws0e z5YjHL>wZ!P=1R(&e}(p2#Z2Q*XjPbVgzmxA_<*m?$w30EC;~{ZfwWJ}qx%#ieGyFwq6anVqRoS!%76(P^r_!D zbJ<;rvVsCd7T9zB|9{T;Z|DEd`IlTSBjI=9wa@3rc1Y5H@J{$k1&xg*G;T<`RFZUA zkC}2cR*K2I9XI3EL@6QK2{Tzul~QOY^_1CFO_$Q5-DP%HGo=jLX}#ObR+W-c&6RSZ zpD}x?y`^4Rib*FWJ$qHs6`Ig<+wr|m;{Ml5eT`hcr}^DCRl_3ZXC{bRtC@zTx`u6$ zMZ=vZ)S6c5>4qbJ+3E0E{Kuj zLnEn&M!GncJFqejjbt&Rbim~F9=*3?q)+eHpXeBQQh!Q+T7Tx#XFo~!I}mHyrf=7G zJT$sfe@=hCeRM8c>Y{0VSF=l!Xt(~tD}rkCd1^Vv z0^L$IJ^8YixLh|}FL4%SGo`uA+^+@oWiM74^%C>YCojRZDj1*l3d^H=cq4_Y~N5k%vmtG9a&g-^el#6s0 zyA9ldkR5OH|Hf-5{#L#rxnh_X*Wydk`$@QuU__Rd7ff!b>{! z8f9L(TsADjEtlB~fM)~+CKC=dE3FZ)yIj^x)p5#Yhg$+6;jiOsOig03uT7wM$ze!r`=}@*pxSBnHa(1Ek-4*FK zDXF{XTGvW?+r9LTwQXcM@q^sj_7|5E@Aqr~Lh@3Kx^-r0+*O4??#Knd{tOK`a?X)( zPSQC?BIqUQ9dK{+&xP6gKg_4YcB{syI7V6{y!A=I^-luoveby@6V1^H@YqmIqe+Q7 zPh0*QOz}PXEOlMV$ShNZi!RiML2doxA6+4l`L%dPT1+F;4acR`=INvQf`23&x3xOA zT{0mA!Ox7Lc7!vg>Q-!4^}jnA9w1ZE0Wy_Oc}drBP3mn!~u?4KygL-`st$(Zf0#eMb)#GRvx6caj|b7 z{fEJS8u`bO&z}0lu7BROy8X=k^jWqSC>mqI0fcvb=N7>9q=eKKz@G_W-C{A)`hnGrHPJU%)+Op0iXai4iA=zMQ@_y`e;b>|I-@HGnPv+clR?H$Y8}6 zJ8*#`2%qEAp&JeSD)GhM0(zSR`OdQwq^Lv7?KuH?!qL-X_w2M52RQFmWSSf>gdnEjxB_B=e_+~GnM*9X z2<76g6Ba8b-{h0Nr4v0cY6ma`&KZ#yqCYL7DG;UwHL5bOhKGsY0xCp(oWmanF_&&h z)8FHbKmV_vf52s9(#_I2${4H!F2o6hbhI8||W2k1p?O!y`QdX7K8 zuL8|?!d}Iorta&G*!(zO^LRUiVb$usZABVzhB9&9`4q*L#)V~{qcwb;2rX-y6qJSS zhTISqg+AUc%!NPg49E$y?ZD;WWSIi1@PAp`!c(5qeGA-1! zs8O;H*;x_Gv>gK(S_2~R&vtSC_QDn6?bNpBY?#yOzYqK^(qQMt4 zUA1kbyIW+rKE{Ioi3+7UKVUe93w`2VvXeBUsyX5sRZ8kL9d3XVKf~-f2-U&a6s|z0 zu4YyXk1(d{9f+~DCaC~IMpK!C010eer=PAQLy;ehL zLxu7A8xd*(DpAZMK3=mI5ysY$8AQ9#8e;XK$`FJp4ZF@rg<*?28G$b)eq;cK+wl0& zl41911!+Z~QwW9wTjn~~u2^rV%?2@TmEUfNWtb|%Hd^PSkheN#SdhIQou-6eWALeC zP@Xxk70U@y%C<#d{zOY4vX=O8@bWTPIhyAGn_$c{nsr0851COK9AX5SF9f{CiJs56pEr%%xcWlDt)h*2;Gq zNl=XPwH94|q4a4Yq=V2tnNKxG!r+!CVqqxi2e`zjaAgdX9bUvOcs&6J0=;MsjRjW= z0g7%@$J!eqh5F-V+cJeavX^#n8^_=ycj~Bt zYj+0@-OIkTrtEm2 zQ*Cy|7d;RlQmnZKl7auERH_E&qoai#5C7dwh(&bCXp|Aab+9D1A zKMMJ|Z%IxHtQ)In?i*7ra6GW3D!*&>#ZnNSTG-iTq)7#@Yf)tuJPZ2{@VNZqChk2I zAG(aALc8Q;YIZHmjt>Kjji6}Hh@T2XCQLUJK=Z(31l9q4os;|{6j!AG%=Rq14|>T; zFImm*zIF7A?4C79=&fsS-B+HEWOeNGi(FQ3e~YXxfZYFsLB>QDJsjj|@L17Z8qWvd z@HSaq!3+6=>2d@-m5;OUVukFFc>y(%*bx*Rwh$slNc;lsv5f72z5$vW{667fJ2-qV zdqmhlaYZTKS4P+4lJfE&ik}Bs{A@eg9>vknNpOy~fp_xs)RsIAQQOc2S0CqJHo^ad z%W7-BzI-qHis0+$iZXg%ImG$;>K}@)ZF-7x-M^xJ6jwua<6Mn^J9#>}B~L@d2N{h8J5rOypU-W?Pp3sO1z21@;oaT23CHV51oEdhx2kpVG=Yt*FQW z8F%nOY{lM0yTc!)aIp#0=B}~mcBV6=^5^exJnnrFO{FBI|AWKV4&UtgB0B`{{Xltc zMS1R?vTIH0f1vDMQFh-~2G(*lbcbSWORpneM%wzBzPjXf>VR+)iAt+^npo_iP^LxPQ8u9{zWZdw0kFyCWx7)8jr= z@$UYktLbB6>Spow%RisFmmU*S0KEPE&z`=QJ{dp;lj}Qs`chX;trMwt*R8}#&){le zaIN=+TZ1dTL#v4)2>bi8D*rm3Ob)DV+k1O(Wm{ogbdH_7J9Ba6*u~%S-bPO%c~0I? z&dbTdh8asGOY%lvGWjPmpE$UoB$6-j;^4ZHO%C21|M>Jrr`IJkZ@={4yb|IUESt*r zvl+0>>vH{fJD>a6pjW^P1mj-A>kUU{{a0dN?){GcG+c88YmpCc4XtmuyKzJiB>SA0c$jcEYpA)`}7u^yLY`Ag~5Ur9T@l6HP24g5+P e{YonSn{*i6-=yYZ^59MF*5F6RZzbLq+x=gss`Y#T literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/__pycache__/models.cpython-312.pyc b/ingest_pipeline/cli/tui/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e52ae11ed3b878a004e44faa8418ca5ac0e627e GIT binary patch literal 1061 zcma)5&ubJh6wdsf-R^d4%W4J15e1pc4tT0iMX*q+vR+!yy@Ygq!75wC=rZGr{Mbx z$J;~Sl`h6=J;t()G3ZrBigz%6TaxH}#=)4TA%u-W{15QG;xftBX*B4W#e^ z-l1dDKWTTT8&wT+^V6qQcVYUx>hz}1s>S}1*{`~b2gCg8#oB)xH-zc`GESL!**In9 zCF3Tp-;$lRG%RD5h}x{1|1t}-t1|{t=TusPi-^f~k(PPLaG6K47p94TEpV2zUC#Do zdoRr?ZoF&>9)o5i&W65|chwQ+74#G=D7d0vQNfaezJgkcYl>aJ0F_bP1yF+CJ8|cx z>s77g!qW6b)n7g`m#dogW?HJXL*J^)NE>b#_?N4P(AZRO{dYO(t~QtGQGOfp;F+W1 k4!{pX)3jr>@)g}Uvs>EI`&VZOerL-r@CQE;{OX>61KGx65? z1HXdbz&}Ca7m!ze0G0ZNcwlB{*I7HhOAc_P$T!}d`DW&u*|C4Sd9&Yx>(9@Ao&Ehu zulEQ3@YjaBdG`_A{M<`=FMG+x%Z4)r@4kl$M&+1K;@A#X+vjhtb}wL`Gbjgs;1e937HwG_pPlt*HZ)41*@-(594Ul*3MGp#|B;Zx8a*}HZ&xnICKvEVxC%CcXBh}+7p zkfK(MckD$mMvJ54TvTOrs?1E)BwLymq-SMJp4DN%jlgNp0U~aI;6PcohT_p+gEx=P zbP>usH1w=%!Z(^{Wx+MD)Ve^1Mzj0`GSgwsGpfrR#N2Hs_-=dCcV9=Spy7zuTx*G= zLMwp3_}4h%sH}vLW!a_S&Mtqjb8p`=tR1)jgVY-OOGhLUarfR8_MdW{Vc+S{aC|Jb zhYy+vhW7Gp=F4oxGa(c91vmVNPoU=n^iWxXy)M_5BIipwOofzhJ8Lu1Op+%R#GzJ8 zy!L)H*9W<)*Rw|~P5`z+*ikf-96FQV9+s>()p7W#&?a zk=w^S7M<|4>V#JhO;&Hm6xM~vYfWt$1E*K)_X81z+3!zeDYPmuieczh zXxX#ZzU?5(BXKB!v7pmVaFm~tMA3zz?!xrgb{Ny*$AWDf^C;Smj|c)Af@hfh-Q;oL zIZ)rtCOAYF1Z`p`n(>7De&4nhBfax%Obh+-T(1hE(TW@)eXjGV#$!DsM&X1}n8Pc& z{;ezv;63()sfNKB-3J3s7FijE0B@sGy_O@kLolsm3LfL!4U>IHhqX2~(&Cc>uRA=2 zBJ#sBBAcTuI+qc$#xND0_sz~(zZ0OjdiuRXzW5BlPm1)xjBCS+lnXh7!jd#!E|>(c_G#3xi$7CI{omy=Y0IU2r-Q4eYN+G-JoJkde|& zZxKDZBu(C3e^#Met<(iae%w^P>hSYN8u{<49BVTu^7B${(CD(!tPZbzyC4H@;BgHr zjEBNeC~|$vCD}91y%N_7T_Mqybt@Vx1U?c(cDFOs@GC5jHR6sbsR6sj*|2r>G#o7o z$$b06L>Le(>xbSHu8C+7c7I?4;D^SwQ4ZS6D@#wYeY zTjOwnpOGdK+c@L)rBO{(s>}k7Y&8;_R|durz<1#*%Qv^Loo$ZCNiN3YvrYIQHHPn0 zzx3YW2d%$$2QNx!paXd2J{ctPWFXT;srUel%hHSsRY+7j5UCoNN)2of%D7mZeee(n nU8VAo-ULPXd93^x{=L1vv9a-cZ}|58?{0kf-cOtQ1_=KFpJKgP literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/app.py b/ingest_pipeline/cli/tui/app.py new file mode 100644 index 0000000..209eb53 --- /dev/null +++ b/ingest_pipeline/cli/tui/app.py @@ -0,0 +1,181 @@ +"""Main TUI application with enhanced keyboard navigation.""" + +from textual import events +from textual.app import App +from textual.binding import Binding + +from ...storage.openwebui import OpenWebUIStorage +from ...storage.weaviate import WeaviateStorage +from .screens import CollectionOverviewScreen, HelpScreen +from .styles import TUI_CSS + + +class CollectionManagementApp(App[None]): + """Enhanced modern Textual application with comprehensive keyboard navigation.""" + + CSS = TUI_CSS + + BINDINGS = [ + Binding("q", "quit", "Quit"), + Binding("ctrl+c", "quit", "Quit"), + Binding("ctrl+q", "quit", "Quit"), + Binding("f1", "help", "Help"), + Binding("ctrl+h", "help", "Help"), + Binding("?", "help", "Quick Help"), + # Global navigation shortcuts + Binding("ctrl+r", "refresh_current", "Refresh Current Screen"), + Binding("ctrl+w", "close_current", "Close Current Screen"), + # Tab navigation shortcuts + Binding("ctrl+1", "dashboard_tab", "Dashboard", show=False), + Binding("ctrl+2", "collections_tab", "Collections", show=False), + Binding("ctrl+3", "analytics_tab", "Analytics", show=False), + ] + + weaviate: WeaviateStorage | None + openwebui: OpenWebUIStorage | None + + def __init__( + self, weaviate: WeaviateStorage | None = None, openwebui: OpenWebUIStorage | None = None + ): + super().__init__() + self.weaviate = weaviate + self.openwebui = openwebui + + def on_mount(self) -> None: + """Initialize the enhanced app with better branding.""" + self.title = "🚀 Enhanced Collection Management System" + self.sub_title = "Advanced Document Ingestion & Management Platform with Keyboard Navigation" + self.push_screen(CollectionOverviewScreen(self.weaviate, self.openwebui)) + + def action_help(self) -> None: + """Show comprehensive help information with all keyboard shortcuts.""" + help_md = """ +# 🚀 Enhanced Collection Management System + +## 🎯 Global Navigation +- **F1** / **Ctrl+H** / **?**: Show this help +- **Q** / **Ctrl+C** / **Ctrl+Q**: Quit application +- **Ctrl+R**: Refresh current screen +- **Ctrl+W**: Close current screen/dialog +- **Escape**: Go back/cancel current action + +## 📑 Tab Navigation +- **Tab** / **Shift+Tab**: Switch between tabs +- **Ctrl+1**: Jump to Dashboard tab +- **Ctrl+2**: Jump to Collections tab +- **Ctrl+3**: Jump to Analytics tab + +## 📚 Collections Management +- **R**: Refresh collections list +- **I**: Start new ingestion +- **M**: Manage documents in selected collection +- **S**: Search within selected collection +- **Ctrl+D**: Delete selected collection + +## 🗂️ Table Navigation +- **Arrow Keys** / **J/K/H/L**: Navigate table cells (Vi-style) +- **Home** / **End**: Jump to first/last row +- **Page Up** / **Page Down**: Scroll by page +- **Enter**: Select/activate current row +- **Space**: Toggle row selection +- **Ctrl+A**: Select all items +- **Ctrl+Shift+A**: Clear all selections + +## 📄 Document Management +- **Space**: Toggle document selection +- **Delete** / **Ctrl+D**: Delete selected documents +- **A**: Select all documents on page +- **N**: Clear selection +- **Page Up/Down**: Navigate between pages +- **Home/End**: Go to first/last page + +## 🔍 Search Features +- **/** : Quick search (focus search field) +- **Ctrl+F**: Focus search input +- **Enter**: Perform search +- **F3**: Repeat last search +- **Ctrl+R**: Clear search results +- **Escape**: Clear search/exit search mode + +## 📥 Ingestion Interface +- **1/2/3**: Select ingestion type (Web/Repository/Documentation) +- **Tab/Shift+Tab**: Navigate between fields +- **Enter**: Start ingestion process +- **Ctrl+I**: Quick start ingestion +- **Escape**: Cancel ingestion + +## 🎨 Visual Features +- Enhanced focus indicators with colored borders +- Smooth keyboard navigation with visual feedback +- Status indicators with real-time updates +- Progress bars with detailed status messages +- Responsive design with accessibility features + +## 💡 Pro Tips +- Use **Vi-style** navigation (J/K/H/L) for efficient movement +- **Tab** through interactive elements for keyboard-only operation +- Hold **Shift** with arrow keys for range selection (where supported) +- Use **Ctrl+** shortcuts for power user efficiency +- **Escape** is your friend - it cancels most operations safely + +## 🚀 Performance Features +- Lazy loading for large collections +- Paginated document views +- Background refresh operations +- Efficient memory management +- Responsive UI updates + +--- + +**Enjoy the enhanced keyboard-driven interface!** 🎉 + +*Press Escape, Enter, or Q to close this help.* + """ + self.push_screen(HelpScreen(help_md)) + + def action_refresh_current(self) -> None: + """Refresh the current screen if it supports it.""" + current_screen = self.screen + if hasattr(current_screen, "action_refresh"): + current_screen.action_refresh() + else: + self.notify("Current screen doesn't support refresh", severity="information") + + def action_close_current(self) -> None: + """Close current screen/dialog.""" + if len(self.screen_stack) > 1: # Don't close the main screen + self.pop_screen() + else: + self.notify("Cannot close main screen. Use Q to quit.", severity="warning") + + def action_dashboard_tab(self) -> None: + """Switch to dashboard tab in current screen.""" + current_screen = self.screen + if hasattr(current_screen, "action_tab_dashboard"): + current_screen.action_tab_dashboard() + + def action_collections_tab(self) -> None: + """Switch to collections tab in current screen.""" + current_screen = self.screen + if hasattr(current_screen, "action_tab_collections"): + current_screen.action_tab_collections() + + def action_analytics_tab(self) -> None: + """Switch to analytics tab in current screen.""" + current_screen = self.screen + if hasattr(current_screen, "action_tab_analytics"): + current_screen.action_tab_analytics() + + def on_key(self, event: events.Key) -> None: + """Handle global keyboard shortcuts.""" + # Handle global shortcuts that might not be bound to specific actions + if event.key == "ctrl+shift+?": + # Alternative help shortcut + self.action_help() + event.prevent_default() + elif event.key == "ctrl+alt+r": + # Force refresh all connections + self.notify("🔄 Refreshing all connections...", severity="information") + # This could trigger a full reinit if needed + event.prevent_default() + # No else clause needed - just handle our events diff --git a/ingest_pipeline/cli/tui/models.py b/ingest_pipeline/cli/tui/models.py new file mode 100644 index 0000000..a92821b --- /dev/null +++ b/ingest_pipeline/cli/tui/models.py @@ -0,0 +1,26 @@ +"""Data models and TypedDict definitions for the TUI.""" + +from typing import TypedDict + + +class CollectionInfo(TypedDict): + """Information about a collection.""" + + name: str + type: str + count: int + backend: str + status: str + last_updated: str + size_mb: float + + +class DocumentInfo(TypedDict): + """Information about a document.""" + + id: str + title: str + source_url: str + content_preview: str + word_count: int + timestamp: str diff --git a/ingest_pipeline/cli/tui/screens/__init__.py b/ingest_pipeline/cli/tui/screens/__init__.py new file mode 100644 index 0000000..4dff2da --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/__init__.py @@ -0,0 +1,18 @@ +"""Screen components for the TUI application.""" + +from .dashboard import CollectionOverviewScreen +from .dialogs import ConfirmDeleteScreen, ConfirmDocumentDeleteScreen +from .documents import DocumentManagementScreen +from .help import HelpScreen +from .ingestion import IngestionScreen +from .search import SearchScreen + +__all__ = [ + "CollectionOverviewScreen", + "IngestionScreen", + "SearchScreen", + "DocumentManagementScreen", + "ConfirmDeleteScreen", + "ConfirmDocumentDeleteScreen", + "HelpScreen", +] diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc74003d7a186a66973a63edb12eb6ff9b3512bb GIT binary patch literal 628 zcmYjOF>ezw7_~2#++EVpp+Z7};aHHuQ1b(z11%CAfT46bAz9}9HD}2_pX^Ie*!UIQ zyYWwu_yt&s7!U&!h;B^SPEz=W=kI;~{N9uNc{&{^(8dk%|Q#n#umBoH2b5&GD>_>8}CeL+jmRu<2gka-5JsJVgdR3ycXv0`^+I`z|1X2y$#(vNwQLSyW2=YV_&Lb#hV7rW zbFjhqX0&$(%+$@6XJ+6tnmj-eU=$z?Fbt4+csbj58Kqp;l)8d5+q~7x@c7bYTrjEU z_TL-7vW;h>noAoWDJr=_?D9lQH?lEIv<@CjN=iSXYwwEwJSFEAj3vEcOTuAEK(4G{ zWTQbl(SZ~#kXj1TF9fmiV=SRmw4$feGk6|7~?PK(RW0? aqGxMF*67|EJy@gicbZR<51BdeTYmu#u(c2X literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/dashboard.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/dashboard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1c14ebf1f38fb44078c03bc74c672733a78e794 GIT binary patch literal 25240 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/dialogs.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/dialogs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e9c5156a547b5e305816599b7c550bb5bea5634 GIT binary patch literal 10519 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/documents.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/documents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..129a1ffe1596f3328c8ecc47d69c1973861ec12a GIT binary patch literal 16520 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 RICnn7eRKK~3d>~V{{?mMyeI$w literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/help.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/help.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80b34ac626c337fe2833048c0493a24598f96894 GIT binary patch literal 3028 zcmZuzTWs6b89pRMk-A!z5!Z>mn2i%BGHT22x&;l~3+x4P5Y!mrbwG&~9L;lV)1gS^ zkdh<8D2yS+VxWTq3|oR?z_O>MNcP^x)=vet2l|y^QMfs9Z z(7Am7x$*n|^ZopDCX+<){QI@f7XA`P=wZWzaPm0p_dp0YS37ITLaKgYidbdEH7FsrvSfM-$~97(b##gqcfp~aQSn?wx3N-j z^?9P0_MAgYx@S7}$iLV^1k6SRMe@etP-Shz%2hZ@Tm$UMnueygN;Wdjs+aUV#Gv%#GgZ?=MecIG9ObV z%#+w0e+$iVIB_$r%8X-@oa1roX6!-hKxzu-aOP%AP2()dEcUhF0~Jo<^jr*gz?hC( z=mw|eW>BHiPcqMhywqO>k_fexg`>_gfO<+6i0ztH@_$@6;-}dh%>XwL+wTWnw{m6%ETQdKI%6zK~mFuRT(((HCHyde=Ue zBhmKE;C4gt?F!nFGpo{u+_Np8c+z+7R_u{{d>1Upk5l4RsBLhp*Lql}4ZweQ3eYQv zgJAR8cFaBGAGCTu0Gr3&BY0TQfvrW$Z}~giA*+bv&sl-g)?DeK9Ie`#DMhx3_F%U} zgf64SK!BotShv>@*zP#^MJIyOwBN$ZMDs9&G)_HCN0$X>j-K-9Dm5Ty47uDANZC8U z0Ea#pz9GDWmbjyM4Tbohge9(!ovwG3m=p}NSPdkDNl6e6AS@^0K+WI(`a^|zQW;dP zL@G5Csgy5zre~4*@I`w;w+(_7Td$h)fmQ_yR;CWF7@k=*y_zydbZ{(jec7$d1`~a8 zf#}s5Y?@>NrMX_sA{FN7zy0og<&xtmruU+9?#D36RW1hCNO7ogkzJR9pD@@3(q=u| zm&(*E>9hv&$D7!fsya1w+p8bbVROgUW&?$D6(hK51&J~@(-bS1%P30^lsPiE6O@J9 zM;z^e%4H5x=m{u+nNW4>`2(@t!g|@*51{xb3+}Hn-Hrayzh=gE@;!~?uYPiVJAY*} zeI@8kHLm__JOA!x`rVy;_r0^*`Jv79&{v(OA9W7ybf3KIJ?Fi(Alu7v~nCKKd-SRd?TlxohVjn0kS#*ghJy7TZD+ zA%_VHD9Q)v;Se))0LD+Vj2*@^Jp+ATfV|=-_Y+Hj4+57CgVAMYQ(EEM*}e!GBrw+{ zxZZalke{;_*#eB~74&5)w>G{yzV^oI8+Wg5rV3BexwWa)smJO5jdcH`^ovaarH29< zVRx2qry-j{*0J>x(KJ7)fqyW3Q$RbdY1b>d)v`#Mh8+W7!S$%$p=r8pJM6Q=u^}T- zMxHNET%P{X<>^Z^L1Lk4z$o(y1&R@Ui52X^`cj}11k;yUFVo6v2C*znD{z#BKoB)= z2Dh1J*+f<_83?ZKx9_8;vB@v<$F^d}{sWDP z{zAU;5!zODH`F_(a(*_lhFP1kSlnM|$;9)-a|1#{K@9YfQRH`ewK9AHXmASl@dF+T zeZe^V2!b7|P$%W_(A%%mNx+y^xFb+B1&-sMqBsA6&U}srzCf>ijt0L#7q-v^XgrIb U44+(Ma?*IS* literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/ingestion.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/ingestion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4cf3768ff1b1d4163b03f99f6ceb78e5bc808a2 GIT binary patch literal 12968 zcmcgT3v3(5mCOHiV|`YlWQmgX^CwCi$Brz^wqk{H{7GsyE=zG&Hf4U)-K8Dz zq{P?e?&Q{~<$hwPZO%%8YZSzH)d2<0z!e8yduda&7myZRF$?EvH0|9PIG{}AqF)^D zz1febU!3BAF2LE@nKy6Vym|BH&3nTiJDm;+o?kuo_UZRqDC&Lu&>pLrSxQ0X3dK== ziZgJ=h{11ww<%(b8vRBCmYE}_sM&8Oc}v6+wfe0jZ;jZZw4WwpV^5%1K z;S9W;{qg8{T;z}NVj?0zfo*R%#sNyNQMUKTV^T02;|1AqFfN3X_!$A1f+yo#Fmjai z=C#V!y$MN*$7IW3Y&;>!mZ9JXACcWd@gO1^1azUG6c=Rkkwk=-tw*Jx6b=CzI)0WH zgfPe7GXsTQlgtciLwO=TA{XjkN8<@0#LKRufF*c}-x~~_;bYuABepWguKsu=!iSIp zgR#*#l$2<^IV{9a3A`xo4GJOTg7z?KX6ai7iuxN651s+x1&IUO%=8P0ia~96Z*|aGkB{XN}94ks-x^a517ru$((^|T@BCdE%X$e=#m8~f) z2b>iva8`0vT=j~QOhg6F-sdahyA%(VbaDc8!Eso!4sE{8T1 zW8HbcYF3451DGnn-wy9;;Mpc_%k_>oVFvKwI(1xKd34rHQ_mUvwMQv$Prg79(M$Ge z^ZcMSzy@O+dxoDFi3bIajRnt!Pk{`^W1X@iqzjMi6nT^*o(o75;~;M1s`LcT1|ta& zTwV+X$N8iyI6mGv9v=@V%T2cS<2sBatszN>w1?$FQ3?uD07PF|ma+vTUXWx%kL*x- z2%O_bWYaVJ$bJx3vv@jwPBv_n3)D)1ACJq9Ban(=5cmn%uuZSy;vrFX?u&;KQ63f} zjzP9Sju(=Jq!(6SG_pwwj>wKtfE5EV{y8b>@IlJ%5AzW&X%|n2N2PWIaVa3<0)I9f zPl!p^VKt|KhH#l_59%QNEuDtstA;C-q}5OtjFZ$$7SK~_iGi9lOi}^8wWMieYK+z! z=^228w#vg{I~{t0@)~^tS?f00A|}Q`9nk?`Z&(ThglcFalt2Rf9M~&t!p|B=K%1C> zpQ8jowg&>CNKh050THQZ+4H~e?>a8>g4iVq!LwZ)f3}N{Oo-uNmojaE@$fhw0oB(P ziiEqQM7T?tY*DR8*%_Y@dH`9nQfIlM5j!>FnxR%~hvc{CsXMguf-Ox~X6Wi{bDumTGZxm>Pn&Pko0b5alC1(SC4^W=T^m?Nx#8!5-_qX#FAh-#&{~pS z&l#>8wUx93>LeT{c{meihB|c`UN&Aatf;k2Qe&!i(pun~v*MQXb&9iXP*1D%_E#;O zV-0?rhD#F$wO^fH$*w`*rnuZ*D$vV8b8>~(-I`|0Yf-eKg;Q%G6>2%quSB0-FV{40 zUR?!;I<#!vx@vCI$~vwFXjAzk>7(Lmuh(hZ=IQ}L71oa!CCfGBjj~9mw2|{%Z_;Kb zZ`PW*&0Nd%R&Dg0cbN{gm-WWo#rIi_|2@u5|N;I4U6Y2!W8sE2-X*S`FBEZ7me5L?z+kONgV8idW>B88<0pQMikg;Tt=la~|w0y`Rx@KDt?9+XaZN%1bCh3yF;0-6R;BpDbF(Aq)1K)V!-M}W-J@d%X4 z#xN&50{v?v`+`QqG1y4hR#2#-$o6z??b_zunLy)%5DFUr+uGxmZLl2#Mx>Z*8yCXS zpfHgvzzwKlgqT%clh6k(WZT)G5Dvy9r6ZgzVBxpe<$Z!}-3t6bx)o#`KNW(4 zzRa%JHrUo}0F9g=&j)p@CRi!XlI>G=YQO;r_pSmc zl@ekHeyA-8Ba&qrT*nH!N(o(GJI}7(73~DQ!XYsc3c<{1`1V1Y1fD2<{^~_m+0?;4 zAe%dZw}7<@W~?hnY)ze6wuoZr%9im&MC82=0TqRS+DO@+kVgW}x`5hEz}-*4ZiEU< za2>y45VpZfME%GLJ0OL{q8R4Miq+eTT7esj_-SbI-?+d0!dafG@BXQ?=T32Ds%q!W z1DWEXTg(uAWs9q?ZojtYl|6G0r8jL&Rd1W$I?w%6{2${t!|8_(r*=G*dg@5ZeKcFy zc;8|waeYXcoUZ#cRZ{Wg&7U8-Z>36V?zqcR<*oA_8TaFm&9XR`O-m=;t?_04^<930nb?GW3aPkCeW=!Asph$f zzX48@wk?h68|iZUH`OdCYqQsqEFT0DDjbZ2lRPV(=GD_<=ll2&sPn>+-` zdFpP*)@k!4*9F&1a)EBl&}}PirMomkDkm4{rbW6fO}E{qw+PsD#XR5~ka;)^nS4ui zOx@a`Sby>@Rp70@)$0H-j~Z`*e@3q}=U>y5-N$r3RHtxGyQ^ znKjHZW1Yq=IQa}59zHsFZ1~6t#heeqsMav4cqIoAL$dLsxr8v`Rcz`_*Y+y*x6Y>W z+jQ%npG{3Fn_xp;KH9D26zgq!iLGMR&UEe0 zjhF?e(6ew3U2#~%Wkwbt3kvQE90$jsVD+=!Vw^Hg8acz6R+#r9Sd99?aJg-sDzgHt zQHNb;;jV%;>9G14WJA1W9rmNuDbipeu$zEh&AYUWmKxK~Cd-BSbWK>jwq(aaa8pEh zR(eF?Uu8NlzGyO=Qh*4QCa&f zXET9oNi!|C885u=x&N#97clhFo{#*YnYS_{dUzhS}^@loqwA9WHAH2Nq-j!FJ>aLDGzV}b z&#EYFt+M}LQl6DVjQnZL6i+TdH zAS|DTJ_Ou{1auAw{g~{B1ohP?@dIsM*%Frcs3`1%yw{=#7>WZN>d0wFlxe_5Hb@}~ zic?!(S(#k8lFYlghyFR*5vLg#$zRSMZN55lT zDD7SO^#PcKj%iztp&D9WcfRKQR$-c{nLao(nk}rj`tYqnPqv|L#`;y)UFG$GooeV- zb_-n=pij&b`S}E7{uIskv9+F&m>@N(#(=Xf>=kfKW#Kp+4T~cBr-VJwxet!27E-0? z0@Uen-q9Dfh@`5_m5X7e64(Gi0|5kX?LM&q~kLgr6dYf}c;QLy!@XFcOh)3ss~tk8AzK4;`i*Uj20M%U#7 z3~2P5=^mJ60{(Q0@{CH0*P3ir&Z}sT>ncC2d6HpEh~t5^Q}9EV2{iX|*S_)+dpN=e zMQnf(pCBuea0xm_t*^RA94N6tM)Cee2$<3>mj^4^rqnYfID2(5 z9YaUNs)wUB$j?!n{V!l4zHGi?`fH1Y0^HOXI@MM^oC96ITmdn6+)}YtJ%%oqRZpxi zuO9tStDYjT4*#pPu(kOr67z;JXwPTcUFyz5##TbSYRy~sz2^|3B8N(&F>d{mS zxT;lUG-cgjR1o-k;HXM8TK_+z!eC+qqR~?}n8o#_s2l=|>Xx;k6je@I#+p_Q7d{~@ zQ1zgB3Apo3w7Q3L8*xuQzJctj0i0L1xOi{x~=f zrw1K2oc-oW24;Hmq!ZGXS1lT?0MBr(XZC?Xw;gC}St}o$m$Ynq)x@=5-=a|n?VM`6 z_6^!~kiI&hT~~g)Sjh(P-4Mpe!~XzHw^bX#YF+_l)EFE#S3hIzt4s2un_O?cuAZ>> zQZvSj-ymrRq=t)ckyIjSZ!dN6f8h7;@O#8VNn13`O_VS`<=SADTDfg`+*@DMZ zT$8#+glhwyYFMYQ>OMv?#`WfE$D|A4{v=L5w1ShM^K>l|lU4l!4>9`{osGpuu%AZK&zHZz!kwGZ@Tr7=P*BH? z2L*_Gfng^C*r~6*g3r5q;7EmL3)~A(>>AP=Ts$E6V**-y%mLRVpmQz|lH4kw_TsB9I;wc(}*}@JSPn z7Xab8yXRg9ypq-Xc_~C-RTCGZi$D_M#PtgSu@@mA2G%TGWdMviS?s6)Q9h*+qE+VU z{XJfb*L1HJQP&<03Sxd6IQR;Q5Jcg5#Ev5(rgmQ}7@0__2u zc~&{r{HhQ>VN!l)JzW|-+9c!-10C*V4SxGE3ua05#b8$LS1b9kwWuu+kj z*JoK>tj+?zA(puU4DcLBJmq+M3OftOSnOg1jtn@oWKMYI=6DGvrn4sLG0ZhuszW8mBVT`O2On^N7`OiJVqks>@ zsBrlbP5`64!Vu|4MFUsd)Ii;6A`+QsNT4X^u6^-BUfdX^6ME!#vdQASX%BK7&uw9o zHg+@!@m-EwBT3*T5z_vs68|G;)g=L)B@31@G{_J^#&ZxEq<}uF1e_FLnauv`#w9jc zvkav6B_bpv``7rE>NoVMkO*!n2Ubg{t=idMB6VWUEpj z+k)amEEJBzt(yqXk1N4U>|Lgx8R&a@u=-^g;0wUjo8yB59t9LHG*T{`VsNji za5NG>CwA&$AiRV?&tq~PlQ<^hkjVCd=R*8AxzS{TwdXBVoD`o!fMb~8Y7l&wd>#_n z1lo~c%e$y0O8hu@kd{Y+-m8QN+87oX90GM_%T|S%vVj+o6X;;ddlX;OUqZ!NZ|0hV z@ihPw&%=;OAQqfWB^6gXFL%yXFBET?wqlIh3 zrT4q#3zd&gJ9Fk|4ECmMUE}nDi^G7xS+~e^q?wMnV5;YMhIu;6R4p=WnqgB7LmB4D zJH=I3Ixct2nih(i7MZ3L)0A_dhilK}J+lv`OWNkfZk0TI-$WJFFS=W9xm$iw=2zZO4+f$OuInqe@DHJ$02&Mez?XOrjJu2*(_ zeK!fXPWR_rEASr9Fi+*-9n3IKti}6FrtdCOs}6PzF`oXL!;AY)r1zaj_4`x)lc|PJ zWthNPEGtm>GR$yZzu-84Aoyy2eQ34Txu$(fZmRu}w0rXmeRcC}Q`X(E=f+ne96Og;69 zl=}oAaJrz8yZUP1=bz8IORp4MF36Vm%(u~^F-?Svoi%5_eq@5Y9d93h2lo= zwx^iJ6>jnjxXImAebe-TmxgoTGjTTmE>}*{N|J&UK?zOL8mh?q+ng0f{M+0X%IWzX zV0TwRVBPM?>eCCd+@E3g=_5~hdcSMB(f7~R8`4eF4QBrNRKxxZa{!4zw@-EVe9^oq zRk{7fIrRP<&?ZqO_oqzbA98l+;13|r3YC8p{{vRyXLtK{8UEGKXB^s2ea~bVsyBbn z+}8si-``;vYBYcUp}scwc#Aa*HJjgR=qrJbA8a=awVHphV-Mti2>EvN4|nu2@bM#q zVW`vkBO{CXM#E6I`A43;W$^LihYdqp%|Cvm4)Q;#prOZ~)Eh7lK#(?*w3VdoB<&<= zH%YgWbi3WxVfx8qeXOt5^iHk8$6DX1vtZt1@pV)0v|6D0oemS`yY0S5Oz%8mf^NVG zhvtgUTZeZbajy&nWJdsE>uAM6o(Tj#18#e@#uh-+46aF{??o;Q1cEVe@{zp^?n0{f zCc|*22m>B6y?4;JZ_sz(D7n`tpzlXOLs@tRlTTqXg2^Z*7)nw+`ZjXC2{tVOi@u2o zdg;k!7ukLk9n$DY7Lr(q&O!lB43vP{Rd%WuGvWShBoOc#gzZpDE)G1d=wRII1x!k? z;xPkUru-}FUGt&4?z#nYUAClg!Q7ZFtynNud|;*Q?io5&-8Oe{f!^^hU5Fp;bI+(B zrkQ7Fcg!U+N|kBmg#pUc=Se4=H>vne?Ki5rf6jvE(vY zT9+IqiMw* zD9GM`Vt6e%Xv@PNlu}m5%)X4JDrY=tur$qkaumLAG<`_kNJaI1Qbl~Dgr8nk_y^?e z*D)dcwGB5(kR38Ta)J8Q2~j~J1#+QQGNSxLKoOc^trSh%ApTiERPH~~fLL~EwaWEb z0bTFQh|YyMxZ5Wxm!S%f5DcA?8&LxKD#;xxxd;w7U_S(aIzEPx4UveZTo8(bagN8a zh^W{eC?f(fP^@Y(k$aog4>5dDnBJfiMv$9U^e$!f>=tf96-XE*4nUGK84QMZsjl}a z*Lzg$FR9A+sOI-5&(Ems?@^n7Mjc+H4nyX@sKGQf2<7il-uu=ulc8zm%xqJ-yye#v Ievoed5B=|Cg#Z8m literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/__pycache__/search.cpython-312.pyc b/ingest_pipeline/cli/tui/screens/__pycache__/search.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..822f537bb91b832d7847ddf33f82890ccd423ffd GIT binary patch literal 10691 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/screens/dashboard.py b/ingest_pipeline/cli/tui/screens/dashboard.py new file mode 100644 index 0000000..1832328 --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/dashboard.py @@ -0,0 +1,542 @@ +"""Main dashboard screen with collections overview.""" + +from datetime import datetime + +from textual import work +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Container, Grid, Horizontal +from textual.reactive import reactive, var +from textual.screen import Screen +from textual.widgets import ( + Button, + Footer, + Header, + LoadingIndicator, + Rule, + Static, + TabbedContent, + TabPane, +) +from typing_extensions import override + +from ....storage.openwebui import OpenWebUIStorage +from ....storage.weaviate import WeaviateStorage +from ..models import CollectionInfo +from ..widgets import EnhancedDataTable, MetricsCard, StatusIndicator + + +class CollectionOverviewScreen(Screen[None]): + """Enhanced dashboard with modern design and metrics.""" + + total_documents: int = 0 + total_collections: int = 0 + active_backends: int = 0 + + BINDINGS = [ + Binding("q", "quit", "Quit"), + Binding("r", "refresh", "Refresh"), + Binding("i", "ingest", "Ingest"), + Binding("m", "manage", "Manage"), + Binding("s", "search", "Search"), + Binding("ctrl+d", "delete", "Delete"), + Binding("ctrl+1", "tab_dashboard", "Dashboard"), + Binding("ctrl+2", "tab_collections", "Collections"), + Binding("ctrl+3", "tab_analytics", "Analytics"), + Binding("tab", "next_tab", "Next Tab"), + Binding("shift+tab", "prev_tab", "Prev Tab"), + Binding("f1", "help", "Help"), + ] + + collections: var[list[CollectionInfo]] = var([]) + is_loading: var[bool] = var(False) + selected_collection: reactive[CollectionInfo | None] = reactive(None) + + def __init__(self, weaviate: WeaviateStorage | None, openwebui: OpenWebUIStorage | None): + super().__init__() + self.weaviate = weaviate + self.openwebui = openwebui + self.total_documents = 0 + self.total_collections = 0 + self.active_backends = 0 + + @override + def compose(self) -> ComposeResult: + yield Header(show_clock=True) + + with TabbedContent("Dashboard", "Collections", "Analytics"): + # Dashboard Tab + with TabPane("Dashboard", id="dashboard"): + yield Container( + Static("🚀 Collection Management System", classes="title"), + Static("Modern document ingestion and management platform", classes="subtitle"), + Rule(line_style="heavy"), + # Metrics Grid + Grid( + MetricsCard( + "Collections", str(self.total_collections), "Active collections" + ), + MetricsCard("Documents", str(self.total_documents), "Total indexed"), + MetricsCard("Backends", str(self.active_backends), "Connected services"), + MetricsCard("Status", "Online", "System health"), + classes="responsive-grid metrics-grid", + ), + Rule(line_style="dashed"), + # Quick Actions + Container( + Static("⚡ Quick Actions", classes="section-title"), + Horizontal( + Button("🔄 Refresh Data", id="quick_refresh", variant="primary"), + Button("📥 New Ingestion", id="quick_ingest", variant="success"), + Button("🔍 Search All", id="quick_search", variant="default"), + Button("⚙️ Settings", id="quick_settings", variant="default"), + classes="action_buttons", + ), + classes="card", + ), + # Recent Activity + Container( + Static("📊 Recent Activity", classes="section-title"), + Static( + "Loading recent activity...", id="activity_feed", classes="status-text" + ), + classes="card", + ), + classes="main_container", + ) + + # Collections Tab + with TabPane("Collections", id="collections"): + yield Container( + Static("📚 Collection Overview", classes="title"), + # Collection controls + Horizontal( + Button("🔄 Refresh", id="refresh_btn", variant="primary"), + Button("📥 Ingest", id="ingest_btn", variant="success"), + Button("🔧 Manage", id="manage_btn", variant="warning"), + Button("🗑️ Delete", id="delete_btn", variant="error"), + Button("🔍 Search", id="search_btn", variant="default"), + classes="button_bar", + ), + # Collection table with enhanced navigation + EnhancedDataTable(id="collections_table", classes="enhanced-table"), + # Status bar + Container( + Static("Ready", id="status_text", classes="status-text"), + StatusIndicator("Ready", id="connection_status"), + classes="status-bar", + ), + LoadingIndicator(id="loading", classes="pulse"), + classes="main_container", + ) + + # Analytics Tab + with TabPane("Analytics", id="analytics"): + yield Container( + Static("📈 Analytics & Insights", classes="title"), + # Analytics content + Container( + Static("🚧 Analytics Dashboard", classes="section-title"), + Static("Advanced analytics and insights coming soon!", classes="subtitle"), + # Placeholder charts area + Container( + Static("📊 Document Distribution", classes="chart-title"), + Static( + "Chart placeholder - integrate with visualization library", + classes="chart-placeholder", + ), + classes="card", + ), + Container( + Static("⏱️ Ingestion Timeline", classes="chart-title"), + Static("Timeline chart placeholder", classes="chart-placeholder"), + classes="card", + ), + classes="analytics-grid", + ), + classes="main_container", + ) + + yield Footer() + + async def on_mount(self) -> None: + """Initialize the screen with enhanced loading.""" + self.query_one("#loading").display = False + self.update_metrics() + self.refresh_collections() # Don't await, let it run as a worker + + def update_metrics(self) -> None: + """Update dashboard metrics with enhanced calculations.""" + self.total_collections = len(self.collections) + self.total_documents = sum(col["count"] for col in self.collections) + + # Count active backends + self.active_backends = 0 + if self.weaviate: + self.active_backends += 1 + if self.openwebui: + self.active_backends += 1 + + # Update metrics cards if they exist + try: + dashboard_tab = self.query_one("#dashboard") + metrics_cards = dashboard_tab.query(MetricsCard) + if len(metrics_cards) >= 4: + # Update existing cards with formatted values + metrics_cards[0].query_one(".metrics-value", Static).update( + f"{self.total_collections:,}" + ) + metrics_cards[1].query_one(".metrics-value", Static).update( + f"{self.total_documents:,}" + ) + metrics_cards[2].query_one(".metrics-value", Static).update( + str(self.active_backends) + ) + + # Update status card based on system health + if self.active_backends > 0 and self.total_collections > 0: + status_text = "🟢 Healthy" + status_class = "status-active" + elif self.active_backends > 0: + status_text = "🟡 Ready" + status_class = "status-warning" + else: + status_text = "🔴 Offline" + status_class = "status-error" + + metrics_cards[3].query_one(".metrics-value", Static).update(status_text) + metrics_cards[3].add_class(status_class) + + except Exception: + pass # Cards might not be rendered yet + + # Update activity feed with real data + try: + dashboard_tab = self.query_one("#dashboard") + activity_feed = dashboard_tab.query_one("#activity_feed", Static) + if self.collections: + recent_activity = [] + for col in self.collections[:3]: # Show top 3 collections + recent_activity.append( + f"📚 {col['name']}: {col['count']:,} docs ({col.get('size_mb', 0):.1f} MB)" + ) + activity_text = "\\n".join(recent_activity) + if len(self.collections) > 3: + activity_text += f"\\n... and {len(self.collections) - 3} more collections" + else: + activity_text = "No collections found. Start by creating your first ingestion!" + + activity_feed.update(activity_text) + except Exception: + pass + + @work(exclusive=True) + async def refresh_collections(self) -> None: + """Refresh collection data with enhanced loading feedback.""" + self.is_loading = True + loading_indicator = self.query_one("#loading") + status_text = self.query_one("#status_text", Static) + + loading_indicator.display = True + status_text.update("🔄 Refreshing collections...") + + try: + collections = [] + + # Get Weaviate collections + if self.weaviate: + try: + status_text.update("🔗 Connecting to Weaviate...") + await self.weaviate.initialize() + weaviate_collections = await self.list_weaviate_collections() + collections.extend(weaviate_collections) + status_text.update("✅ Weaviate collections loaded") + except Exception as e: + self.notify(f"❌ Weaviate error: {e}", severity="error") + status_text.update("❌ Weaviate connection failed") + + # Get OpenWebUI collections + if self.openwebui: + try: + status_text.update("🔗 Connecting to OpenWebUI...") + await self.openwebui.initialize() + openwebui_collections = await self.list_openwebui_collections() + collections.extend(openwebui_collections) + status_text.update("✅ OpenWebUI collections loaded") + except Exception as e: + self.notify(f"❌ OpenWebUI error: {e}", severity="error") + status_text.update("❌ OpenWebUI connection failed") + + self.collections = collections + await self.update_collections_table() + self.update_metrics() + status_text.update(f"✨ Ready - {len(collections)} collections loaded") + + # Update connection status + connection_status = self.query_one("#connection_status", StatusIndicator) + if collections: + connection_status.update_status("✓ Active") + else: + connection_status.update_status("No Data") + + except Exception as e: + status_text.update(f"❌ Error: {e}") + self.notify(f"Failed to refresh: {e}", severity="error") + finally: + self.is_loading = False + loading_indicator.display = False + + async def list_weaviate_collections(self) -> list[CollectionInfo]: + """List Weaviate collections with enhanced metadata.""" + if not self.weaviate: + return [] + + try: + collections = [] + collections_list = ( + self.weaviate.client.collections.list_all() + if self.weaviate and self.weaviate.client + else [] + ) + for collection in collections_list: + collection_obj = ( + self.weaviate.client.collections.get(collection) + if self.weaviate and self.weaviate.client + else None + ) + if not collection_obj: + continue + count = collection_obj.aggregate.over_all(total_count=True).total_count or 0 + + # Estimate size + size_mb = count * 0.01 # Rough estimate + + collection_info = CollectionInfo( + name=collection, + type="weaviate", + count=count, + backend="🗄️ Weaviate", + status="✓ Active", + last_updated=datetime.now().strftime("%Y-%m-%d %H:%M"), + size_mb=size_mb, + ) + collections.append(collection_info) + + return collections + except Exception as e: + self.notify(f"Error listing Weaviate collections: {e}", severity="error") + return [] + + async def list_openwebui_collections(self) -> list[CollectionInfo]: + """List OpenWebUI collections with enhanced metadata.""" + if not self.openwebui: + return [] + + try: + response = await self.openwebui.client.get("/api/v1/knowledge/") + response.raise_for_status() + knowledge_bases = response.json() + + collections = [] + for kb in knowledge_bases: + file_count = len(kb.get("files", [])) + size_mb = file_count * 0.5 # Rough estimate + + collection_info = CollectionInfo( + name=kb.get("name", "Unknown"), + type="openwebui", + count=file_count, + backend="🌐 OpenWebUI", + status="✓ Active", + last_updated=kb.get("updated_at", datetime.now().strftime("%Y-%m-%d %H:%M")), + size_mb=size_mb, + ) + collections.append(collection_info) + + return collections + except Exception as e: + self.notify(f"Error listing OpenWebUI collections: {e}", severity="error") + return [] + + async def update_collections_table(self) -> None: + """Update the collections table with enhanced formatting.""" + table = self.query_one("#collections_table", EnhancedDataTable) + table.clear() + + # Add enhanced columns + table.add_columns("Collection", "Backend", "Documents", "Size", "Status", "Updated") + + # Add rows with enhanced formatting + for collection in self.collections: + # Format size + size_str = f"{collection['size_mb']:.1f} MB" + if collection["size_mb"] > 1000: + size_str = f"{collection['size_mb'] / 1000:.1f} GB" + + # Format document count + doc_count = f"{collection['count']:,}" + + table.add_row( + collection["name"], + collection["backend"], + doc_count, + size_str, + collection["status"], + collection["last_updated"], + ) + + def get_selected_collection(self) -> CollectionInfo | None: + """Get the currently selected collection.""" + table = self.query_one("#collections_table", EnhancedDataTable) + try: + if table.cursor_coordinate.row < len(self.collections): + return self.collections[table.cursor_coordinate.row] + except (AttributeError, IndexError): + pass + return None + + # Action methods + def action_refresh(self) -> None: + """Refresh collections.""" + self.refresh_collections() + + def action_ingest(self) -> None: + """Show enhanced ingestion dialog.""" + selected = self.get_selected_collection() + if selected: + from .ingestion import IngestionScreen + self.app.push_screen(IngestionScreen(selected)) + else: + self.notify("🔍 Please select a collection first", severity="warning") + + def action_manage(self) -> None: + """Manage documents in selected collection.""" + selected = self.get_selected_collection() + if selected: + if selected["type"] == "weaviate": + from .documents import DocumentManagementScreen + self.app.push_screen(DocumentManagementScreen(selected, self.weaviate)) + else: + self.notify( + "🚧 Document management only available for Weaviate", severity="warning" + ) + else: + self.notify("🔍 Please select a collection first", severity="warning") + + def action_search(self) -> None: + """Search in selected collection.""" + selected = self.get_selected_collection() + if selected: + from .search import SearchScreen + self.app.push_screen(SearchScreen(selected, self.weaviate, self.openwebui)) + else: + self.notify("🔍 Please select a collection first", severity="warning") + + def action_delete(self) -> None: + """Delete selected collection.""" + selected = self.get_selected_collection() + if selected: + from .dialogs import ConfirmDeleteScreen + self.app.push_screen(ConfirmDeleteScreen(selected, self)) + else: + self.notify("🔍 Please select a collection first", severity="warning") + + def action_tab_dashboard(self) -> None: + """Switch to dashboard tab.""" + tabs = self.query_one(TabbedContent) + tabs.active = "dashboard" + + def action_tab_collections(self) -> None: + """Switch to collections tab.""" + tabs = self.query_one(TabbedContent) + tabs.active = "collections" + + def action_tab_analytics(self) -> None: + """Switch to analytics tab.""" + tabs = self.query_one(TabbedContent) + tabs.active = "analytics" + + def action_next_tab(self) -> None: + """Switch to next tab.""" + tabs = self.query_one(TabbedContent) + tab_ids = ["dashboard", "collections", "analytics"] + current = tabs.active + try: + current_index = tab_ids.index(current) + next_index = (current_index + 1) % len(tab_ids) + tabs.active = tab_ids[next_index] + except (ValueError, AttributeError): + tabs.active = tab_ids[0] + + def action_prev_tab(self) -> None: + """Switch to previous tab.""" + tabs = self.query_one(TabbedContent) + tab_ids = ["dashboard", "collections", "analytics"] + current = tabs.active + try: + current_index = tab_ids.index(current) + prev_index = (current_index - 1) % len(tab_ids) + tabs.active = tab_ids[prev_index] + except (ValueError, AttributeError): + tabs.active = tab_ids[0] + + def action_help(self) -> None: + """Show help screen.""" + from .help import HelpScreen + help_md = """ +# 🚀 Modern Collection Management System + +## Navigation +- **Tab** / **Shift+Tab**: Switch between tabs +- **Ctrl+1/2/3**: Direct tab access +- **Enter**: Activate selected item +- **Escape**: Go back/cancel +- **Arrow Keys**: Navigate within tables +- **Home/End**: Jump to first/last row +- **Page Up/Down**: Scroll by page + +## Collections +- **R**: Refresh collections +- **I**: Start ingestion +- **M**: Manage documents +- **S**: Search collection +- **Ctrl+D**: Delete collection + +## Table Navigation +- **Up/Down** or **J/K**: Navigate rows +- **Space**: Toggle selection +- **Ctrl+A**: Select all +- **Ctrl+Shift+A**: Clear selection + +## General +- **Q** / **Ctrl+C**: Quit application +- **F1**: Show this help + +Enjoy the enhanced interface! 🎉 + """ + self.app.push_screen(HelpScreen(help_md)) + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses with enhanced feedback.""" + button_id = event.button.id + + # Add visual feedback + event.button.add_class("pressed") + self.call_later(self.remove_pressed_class, event.button) + + if button_id == "refresh_btn" or button_id == "quick_refresh": + self.action_refresh() + elif button_id == "ingest_btn" or button_id == "quick_ingest": + self.action_ingest() + elif button_id == "manage_btn": + self.action_manage() + elif button_id == "delete_btn": + self.action_delete() + elif button_id == "search_btn" or button_id == "quick_search": + self.action_search() + elif button_id == "quick_settings": + self.notify("⚙️ Settings panel coming soon!", severity="information") + + def remove_pressed_class(self, button: Button) -> None: + """Remove pressed visual feedback class.""" + button.remove_class("pressed") diff --git a/ingest_pipeline/cli/tui/screens/dialogs.py b/ingest_pipeline/cli/tui/screens/dialogs.py new file mode 100644 index 0000000..5840693 --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/dialogs.py @@ -0,0 +1,189 @@ +"""Dialog screens for confirmations and user interactions.""" + +from typing import TYPE_CHECKING + +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Container, Horizontal +from textual.screen import Screen +from textual.widgets import Button, Footer, Header, LoadingIndicator, Static +from typing_extensions import override + +from ..models import CollectionInfo + +if TYPE_CHECKING: + from .dashboard import CollectionOverviewScreen + from .documents import DocumentManagementScreen + + +class ConfirmDeleteScreen(Screen[None]): + """Screen for confirming collection deletion.""" + + collection: CollectionInfo + parent_screen: "CollectionOverviewScreen" + + BINDINGS = [ + Binding("escape", "app.pop_screen", "Cancel"), + Binding("y", "confirm_delete", "Yes"), + Binding("n", "app.pop_screen", "No"), + Binding("enter", "confirm_delete", "Confirm"), + ] + + def __init__(self, collection: CollectionInfo, parent_screen: "CollectionOverviewScreen"): + super().__init__() + self.collection = collection + self.parent_screen = parent_screen + + @override + def compose(self) -> ComposeResult: + yield Header() + yield Container( + Static("⚠️ Confirm Deletion", classes="title warning"), + Static(f"Are you sure you want to delete collection '{self.collection['name']}'?"), + Static(f"Backend: {self.collection['backend']}"), + Static(f"Documents: {self.collection['count']:,}"), + Static("This action cannot be undone!", classes="warning"), + Static("Press Y to confirm, N or Escape to cancel", classes="subtitle"), + Horizontal( + Button("✅ Yes, Delete (Y)", id="yes_btn", variant="error"), + Button("❌ Cancel (N)", id="no_btn", variant="default"), + classes="action_buttons", + ), + classes="main_container center", + ) + yield Footer() + + def on_mount(self) -> None: + """Initialize the screen with focus on cancel button for safety.""" + self.query_one("#no_btn").focus() + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses.""" + if event.button.id == "yes_btn": + self.action_confirm_delete() + elif event.button.id == "no_btn": + self.app.pop_screen() + + def action_confirm_delete(self) -> None: + """Confirm deletion.""" + self.run_worker(self.delete_collection()) + + async def delete_collection(self) -> None: + """Delete the collection.""" + try: + if self.collection["type"] == "weaviate" and self.parent_screen.weaviate: + # Delete Weaviate collection + if self.parent_screen.weaviate and self.parent_screen.weaviate.client: + self.parent_screen.weaviate.client.collections.delete(self.collection["name"]) + self.notify( + f"Deleted Weaviate collection: {self.collection['name']}", + severity="information", + ) + elif self.collection["type"] == "openwebui" and self.parent_screen.openwebui: + # Delete OpenWebUI knowledge base + response = await self.parent_screen.openwebui.client.delete( + f"/api/v1/knowledge/{self.collection['name']}" + ) + response.raise_for_status() + self.notify( + f"Deleted OpenWebUI collection: {self.collection['name']}", + severity="information", + ) + + # Refresh parent screen + self.parent_screen.refresh_collections() # Don't await, let it run as a worker + self.app.pop_screen() + + except Exception as e: + self.notify(f"Failed to delete collection: {e}", severity="error") + + +class ConfirmDocumentDeleteScreen(Screen[None]): + """Screen for confirming document deletion.""" + + doc_ids: list[str] + collection: CollectionInfo + parent_screen: "DocumentManagementScreen" + + BINDINGS = [ + Binding("escape", "app.pop_screen", "Cancel"), + Binding("y", "confirm_delete", "Yes"), + Binding("n", "app.pop_screen", "No"), + Binding("enter", "confirm_delete", "Confirm"), + ] + + def __init__( + self, + doc_ids: list[str], + collection: CollectionInfo, + parent_screen: "DocumentManagementScreen", + ): + super().__init__() + self.doc_ids = doc_ids + self.collection = collection + self.parent_screen = parent_screen + + @override + def compose(self) -> ComposeResult: + yield Header() + yield Container( + Static("⚠️ Confirm Document Deletion", classes="title warning"), + Static( + f"Are you sure you want to delete {len(self.doc_ids)} documents from '{self.collection['name']}'?" + ), + Static("This action cannot be undone!", classes="warning"), + Static("Press Y to confirm, N or Escape to cancel", classes="subtitle"), + Horizontal( + Button("✅ Yes, Delete (Y)", id="yes_btn", variant="error"), + Button("❌ Cancel (N)", id="no_btn", variant="default"), + classes="action_buttons", + ), + LoadingIndicator(id="loading"), + classes="main_container center", + ) + yield Footer() + + def on_mount(self) -> None: + """Initialize the screen with focus on cancel button for safety.""" + self.query_one("#loading").display = False + self.query_one("#no_btn").focus() + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses.""" + if event.button.id == "yes_btn": + self.action_confirm_delete() + elif event.button.id == "no_btn": + self.app.pop_screen() + + def action_confirm_delete(self) -> None: + """Confirm deletion.""" + self.run_worker(self.delete_documents()) + + async def delete_documents(self) -> None: + """Delete the selected documents.""" + loading = self.query_one("#loading") + loading.display = True + + try: + if self.parent_screen.weaviate: + # Delete documents + results = await self.parent_screen.weaviate.delete_documents(self.doc_ids) + + # Count successful deletions + successful = sum(1 for success in results.values() if success) + failed = len(results) - successful + + if successful > 0: + self.notify(f"Deleted {successful} documents", severity="information") + if failed > 0: + self.notify(f"Failed to delete {failed} documents", severity="error") + + # Clear selection and refresh parent screen + self.parent_screen.selected_docs.clear() + await self.parent_screen.load_documents() + self.app.pop_screen() + + except Exception as e: + self.notify(f"Failed to delete documents: {e}", severity="error") + finally: + loading.display = False diff --git a/ingest_pipeline/cli/tui/screens/documents.py b/ingest_pipeline/cli/tui/screens/documents.py new file mode 100644 index 0000000..6755526 --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/documents.py @@ -0,0 +1,279 @@ +"""Document management screen with enhanced navigation.""" + +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Container, Horizontal +from textual.screen import Screen +from textual.widgets import Button, Footer, Header, Label, LoadingIndicator, Static +from typing_extensions import override + +from ....storage.weaviate import WeaviateStorage +from ..models import CollectionInfo, DocumentInfo +from ..widgets import EnhancedDataTable + + +class DocumentManagementScreen(Screen[None]): + """Screen for managing documents within a collection with enhanced keyboard navigation.""" + + collection: CollectionInfo + weaviate: WeaviateStorage | None + documents: list[DocumentInfo] + selected_docs: set[str] + current_offset: int + page_size: int + + BINDINGS = [ + Binding("escape", "app.pop_screen", "Back"), + Binding("r", "refresh", "Refresh"), + Binding("delete", "delete_selected", "Delete Selected"), + Binding("a", "select_all", "Select All"), + Binding("ctrl+a", "select_all", "Select All"), + Binding("n", "select_none", "Clear Selection"), + Binding("ctrl+shift+a", "select_none", "Clear Selection"), + Binding("space", "toggle_selection", "Toggle Selection"), + Binding("ctrl+d", "delete_selected", "Delete Selected"), + Binding("pageup", "prev_page", "Previous Page"), + Binding("pagedown", "next_page", "Next Page"), + Binding("home", "first_page", "First Page"), + Binding("end", "last_page", "Last Page"), + ] + + def __init__(self, collection: CollectionInfo, weaviate: WeaviateStorage | None): + super().__init__() + self.collection = collection + self.weaviate = weaviate + self.documents: list[DocumentInfo] = [] + self.selected_docs: set[str] = set() + self.current_offset = 0 + self.page_size = 50 + + @override + def compose(self) -> ComposeResult: + yield Header() + yield Container( + Static(f"📄 Document Management: {self.collection['name']}", classes="title"), + Static( + f"Total Documents: {self.collection['count']:,} | Use Space to select, Delete to remove", + classes="subtitle" + ), + Label(f"Page size: {self.page_size} documents"), + EnhancedDataTable(id="documents_table", classes="enhanced-table"), + Horizontal( + Button("🔄 Refresh", id="refresh_docs_btn", variant="primary"), + Button("🗑️ Delete Selected", id="delete_selected_btn", variant="error"), + Button("✅ Select All", id="select_all_btn", variant="default"), + Button("❌ Clear Selection", id="clear_selection_btn", variant="default"), + Button("⬅️ Previous Page", id="prev_page_btn", variant="default"), + Button("➡️ Next Page", id="next_page_btn", variant="default"), + classes="button_bar", + ), + Label("", id="selection_status"), + Static("", id="page_info", classes="status-text"), + LoadingIndicator(id="loading"), + classes="main_container", + ) + yield Footer() + + async def on_mount(self) -> None: + """Initialize the screen.""" + self.query_one("#loading").display = False + + # Setup documents table + table = self.query_one("#documents_table", EnhancedDataTable) + table.add_columns("✓", "Title", "Source URL", "Words", "ID") + + # Set up message handling for table events + table.can_focus = True + + await self.load_documents() + + async def load_documents(self) -> None: + """Load documents from the collection.""" + loading = self.query_one("#loading") + loading.display = True + + try: + if self.weaviate: + # Set the collection name + self.weaviate.config.collection_name = self.collection["name"] + + # Load documents with pagination + raw_docs = await self.weaviate.list_documents( + limit=self.page_size, offset=self.current_offset + ) + # Cast to proper type with type checking + self.documents = [ + DocumentInfo( + id=str(doc["id"]), + title=str(doc["title"]), + source_url=str(doc["source_url"]), + content_preview=str(doc["content_preview"]), + word_count=int(doc["word_count"]) + if isinstance(doc["word_count"], (int, str)) + and str(doc["word_count"]).isdigit() + else 0, + timestamp=str(doc["timestamp"]), + ) + for doc in raw_docs + ] + + await self.update_table() + self.update_selection_status() + self.update_page_info() + + except Exception as e: + self.notify(f"Error loading documents: {e}", severity="error") + finally: + loading.display = False + + async def update_table(self) -> None: + """Update the documents table.""" + table = self.query_one("#documents_table", EnhancedDataTable) + table.clear() + + # Re-add columns + table.add_columns("✓", "Title", "Source URL", "Words", "ID") + + # Add rows + for doc in self.documents: + selected = "✓" if doc["id"] in self.selected_docs else "" + table.add_row( + selected, + doc.get("title", "Untitled")[:50], + doc.get("source_url", "")[:50], + str(doc.get("word_count", 0)), + doc["id"][:8] + "...", # Show truncated ID + ) + + def update_selection_status(self) -> None: + """Update the selection status label.""" + status_label = self.query_one("#selection_status", Label) + total_selected = len(self.selected_docs) + status_label.update(f"Selected: {total_selected} documents") + + def update_page_info(self) -> None: + """Update the page information.""" + page_info = self.query_one("#page_info", Static) + total_docs = self.collection["count"] + start = self.current_offset + 1 + end = min(self.current_offset + len(self.documents), total_docs) + page_num = (self.current_offset // self.page_size) + 1 + total_pages = (total_docs + self.page_size - 1) // self.page_size + + page_info.update( + f"Showing {start:,}-{end:,} of {total_docs:,} documents (Page {page_num} of {total_pages})" + ) + + def get_current_document(self) -> DocumentInfo | None: + """Get the currently selected document.""" + table = self.query_one("#documents_table", EnhancedDataTable) + try: + if 0 <= table.cursor_coordinate.row < len(self.documents): + return self.documents[table.cursor_coordinate.row] + except (AttributeError, IndexError): + pass + return None + + # Action methods + def action_refresh(self) -> None: + """Refresh the document list.""" + self.run_worker(self.load_documents()) + + def action_toggle_selection(self) -> None: + """Toggle selection of current row.""" + doc = self.get_current_document() + if doc: + doc_id = doc["id"] + if doc_id in self.selected_docs: + self.selected_docs.remove(doc_id) + else: + self.selected_docs.add(doc_id) + + self.run_worker(self.update_table()) + self.update_selection_status() + + def action_select_all(self) -> None: + """Select all documents on current page.""" + for doc in self.documents: + self.selected_docs.add(doc["id"]) + self.run_worker(self.update_table()) + self.update_selection_status() + + def action_select_none(self) -> None: + """Clear all selections.""" + self.selected_docs.clear() + self.run_worker(self.update_table()) + self.update_selection_status() + + def action_delete_selected(self) -> None: + """Delete selected documents.""" + if self.selected_docs: + from .dialogs import ConfirmDocumentDeleteScreen + self.app.push_screen( + ConfirmDocumentDeleteScreen(list(self.selected_docs), self.collection, self) + ) + else: + self.notify("No documents selected", severity="warning") + + def action_next_page(self) -> None: + """Go to next page.""" + if self.current_offset + self.page_size < self.collection["count"]: + self.current_offset += self.page_size + self.run_worker(self.load_documents()) + + def action_prev_page(self) -> None: + """Go to previous page.""" + if self.current_offset >= self.page_size: + self.current_offset -= self.page_size + self.run_worker(self.load_documents()) + + def action_first_page(self) -> None: + """Go to first page.""" + if self.current_offset > 0: + self.current_offset = 0 + self.run_worker(self.load_documents()) + + def action_last_page(self) -> None: + """Go to last page.""" + total_docs = self.collection["count"] + last_offset = ((total_docs - 1) // self.page_size) * self.page_size + if self.current_offset != last_offset: + self.current_offset = last_offset + self.run_worker(self.load_documents()) + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses.""" + if event.button.id == "refresh_docs_btn": + self.action_refresh() + elif event.button.id == "delete_selected_btn": + self.action_delete_selected() + elif event.button.id == "select_all_btn": + self.action_select_all() + elif event.button.id == "clear_selection_btn": + self.action_select_none() + elif event.button.id == "next_page_btn": + self.action_next_page() + elif event.button.id == "prev_page_btn": + self.action_prev_page() + + def on_enhanced_data_table_row_toggled(self, event: EnhancedDataTable.RowToggled) -> None: + """Handle row toggle from enhanced table.""" + if 0 <= event.row_index < len(self.documents): + doc = self.documents[event.row_index] + doc_id = doc["id"] + + if doc_id in self.selected_docs: + self.selected_docs.remove(doc_id) + else: + self.selected_docs.add(doc_id) + + self.run_worker(self.update_table()) + self.update_selection_status() + + def on_enhanced_data_table_select_all(self, event: EnhancedDataTable.SelectAll) -> None: + """Handle select all from enhanced table.""" + self.action_select_all() + + def on_enhanced_data_table_clear_selection(self, event: EnhancedDataTable.ClearSelection) -> None: + """Handle clear selection from enhanced table.""" + self.action_select_none() diff --git a/ingest_pipeline/cli/tui/screens/help.py b/ingest_pipeline/cli/tui/screens/help.py new file mode 100644 index 0000000..5926ec9 --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/help.py @@ -0,0 +1,50 @@ +"""Help screen with keyboard shortcuts and usage information.""" + +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Container, ScrollableContainer +from textual.screen import ModalScreen +from textual.widgets import Button, Markdown, Rule, Static +from typing_extensions import override + + +class HelpScreen(ModalScreen[None]): + """Modern help screen with comprehensive keyboard shortcuts.""" + + help_content: str + + BINDINGS = [ + Binding("escape", "app.pop_screen", "Close"), + Binding("q", "app.pop_screen", "Close"), + Binding("enter", "app.pop_screen", "Close"), + Binding("f1", "app.pop_screen", "Close"), + ] + + def __init__(self, help_content: str): + super().__init__() + self.help_content = help_content + + @override + def compose(self) -> ComposeResult: + with Container(classes="modal-container"): + yield Static("📚 Help & Keyboard Shortcuts", classes="title") + yield Static("Enhanced navigation and productivity features", classes="subtitle") + yield Rule(line_style="heavy") + + with ScrollableContainer(): + yield Markdown(self.help_content) + + yield Container( + Button("✅ Got it! (Press Escape or Enter)", id="close_btn", variant="primary"), + classes="action_buttons center", + ) + + def on_mount(self) -> None: + """Initialize the help screen.""" + # Focus the close button + self.query_one("#close_btn").focus() + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Close help screen.""" + if event.button.id == "close_btn": + self.app.pop_screen() diff --git a/ingest_pipeline/cli/tui/screens/ingestion.py b/ingest_pipeline/cli/tui/screens/ingestion.py new file mode 100644 index 0000000..419910e --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/ingestion.py @@ -0,0 +1,253 @@ +"""Enhanced ingestion screen with better UX.""" + +import asyncio +from datetime import datetime + +from textual import work +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Container, Horizontal +from textual.screen import ModalScreen +from textual.widgets import Button, Input, Label, LoadingIndicator, Rule, Static +from typing_extensions import override + +from ....core.models import IngestionJob, IngestionSource, StorageBackend +from ..models import CollectionInfo +from ..widgets import EnhancedProgressBar + + +class IngestionScreen(ModalScreen[None]): + """Enhanced ingestion screen with better UX and keyboard navigation.""" + + collection: CollectionInfo + selected_type: IngestionSource + progress_value: int + + BINDINGS = [ + Binding("escape", "app.pop_screen", "Cancel"), + Binding("ctrl+i", "start_ingestion", "Start"), + Binding("1", "select_web", "Web", show=False), + Binding("2", "select_repo", "Repository", show=False), + Binding("3", "select_docs", "Documentation", show=False), + Binding("enter", "start_ingestion", "Start Ingestion"), + Binding("tab", "focus_next", "Next Field"), + Binding("shift+tab", "focus_previous", "Previous Field"), + ] + + def __init__(self, collection: CollectionInfo): + super().__init__() + self.collection = collection + self.selected_type = IngestionSource.WEB + self.progress_value = 0 + + @override + def compose(self) -> ComposeResult: + with Container(classes="modal-container"): + yield Static("📥 Modern Ingestion Interface", classes="title") + yield Static( + f"Target: {self.collection['name']} ({self.collection['backend']})", + classes="subtitle", + ) + yield Rule() + + # Enhanced input section + yield Container( + Label("🌐 Source URL:", classes="input-label"), + Input( + placeholder="https://docs.example.com or file:///path/to/repo", + id="url_input", + classes="modern-input", + ), + Label("📋 Source Type (Press 1/2/3):", classes="input-label"), + Horizontal( + Button("🌐 Web (1)", id="web_btn", variant="primary", classes="type-button"), + Button( + "📦 Repository (2)", id="repo_btn", variant="default", classes="type-button" + ), + Button( + "📖 Documentation (3)", id="docs_btn", variant="default", classes="type-button" + ), + classes="type_buttons", + ), + Rule(line_style="dashed"), + classes="input-section card", + ) + + # Enhanced Progress section + yield Container( + Label("🔄 Progress:", classes="progress-label"), + EnhancedProgressBar(id="enhanced_progress", total=100), + Static("Ready to start", id="progress_text", classes="status-text"), + classes="progress-section card", + ) + + # Action buttons + yield Horizontal( + Button("🚀 Start Ingestion", id="start_btn", variant="success"), + Button("❌ Cancel", id="cancel_btn", variant="error"), + classes="action_buttons", + ) + + yield LoadingIndicator(id="loading", classes="pulse") + + def on_mount(self) -> None: + """Initialize the screen.""" + self.query_one("#loading").display = False + self.selected_type = IngestionSource.WEB + # Focus the URL input field by default + self.query_one("#url_input").focus() + + def action_select_web(self) -> None: + """Select web ingestion type.""" + self.selected_type = IngestionSource.WEB + self.update_type_buttons("web") + + def action_select_repo(self) -> None: + """Select repository ingestion type.""" + self.selected_type = IngestionSource.REPOSITORY + self.update_type_buttons("repo") + + def action_select_docs(self) -> None: + """Select documentation ingestion type.""" + self.selected_type = IngestionSource.DOCUMENTATION + self.update_type_buttons("docs") + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses with enhanced feedback.""" + button_id = event.button.id + + if button_id == "web_btn": + self.action_select_web() + elif button_id == "repo_btn": + self.action_select_repo() + elif button_id == "docs_btn": + self.action_select_docs() + elif button_id == "start_btn": + self.action_start_ingestion() + elif button_id == "cancel_btn": + self.app.pop_screen() + + def update_type_buttons(self, selected: str) -> None: + """Update type button visual states.""" + buttons = { + "web": self.query_one("#web_btn", Button), + "repo": self.query_one("#repo_btn", Button), + "docs": self.query_one("#docs_btn", Button), + } + + for btn_type, button in buttons.items(): + if btn_type == selected: + button.variant = "primary" + else: + button.variant = "default" + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Handle URL input submission.""" + if event.input.id == "url_input": + self.action_start_ingestion() + + def action_start_ingestion(self) -> None: + """Start the enhanced ingestion process.""" + url_input = self.query_one("#url_input", Input) + if not url_input.value.strip(): + self.notify("🔍 Please enter a source URL", severity="error") + url_input.focus() + return + + self.perform_ingestion(url_input.value.strip()) + + @work(exclusive=True) + async def perform_ingestion(self, source_url: str) -> None: + """Perform ingestion with enhanced progress tracking and better UX.""" + loading = self.query_one("#loading") + enhanced_progress = self.query_one("#enhanced_progress", EnhancedProgressBar) + progress_text = self.query_one("#progress_text", Static) + + try: + loading.display = True + + # Enhanced progress tracking with better visual feedback + enhanced_progress.update_progress(5, "Initializing ingestion pipeline...") + progress_text.update("🚀 Starting modern ingestion process...") + await asyncio.sleep(0.3) + + # Determine storage backend + storage_backend = ( + StorageBackend.WEAVIATE + if self.collection["type"] == "weaviate" + else StorageBackend.OPEN_WEBUI + ) + + enhanced_progress.update_progress(15, "Creating ingestion job...") + progress_text.update("📋 Configuring job parameters...") + await asyncio.sleep(0.4) + + # Create ingestion job + job = IngestionJob( + source_url=source_url, + source_type=self.selected_type, + storage_backend=storage_backend, + created_at=datetime.now(), + ) + + enhanced_progress.update_progress(25, "Loading ingestion modules...") + progress_text.update("⚡ Importing processing components...") + await asyncio.sleep(0.4) + + from ....flows.ingestion import ingest_documents_task + + enhanced_progress.update_progress(35, "Connecting to services...") + progress_text.update(f"🔗 Establishing connection to {storage_backend.value}...") + await asyncio.sleep(0.5) + + enhanced_progress.update_progress(45, "Fetching source content...") + progress_text.update("📄 Retrieving documents from source...") + await asyncio.sleep(0.6) + + # Simulate realistic progress steps + progress_steps = [ + (55, "Parsing document structure...", "🔍 Analyzing content structure..."), + (65, "Extracting text content...", "📝 Processing text and metadata..."), + (75, "Generating embeddings...", "🧠 Creating vector embeddings..."), + (85, "Storing in database...", "💾 Persisting to storage backend..."), + (95, "Finalizing operation...", "🎯 Completing ingestion process..."), + ] + + for progress, status, text in progress_steps: + enhanced_progress.update_progress(progress, status) + progress_text.update(text) + await asyncio.sleep(0.7) + + # Perform actual ingestion + successful, failed = await ingest_documents_task( + job, collection_name=self.collection["name"] + ) + + # Success handling with celebratory feedback + enhanced_progress.update_progress(100, "Completed successfully!") + progress_text.update( + f"🎉 Ingestion complete: {successful} documents added, {failed} failed" + ) + + # Show enhanced success notification + if successful > 0: + self.notify( + f"🎉 Successfully ingested {successful} documents!", + severity="information" + ) + if failed > 0: + self.notify(f"⚠️ {failed} documents failed to process", severity="warning") + else: + self.notify("❌ No documents were successfully processed", severity="error") + + # Keep results visible before closing + await asyncio.sleep(3) + self.app.pop_screen() + + except Exception as e: + enhanced_progress.update_progress(0, "Ingestion failed") + progress_text.update(f"❌ Error occurred: {str(e)[:100]}") + self.notify(f"❌ Ingestion failed: {e}", severity="error") + await asyncio.sleep(2) # Show error before allowing interaction + finally: + loading.display = False diff --git a/ingest_pipeline/cli/tui/screens/search.py b/ingest_pipeline/cli/tui/screens/search.py new file mode 100644 index 0000000..e0a7023 --- /dev/null +++ b/ingest_pipeline/cli/tui/screens/search.py @@ -0,0 +1,190 @@ +"""Search screen for finding documents within collections.""" + +from textual.app import ComposeResult +from textual.binding import Binding +from textual.containers import Container +from textual.screen import Screen +from textual.widgets import Button, Footer, Header, Input, LoadingIndicator, Static +from typing_extensions import override + +from ....storage.openwebui import OpenWebUIStorage +from ....storage.weaviate import WeaviateStorage +from ..models import CollectionInfo +from ..widgets import EnhancedDataTable + + +class SearchScreen(Screen[None]): + """Screen for searching within a collection with enhanced keyboard navigation.""" + + collection: CollectionInfo + weaviate: WeaviateStorage | None + openwebui: OpenWebUIStorage | None + + BINDINGS = [ + Binding("escape", "app.pop_screen", "Back"), + Binding("enter", "perform_search", "Search"), + Binding("ctrl+f", "focus_search", "Focus Search"), + Binding("f3", "perform_search", "Search Again"), + Binding("ctrl+r", "clear_results", "Clear Results"), + Binding("/", "focus_search", "Quick Search"), + ] + + def __init__( + self, + collection: CollectionInfo, + weaviate: WeaviateStorage | None, + openwebui: OpenWebUIStorage | None, + ): + super().__init__() + self.collection = collection + self.weaviate = weaviate + self.openwebui = openwebui + + @override + def compose(self) -> ComposeResult: + yield Header() + yield Container( + Static( + f"🔍 Search in: {self.collection['name']} ({self.collection['backend']})", + classes="title", + ), + Static("Press / or Ctrl+F to focus search, Enter to search", classes="subtitle"), + Input(placeholder="Enter search query... (press Enter to search)", id="search_input"), + Button("🔍 Search", id="search_btn", variant="primary"), + Button("🗑️ Clear Results", id="clear_btn", variant="default"), + EnhancedDataTable(id="results_table"), + Static("Enter your search query to find relevant documents.", id="search_status", classes="status-text"), + LoadingIndicator(id="loading"), + classes="main_container", + ) + yield Footer() + + def on_mount(self) -> None: + """Initialize the screen.""" + self.query_one("#loading").display = False + + # Setup results table + table = self.query_one("#results_table", EnhancedDataTable) + table.add_columns("Title", "Content Preview", "Score") + + # Focus search input + self.query_one("#search_input").focus() + + def action_focus_search(self) -> None: + """Focus the search input field.""" + search_input = self.query_one("#search_input", Input) + search_input.focus() + + def action_clear_results(self) -> None: + """Clear search results.""" + table = self.query_one("#results_table", EnhancedDataTable) + table.clear() + table.add_columns("Title", "Content Preview", "Score") + + status = self.query_one("#search_status", Static) + status.update("Search results cleared. Enter a new query to search.") + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Handle search input submission.""" + if event.input.id == "search_input": + self.action_perform_search() + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handle button presses.""" + if event.button.id == "search_btn": + self.action_perform_search() + elif event.button.id == "clear_btn": + self.action_clear_results() + + def action_perform_search(self) -> None: + """Perform search.""" + search_input = self.query_one("#search_input", Input) + if not search_input.value.strip(): + self.notify("Please enter a search query", severity="warning") + search_input.focus() + return + + self.run_worker(self.search_collection(search_input.value.strip())) + + async def search_collection(self, query: str) -> None: + """Search the collection.""" + loading = self.query_one("#loading") + table = self.query_one("#results_table", EnhancedDataTable) + status = self.query_one("#search_status", Static) + + try: + loading.display = True + status.update(f"🔍 Searching for '{query}'...") + table.clear() + table.add_columns("Title", "Content Preview", "Score") + + results = [] + + if self.collection["type"] == "weaviate" and self.weaviate: + results = await self.search_weaviate(query) + elif self.collection["type"] == "openwebui" and self.openwebui: + results = await self.search_openwebui(query) + + # Add results to table + for result in results: + title = result.get("title", "Untitled") + content = result.get("content", "") + score = result.get("score", 0) + table.add_row( + title[:50] if isinstance(title, str) else str(title)[:50], + (content[:100] + "...") + if isinstance(content, str) + else str(content)[:100] + "...", + f"{score:.3f}" if isinstance(score, (int, float)) else str(score), + ) + + if not results: + status.update(f"No results found for '{query}'. Try different keywords.") + self.notify("No results found", severity="information") + else: + status.update(f"Found {len(results)} results for '{query}'. Use arrow keys to navigate.") + self.notify(f"Found {len(results)} results", severity="information") + # Focus the table for navigation + table.focus() + + except Exception as e: + status.update(f"Search error: {e}") + self.notify(f"Search error: {e}", severity="error") + finally: + loading.display = False + + async def search_weaviate(self, query: str) -> list[dict[str, str | float]]: + """Search Weaviate collection.""" + if not self.weaviate: + return [] + + try: + await self.weaviate.initialize() + results_generator = self.weaviate.search(query, limit=20) + results = [doc async for doc in results_generator] + # Convert Document objects to dict format expected by the UI + return [ + { + "title": getattr(doc, "title", "Untitled"), + "content": getattr(doc, "content", ""), + "score": getattr(doc, "score", 0.0), + } + for doc in results + ] + except Exception as e: + self.notify(f"Weaviate search error: {e}", severity="error") + return [] + + async def search_openwebui(self, query: str) -> list[dict[str, str | float]]: + """Search OpenWebUI collection.""" + if not self.openwebui: + return [] + + try: + # OpenWebUI does not have a direct search API, so return empty + # In a real implementation, you would need to implement search via their API + self.notify("OpenWebUI search not yet implemented", severity="warning") + return [] + except Exception as e: + self.notify(f"OpenWebUI search error: {e}", severity="error") + return [] diff --git a/ingest_pipeline/cli/tui/styles.py b/ingest_pipeline/cli/tui/styles.py new file mode 100644 index 0000000..8efa7c4 --- /dev/null +++ b/ingest_pipeline/cli/tui/styles.py @@ -0,0 +1,346 @@ +"""Modern CSS styles for the TUI application.""" + +# Enhanced modern CSS with better focus indicators and navigation feedback +TUI_CSS = """ +/* Base styling */ +Screen { + background: #1a1a1a; +} + +* { + color: #ffffff; +} + +/* Title styling */ +.title { + text-align: center; + margin: 1; + color: #ffffff; + text-style: bold; + background: #333333; + padding: 1; + border: solid #0088cc; +} + +.subtitle { + text-align: center; + margin: 1 0; + color: #cccccc; + text-style: italic; + background: #333333; + padding: 1; +} + +/* Container styling */ +.main_container { + margin: 1; + padding: 1; + background: #333333; +} + +.card { + background: #333333; + padding: 1; + margin: 1; + color: #ffffff; + border: solid #444444; +} + +.card:focus-within { + border: solid #0088cc; +} + +/* Button styling with focus states */ +Button { + background: #444444; + color: #ffffff; + margin: 0 1; + border: solid transparent; +} + +Button:hover { + background: #0088cc; + color: #ffffff; +} + +Button:focus { + border: solid #ffffff; + background: #0088cc; +} + +Button.-primary { + background: #0088cc; + color: #ffffff; +} + +Button.-success { + background: #28a745; + color: #ffffff; +} + +Button.-error { + background: #dc3545; + color: #ffffff; +} + +Button.-warning { + background: #ffc107; + color: #000000; +} + +/* Enhanced DataTable with focus indicators */ +DataTable { + background: #333333; + color: #ffffff; + border: solid #444444; +} + +DataTable:focus { + border: solid #0088cc; +} + +DataTable > .datatable--header { + background: #444444; + color: #ffffff; + text-style: bold; +} + +DataTable > .datatable--cursor { + background: #0088cc; + color: #ffffff; +} + +DataTable > .datatable--cursor-row { + background: #0066aa; + color: #ffffff; +} + +/* Input styling */ +Input { + background: #333333; + color: #ffffff; + border: solid #666666; +} + +Input:focus { + border: solid #0088cc; +} + +/* Header and Footer */ +Header, Footer { + background: #333333; + color: #ffffff; +} + +/* Tab styling with focus indicators */ +Tab { + background: #333333; + color: #ffffff; + border: solid transparent; +} + +Tab:focus { + border: solid #ffffff; +} + +Tab.-active { + background: #0088cc; + color: #ffffff; + text-style: bold; +} + +/* Label styling */ +Label { + color: #ffffff; +} + +/* Status indicators */ +.status-active { + color: #28a745; +} + +.status-error { + color: #dc3545; +} + +.status-warning { + color: #ffc107; +} + +/* Animations */ +.pulse { + text-style: blink; +} + +.glow { + background: #0088cc; + color: #ffffff; +} + +.shimmer { + text-style: italic; +} + +/* Metrics styling */ +.metrics-value { + text-style: bold; + text-align: center; + color: #ffffff; +} + +.metrics-label { + text-align: center; + color: #cccccc; +} + +.metrics-description { + text-align: center; + color: #999999; + text-style: italic; +} + +/* Section titles */ +.section-title { + text-style: bold; + color: #ffffff; + margin: 1 0; +} + +/* Status text */ +.status-text { + color: #cccccc; +} + +/* Button groups */ +.button_bar { + margin: 1 0; +} + +.action_buttons { + margin: 1; + text-align: center; +} + +/* Progress styling */ +.progress-label { + color: #ffffff; + margin: 1 0; +} + +/* Responsive grid */ +.responsive-grid { + grid-size: 4; + grid-gutter: 1; +} + +.metrics-grid { + grid-size: 4; + grid-gutter: 1; + margin: 1; +} + +/* Modal container */ +.modal-container { + background: #333333; + border: solid #0088cc; + padding: 2; + margin: 2; +} + +/* Chart placeholders */ +.chart-title { + text-style: bold; + color: #ffffff; + margin: 1 0; +} + +.chart-placeholder { + color: #999999; + text-style: italic; + text-align: center; + padding: 2; +} + +/* Analytics grid */ +.analytics-grid { + grid-size: 2; + grid-gutter: 1; +} + +/* Enhanced table styling */ +.enhanced-table { + background: #333333; + color: #ffffff; + border: solid #666666; +} + +.enhanced-table:focus { + border: solid #0088cc; +} + +/* Status bar */ +.status-bar { + background: #444444; + color: #ffffff; + padding: 0 1; +} + +/* Input section styling */ +.input-section { + margin: 1; + padding: 1; +} + +.input-label { + color: #ffffff; + margin: 1 0; +} + +.modern-input { + background: #333333; + color: #ffffff; + border: solid #666666; + margin: 1 0; +} + +.modern-input:focus { + border: solid #0088cc; +} + +/* Type buttons */ +.type_buttons { + margin: 1 0; +} + +.type-button { + margin: 0 1; +} + +/* Progress section */ +.progress-section { + margin: 1; + padding: 1; +} + +/* Center alignment */ +.center { + text-align: center; +} + +/* Warning styling */ +.warning { + color: #ffc107; + text-style: bold; +} + +/* Pressed button state */ +.pressed { + background: #006699; + color: #ffffff; +} + +/* Focus ring for better accessibility */ +*:focus { + outline: solid #0088cc; +} +""" diff --git a/ingest_pipeline/cli/tui/utils/__init__.py b/ingest_pipeline/cli/tui/utils/__init__.py new file mode 100644 index 0000000..6bfaadc --- /dev/null +++ b/ingest_pipeline/cli/tui/utils/__init__.py @@ -0,0 +1,5 @@ +"""Utility functions for the TUI.""" + +from .runners import dashboard, run_textual_tui + +__all__ = ["dashboard", "run_textual_tui"] diff --git a/ingest_pipeline/cli/tui/utils/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/cli/tui/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..745ee04dad89e45732f5c559f9a0d5b55c7ff751 GIT binary patch literal 317 zcmYLDu};G<6tt6+&{h>&#e_1jgrWWcK&&iGWXbYmVz;s6IFapCgbjXzZ{SamSh|v_ z5(8oasT&h75{Mh_y{9|fyO-H)LJ*&4&*cNox8e9h@Vo31Tpo!aIT2I@npQy`RAC-c zB0>?Zf;{R*>E7K@=9N~yOO~xEysC{$mbFcMDU<8$a`8biCJp+r;I6!>xfT6^Z4G#N z>szkDx5|cSu@TZ$I_hHpQ5OJxj5Z^!`Lp4%w~4qe?G=_{m^zLMj2!nV`- O<}lq%PVQqnM(GE8&RKW> literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/utils/__pycache__/runners.cpython-312.pyc b/ingest_pipeline/cli/tui/utils/__pycache__/runners.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dea20f460c1e3dd169f9c65a131ec00757de43ea GIT binary patch literal 3024 zcmbVOO>7&-6`uVgcS&kVC6s^UpRPp75gT}^#_^xh*id8|R@^8_W7h~T#ELtTR$eZN z{jp4y4AdrR9mI$)L5(C0;$sUpfRDX8iGl#Rh!6{^3;WQN0{7<8A|JO{*eq@n^+DV5f#0;Mu4tJ7ts zz?9hnOCeZuI#-SqA|4&lqh-Fp107X)T`0#2F`(nB@D^K$!yJP-QKPeo<*{?;rzO)d z3~Wksj#09;ieX8Lp-P&e*_xtj%L>8d`^1=;`z-utX;*yBu9(U^enBZ+#D?njb=OlB zV@{jTQf}(yDmE_Q+4Ixg9l(+ouyRRLY}|$J69)m`IaSeh?5Tc9F+ge=8}?+iS|Tcg z5dq>&_-#rILT3?00z!g;%2WeZG^(SUUET(Sr$NJyvAN*5%HE`WnnD8Ff^#8mM+0sf zuEHOS-t`14f38!DiANE#0#w|Ns*yiLZ<3(FXs~v_PXTw&o(5f~7oQC9a1OkA6E64X z;PY>o>&&k>4t(8WJs(>Ch*z-?f2>zKLOlTm_U{Vla0YZRTm{r4UPbJE0bY*;dK*lg z`QBc|11S`$)7I`fW9{7Hb6>c-1r=N07Oz!ELcdsB<5okLghN>u`$=?hWp0%b%C1C3Km29W7BHYvupS zN_?lRYmbrj>Zi;Sl}#;)`gV?i-WSe42Z6`O`c5`Iau7o=Hi+mQ9xG%KbA zJgTl4^N&k?6J8(5)5JWsix!*$K5ND~259G#4_Bn=pi+?YFZo)+l$OGrmR_q_HZIGu z?CkdrzkgkVieZr4hog2y3MvG!xKS0)DUNPuSvNY{tus6nn`%WfY&WV@wc?u{t3+ z_RmrH>fn*Xd#i&V^ zUy7w}jF#nBbgWodDlOpBMX6SCOmYIsvSWIs+`D4itVzneq8YMt)N}8>cch;v&;S<5 zP2__&(|asuac#Dun5r}bC4Am*39_8+C*dcu<=|bIaS-QnRTD1BB<0s6)z9TGBHsEji9xJTH zPPN#4hfQ$D@9=$VqTJ$hcLtxjlOArTpKhj~-Wb@|9>_HZavSNr?exKB`ryXi(FX#z zCw3+IAc4}E8zAG1lt-Ls@h3NU(Q|OB#h$ts8@e&}yXp1Vk(H^9Sl_jS?;LzL*JgJ& z+1;I^NErFgy?FXxi1LGwI&^FD)}b}=Xq!LQ3|F6Q?$tGhjXKSz)ZLwF8QaCmi0I4v-% zKN3%~%<4(P^Cimx@}?XTgJc+~(VHjW?>e%(Upfda2A_~URnm#R@& zdig284tUL@+cUxvE2)aL;8g;7Cd)t-CaI)VTn1jN_d None: + """Run the enhanced modern TUI with better error handling and initialization.""" + from ....config.settings import get_settings + + settings = get_settings() + + # Initialize storage backends with enhanced error handling + weaviate = None + openwebui = None + + print("🚀 Initializing Modern Collection Management System...") + + try: + print("🔗 Connecting to Weaviate...") + weaviate_config = StorageConfig( + backend=StorageBackend.WEAVIATE, + endpoint=settings.weaviate_endpoint, + api_key=settings.weaviate_api_key, + collection_name="default", + ) + weaviate = WeaviateStorage(weaviate_config) + await weaviate.initialize() + print("✅ Weaviate connected successfully!") + except Exception as e: + print(f"⚠️ Weaviate connection failed: {e}") + + try: + print("🔗 Connecting to OpenWebUI...") + openwebui_config = StorageConfig( + backend=StorageBackend.OPEN_WEBUI, + endpoint=settings.openwebui_endpoint, + api_key=settings.openwebui_api_key, + collection_name="default", + ) + openwebui = OpenWebUIStorage(openwebui_config) + await openwebui.initialize() + print("✅ OpenWebUI connected successfully!") + except Exception as e: + print(f"⚠️ OpenWebUI connection failed: {e}") + + if not weaviate and not openwebui: + print("❌ Error: Could not connect to any storage backend") + print("Please check your configuration and try again.") + return + + print("🎉 Launching Enhanced TUI with Keyboard Navigation...") + + app = CollectionManagementApp(weaviate, openwebui) + await app.run_async() + + +def dashboard() -> None: + """Launch the modern collection dashboard.""" + asyncio.run(run_textual_tui()) diff --git a/ingest_pipeline/cli/tui/widgets/__init__.py b/ingest_pipeline/cli/tui/widgets/__init__.py new file mode 100644 index 0000000..7caf3b0 --- /dev/null +++ b/ingest_pipeline/cli/tui/widgets/__init__.py @@ -0,0 +1,12 @@ +"""Enhanced widgets with keyboard navigation support.""" + +from .cards import MetricsCard +from .indicators import EnhancedProgressBar, StatusIndicator +from .tables import EnhancedDataTable + +__all__ = [ + "MetricsCard", + "StatusIndicator", + "EnhancedProgressBar", + "EnhancedDataTable", +] diff --git a/ingest_pipeline/cli/tui/widgets/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/cli/tui/widgets/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa43c46b153e0aa48d05f0a28c2b321b1cb0db44 GIT binary patch literal 451 zcmXv~yG{c!5VY?Z5=9ZBp+eVCToX%FDFQ`+f&zpDG}lG%^Q zz5q*z0#Q(bNJoV)Oa7wvk2nwcJH{Dks@U@y5TfqTG?06qHDI8mPFF1 zAakA(&C4QEbyby84?pc+KnzXqlxfLRb!c_5W479)b6IARDK#O|3@$X$wK^#%Pl+z2 zwQaV#BcjPAnF5TJ|)`A_#fsJf#ptdc1yAZ+`F!}&HZ(#fic7Hs-=iG&|Z-;-XqJYx? literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/widgets/__pycache__/cards.cpython-312.pyc b/ingest_pipeline/cli/tui/widgets/__pycache__/cards.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e62957a7ff9bdb228739199b5eff7032d6b61842 GIT binary patch literal 1629 zcmZuxOK%)S5bmDW&c1Bf-hg8#uyPc`9N0Y|Aw>!g%Lj-N7Q#eABSdSMZkvJG*`a&Z z&aU+540UUx1cMkjjBrdU&D9*@{kRTGAFl-_PPE_~KZju!xO;vSO^`ol3 znxD(%0s{K;`Jdh!*AeoWTtQAT zEoMnALQb707OIUVWjv}jvQ%jx;?QfWlxwM(1WCwJbKMJDEG<$dd>+I}(2P>+gAI?b ziPYjOY4K>;H?rHLkbRMfs=j=Mw^4$nS<)r5i#~@~iUZU&zQJE$jJmjsh}QQb)YV{> zbplp8tcGW|k)thtiySjGMJr}JwFwELAR&b3VLz8p;qqq3N)5rn)e$oNw_;U=^}vRbBxOhxUiFGxZSYYnkzJ8N3ASciENS#wwTJ+;+couRM8u?99G> z=5W+-P#nKmoZT7#|2g%cQ3LQYA- zGx8dYkhDMuj9M#{dWnz^TV9xXa)i*P4`y8?JRK**i=w9di8LcXD3_Cd|=6DN%b|I=x-tr%_1So&`(|EGcNB?7pWH8XZuxs rfu9D4?7f%+cBEsBZ=snx=&?KK^ldbE8_nOfCh-!!Jbw>Ksm}Nh2L*%m literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/widgets/__pycache__/indicators.cpython-312.pyc b/ingest_pipeline/cli/tui/widgets/__pycache__/indicators.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5bdffb63a13189912b3a8b675dd6ac857ba3493 GIT binary patch literal 4886 zcmb7IU2qfE6}~I2q?NRiaWKTj{46F=1i?zvlwV_#*l9=&ZU;iz(a>xb-D|v{zuaA! zNOc*eI8&Rng-+Ar&~z}JcIwo0hKD@#p$~mzrY};E$6_a)deUa5Jk_aTlJ=?R-rbdK zBO9_azQ5<5d+xdCeD{<88i|AueE&Xpd$3JL=pU@(em_9y)5KqA2VzLb;>CV~R;pqCN$k02Z%fhn)8GZEsW5{w2HjD`!L zSh)D&aZ@w%hN5S%p47}NH54s_l^o5Uro=FmQyPq%)6GGJWCpcNl3-;>H}YCa86X6o z(voL8@3C!Sg5`Z7Q-CVclTGKc202EIe9DY@tnjxTWiM*f^0WQ)B*;nGAwsE+Nzyjc zF8xkt{s=UF1fC!$A%HhLC%^{@FA<3kd%9o_dx(g=#E(Vn8}N^!0Rhe}Cjxw&jfprg zA||9!^p=na9!D|BYO>GzihI=JzN1-8Xht!X_`p7JGMma$C7H!~<}`PTmeJE-d^Tfr zT7G+t3Lsbnh1u2Sh;eWmH69%D1+@#u29Fw6qNfiuTi-Qx0 zVX8SjM^bu*#FHsKZszs)IUS!SrUCPA0CnaHbQP#8wl48^r)ypl%o^LF_~HWkSc;4V z%2I1t>KNbsk}ByXg05YG6~7KOjGMKV~lZ5ar9bAgaZ9Eo4UdD3O_mQ12-p_XFm^`#Hm=1y z^a)tX*BA^o6@|92fo3R*8(=TFtM%Nx_w%X zRU@k}pBy{+&g-R3N6V3;u55Qj-r=_QROBryt?MmEdL7wP`_78|^y4nvTaN5?W%pI& ztq*N7>safbSo>bnw7i|J0guE&>Hhs``OIS2N$UN9=j~x>yoYRw>9oLzVsyC8nx86xKyGG`P%62N{OaF~WP5=lrTi2Woq;K9KW(S0!zVH^QgBH|D* z6E2U#Bff;pSx5v}NYq;Wl0&2x(4ykA?%*QkWdp`3Vw!|LM7u1X2~JO0lAC;1*oFwz zByXAgfv3PS(K3{zAEuU}B;MsH31$SP_OfCR^jPxp56FnvPgxpTzGFng1sa7mq%?c% z?Qr-yYwc#m9yTS~>B~nM9kBz$B_eiga2~PVbY^e=_0z}3u}8{na-IRTbC zANH~9zbQ>QbM~qN`lI)p5DWHO$ZB%$&EaRos5`JLrJW)vKv0hkn9La(F)Z1gR)Leh zO1HvB;6m=bgV44lV=#M8C8kEZ*_4;EQ&9%9Tqq`Rck{%vaS~M9Oug9N$idF!0Z?G< zg5qDy!~cx7Tsc-+zpJ$8CaaFq>a4$QBX?{2ucx@qfZZ$(}Y^rm(F)$>0)Kau_L`*)t~8I`M2 z%Z$`smf9!2Tawx0E5@ZmRe9ZvytyoIp6H!y znThQy$M%6tspCLJ?yAbIGjc~+?wHs;4a6q0X8eguuh$p6H2Kubj{W5w`%AF{rH-y? z`QSq$OLR_y&PQvRt(o-j4mOt&Xb(690)Zh|EJ}AmMW1E`Lo?b5h2_;VCVhr=8UUoNQ_JP}QLH9+#QDu`$14j}tJz+u!V;1(7?Gpg z1&o&QFJ?Y@7r5W6yP UA3=5hu&`a|9^du=LB-YmA9PJ!X#fBK literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/widgets/__pycache__/tables.cpython-312.pyc b/ingest_pipeline/cli/tui/widgets/__pycache__/tables.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eaef22bd90d637f77ed286d0819eaa8f2dc50feb GIT binary patch literal 7694 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 P_q*=ae*@l}x4z+j{`>Yw literal 0 HcmV?d00001 diff --git a/ingest_pipeline/cli/tui/widgets/cards.py b/ingest_pipeline/cli/tui/widgets/cards.py new file mode 100644 index 0000000..fad2aeb --- /dev/null +++ b/ingest_pipeline/cli/tui/widgets/cards.py @@ -0,0 +1,28 @@ +"""Metrics card widget.""" + +from typing import Any + +from textual.app import ComposeResult +from textual.widgets import Static +from typing_extensions import override + + +class MetricsCard(Static): + """A modern metrics display card.""" + + title: str + value: str + description: str + + def __init__(self, title: str, value: str, description: str = "", **kwargs: Any) -> None: + super().__init__(**kwargs) + self.title = title + self.value = value + self.description = description + + @override + def compose(self) -> ComposeResult: + yield Static(self.value, classes="metrics-value") + yield Static(self.title, classes="metrics-label") + if self.description: + yield Static(self.description, classes="metrics-description") diff --git a/ingest_pipeline/cli/tui/widgets/indicators.py b/ingest_pipeline/cli/tui/widgets/indicators.py new file mode 100644 index 0000000..790008d --- /dev/null +++ b/ingest_pipeline/cli/tui/widgets/indicators.py @@ -0,0 +1,86 @@ +"""Status indicators and progress bars with enhanced visual feedback.""" + +from typing import Any + +from textual.app import ComposeResult +from textual.widgets import ProgressBar, Static +from typing_extensions import override + + +class StatusIndicator(Static): + """Modern status indicator with color coding and animations.""" + + status: str + + def __init__(self, status: str, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.status = status + self.update_status(status) + + def update_status(self, status: str) -> None: + """Update the status display with enhanced visual feedback.""" + self.status = status + + # Remove previous status classes + self.remove_class("status-active", "status-error", "status-warning", "pulse", "glow") + + if status.lower() in ["active", "online", "connected", "✓ active"]: + self.add_class("status-active") + self.add_class("glow") + self.update("🟢 " + status) + elif status.lower() in ["error", "failed", "offline", "disconnected"]: + self.add_class("status-error") + self.add_class("pulse") + self.update("🔴 " + status) + elif status.lower() in ["warning", "pending", "in_progress"]: + self.add_class("status-warning") + self.add_class("pulse") + self.update("🟡 " + status) + elif status.lower() in ["loading", "connecting"]: + self.add_class("shimmer") + self.update("🔄 " + status) + else: + self.update("⚪ " + status) + + +class EnhancedProgressBar(Static): + """Enhanced progress bar with better visual feedback.""" + + total: int + progress: int + status_text: str + + def __init__(self, total: int = 100, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.total = total + self.progress = 0 + self.status_text = "Ready" + + @override + def compose(self) -> ComposeResult: + yield Static("", id="progress_status", classes="progress-label") + yield ProgressBar(total=self.total, id="progress_bar", show_eta=True, classes="shimmer") + + def update_progress(self, progress: int, status: str = "") -> None: + """Update progress with enhanced feedback.""" + self.progress = progress + if status: + self.status_text = status + + # Update the progress bar + progress_bar = self.query_one("#progress_bar", ProgressBar) + progress_bar.update(progress=progress) + + # Update status text with icons + status_display = self.query_one("#progress_status", Static) + if progress >= 100: + status_display.update(f"✅ {self.status_text}") + progress_bar.add_class("glow") + elif progress >= 75: + status_display.update(f"🔥 {self.status_text}") + elif progress >= 50: + status_display.update(f"⚡ {self.status_text}") + elif progress >= 25: + status_display.update(f"🔄 {self.status_text}") + else: + status_display.update(f"🚀 {self.status_text}") diff --git a/ingest_pipeline/cli/tui/widgets/tables.py b/ingest_pipeline/cli/tui/widgets/tables.py new file mode 100644 index 0000000..cab3877 --- /dev/null +++ b/ingest_pipeline/cli/tui/widgets/tables.py @@ -0,0 +1,126 @@ +"""Enhanced DataTable with improved keyboard navigation.""" + +from typing import Any + +from textual import events +from textual.binding import Binding +from textual.message import Message +from textual.widgets import DataTable + + +class EnhancedDataTable(DataTable[Any]): + """DataTable with enhanced keyboard navigation and visual feedback.""" + + BINDINGS = [ + Binding("up,k", "cursor_up", "Cursor Up", show=False), + Binding("down,j", "cursor_down", "Cursor Down", show=False), + Binding("left,h", "cursor_left", "Cursor Left", show=False), + Binding("right,l", "cursor_right", "Cursor Right", show=False), + Binding("home", "cursor_home", "First Row", show=False), + Binding("end", "cursor_end", "Last Row", show=False), + Binding("pageup", "page_up", "Page Up", show=False), + Binding("pagedown", "page_down", "Page Down", show=False), + Binding("enter", "select_cursor", "Select", show=False), + Binding("space", "toggle_selection", "Toggle Selection", show=False), + Binding("ctrl+a", "select_all", "Select All", show=False), + Binding("ctrl+shift+a", "clear_selection", "Clear Selection", show=False), + ] + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.cursor_type = "row" # Default to row selection + self.zebra_stripes = True # Enable zebra striping for better visibility + self.show_cursor = True + + def on_key(self, event: events.Key) -> None: + """Handle additional keyboard shortcuts.""" + if event.key == "ctrl+1": + # Jump to first column + self.move_cursor(column=0) + event.prevent_default() + elif event.key == "ctrl+9": + # Jump to last column + if self.columns: + self.move_cursor(column=len(self.columns) - 1) + event.prevent_default() + elif event.key == "/": + # Start quick search (to be implemented by parent) + self.post_message(self.QuickSearch(self)) + event.prevent_default() + elif event.key == "escape": + # Clear selection or exit search + # Clear selection by calling action + self.action_clear_selection() + event.prevent_default() + # No else clause needed - just handle our events + + def action_cursor_home(self) -> None: + """Move cursor to first row.""" + if self.row_count > 0: + self.move_cursor(row=0) + + def action_cursor_end(self) -> None: + """Move cursor to last row.""" + if self.row_count > 0: + self.move_cursor(row=self.row_count - 1) + + def action_page_up(self) -> None: + """Move cursor up by visible page size.""" + if self.row_count > 0: + page_size = max(1, self.size.height // 2) # Approximate visible rows + new_row = max(0, self.cursor_coordinate.row - page_size) + self.move_cursor(row=new_row) + + def action_page_down(self) -> None: + """Move cursor down by visible page size.""" + if self.row_count > 0: + page_size = max(1, self.size.height // 2) # Approximate visible rows + new_row = min(self.row_count - 1, self.cursor_coordinate.row + page_size) + self.move_cursor(row=new_row) + + def action_toggle_selection(self) -> None: + """Toggle selection of current row.""" + if self.row_count > 0: + current_row = self.cursor_coordinate.row + # This will be handled by the parent screen + self.post_message(self.RowToggled(self, current_row)) + + def action_select_all(self) -> None: + """Select all rows.""" + # This will be handled by the parent screen + self.post_message(self.SelectAll(self)) + + def action_clear_selection(self) -> None: + """Clear all selections.""" + # This will be handled by the parent screen + self.post_message(self.ClearSelection(self)) + + # Custom messages for enhanced functionality + class QuickSearch(Message): + """Posted when user wants to start a quick search.""" + + def __init__(self, table: "EnhancedDataTable") -> None: + super().__init__() + self.table = table + + class RowToggled(Message): + """Posted when a row selection is toggled.""" + + def __init__(self, table: "EnhancedDataTable", row_index: int) -> None: + super().__init__() + self.table = table + self.row_index = row_index + + class SelectAll(Message): + """Posted when user wants to select all rows.""" + + def __init__(self, table: "EnhancedDataTable") -> None: + super().__init__() + self.table = table + + class ClearSelection(Message): + """Posted when user wants to clear selection.""" + + def __init__(self, table: "EnhancedDataTable") -> None: + super().__init__() + self.table = table diff --git a/ingest_pipeline/config/__init__.py b/ingest_pipeline/config/__init__.py new file mode 100644 index 0000000..87b0151 --- /dev/null +++ b/ingest_pipeline/config/__init__.py @@ -0,0 +1,5 @@ +"""Configuration management.""" + +from .settings import Settings, get_settings + +__all__ = ["Settings", "get_settings"] diff --git a/ingest_pipeline/config/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c8e99cecfe9b891d96dc961deb81d6e19e0db0c GIT binary patch literal 262 zcmX@j%ge<81QwHzXOscy#~=<2FhLog6@ZNC3@Hpz3@MB$OgW6XOi@g^%u&pY3@OYh zEa^;9ES0R9Y*muZ`FUxX>7_-9C7Jno3b~1SiRr1isd*)OFByR*Xfoa62u>|2$;?YH zzQvQCS`uFjFrQ_*)#9@*v5?oSgXhl?_wbF832@qM6mz> literal 0 HcmV?d00001 diff --git a/ingest_pipeline/config/__pycache__/settings.cpython-312.pyc b/ingest_pipeline/config/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b302dea6a54ffdc7b488fb1bbe06d862a453895d GIT binary patch literal 3840 zcma)9O-vlg6|SD?`C%A53^V2*GyF4P7;NmU4VYbH8^;?jP9UtMKBUuh7tp4sdtBWE zW<<0oa-?1DA^2pZBvOv7?8D^dV-AsW$Yt0;^0=iait@`12TnQVRZVw;9V<#p^SbK2 zSFc{Z_q|s=f2gaAAb9@%yWg)qu0`lCbmBe%XK<{+;5kx|fD}&gX!CETO-tDBSM5jK6Do;!KX+G;hGozs$HQL`;;)QUG^z8Px%+l zWV*fEoSz%DqFJS33~;d$?>N?8Uu_3 z4&vZjgwqRtAHuxiQ~V0A1YQPT@Ks$QpoD}V4k@8R2q7F+!c!msUd{o)4rf|Dyf%Uo^~UXcYE?4koTUEH!*o1v(_&( z;}&J01-t?e1{p6cv@-98*eoQpK~&s$4m-T|GqJnKcq2jwTQy2&5Xpp+fqlo$?0bRj zd&!f(?{_DYU-sk~Wn8&(W_^muud>~~BXoh^A31(sW4!C&(*$6eS0Q^S9F8mG;fE2EiS7Xw&U(d#JX%K zP$Kp@8PF}7ST|KmUB`9+Ke323XUCUSf@LCYXrh!;#WlQX$Bi7;H}Fzk^;q>ASXx&l z3%i`cxm75fiA+Y*vZI=`WJh5DyD2$C)vZE`z2~=9zZ-*4TEaf%J_N~AVV%y*_#3jj+S4C4TVD*!k7_#fK z(i0Yb#yg}<@{m^RVG>B%Pg>yxRxoL&gBaDW#$CZVYxPh8uKG`CfoDZIX$6~h*o|D; zNBZgF2o00SQe-!|{+O0QKpp&&*B~G^uNbmvx42>%LW5=6HpLvl%jy$5M&H0T=vw+c3zQm@^FRkRn?B@c?+@nKz-sDtQfykbX?oCCyH<6B@dlCj{%3Xi4Up zc}Q;+^&-T@juHGg59tvth`y1xZ2sQd>;u4Y;P#!F#k*vjo;39E=KS2fxx04Y&iwp? zd1&Xxim2grtl1&=ph1GIJke#EHus?|mtH5wOF*8ZEl%Oq1|hZ4E%eCMIrCni8a_|s ztR+Wl0a@;~hZO%7_Y3qZpW8lv%BA_j^GM2h?qVzDA~LmXkd%{|l;iMd#8bUVR?G=+ zM%Oz!6RCx2I$cIem$LYD;XFI?du#7F<(FEVzwa4kh#`2lY z>HS2y6irv^lDpAw>v|b)Xg@JjiVpp?p}o{OR%|NPlsYEL4U?tlwDoXdLwrG163ybMvi-2DE%6jWRl zE>%zeCrq5v08FaN{|`LeJphq&hK{}cq07~Hz0|JpS};RSnkV&e5)!AR0f_B~WC3ny zddMe~QR_G(W~H1-iw!t0jIeoLm((%}r6 z+g~@2>^G0R4e?EpFKdq?sJ`vzUwrk&?nXH_Pznza3XpWK9Tr7eAfjkTMB3zOClc+b zC_c_hnj;B`BD6$NBw^}XlwAd46|C%xw%@df9TP=K*A0t(Mw+1Qot)5bK9ZosD;m;B zr%jYJ1F`+k{s?Upq=S+Rl=M*2M+tNwL?~>?5GBKuT%=@#k_;tdl(1WfMukjMG7IDz z#E#11N@x>hgbvK#0IYt7-te7&PV`mcO_kQJN>h8~T;oYiAQ{}ge^ih9(k1@Bzhk=0f3MOvT;e-+%`!g>@_}K{eYk5q`%#%6s&x0$mAhA-31z;& z($UXO4?}RLf4F%0Ro8C=2Ys{7@>$?^_3uf=o3FYLQs89sZVxy~{b(rqisnJvWYv#J z*yrOr-1faMH%d(t`?1L~fBT4!1*Z0H9U*uX?;g|FQFkmb`0H`X8r&N%Ccl|Dp~Gr6 zSt74P5@q;#4{bN>@}l5+jx48bAmsiRf(F7B9nQ1NHm~O`inkId1UFtrJAwN6^xJwMV`X*SNF#F9fe+ MKQP`>20O`r0Efv2$N&HU literal 0 HcmV?d00001 diff --git a/ingest_pipeline/config/settings.py b/ingest_pipeline/config/settings.py new file mode 100644 index 0000000..d878450 --- /dev/null +++ b/ingest_pipeline/config/settings.py @@ -0,0 +1,103 @@ +"""Application settings and configuration.""" + +from functools import lru_cache +from typing import Literal + +from pydantic import Field, HttpUrl +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Application settings.""" + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + extra="ignore", # Ignore extra environment variables + ) + + # API Keys + firecrawl_api_key: str | None = None + openwebui_api_key: str | None = None + weaviate_api_key: str | None = None + + # Endpoints + llm_endpoint: HttpUrl = HttpUrl("http://llm.lab") + weaviate_endpoint: HttpUrl = HttpUrl("http://weaviate.yo") + openwebui_endpoint: HttpUrl = HttpUrl("http://chat.lab") # This will be the API URL + firecrawl_endpoint: HttpUrl = HttpUrl("http://crawl.lab:30002") + + # Model Configuration + embedding_model: str = "ollama/bge-m3:latest" + embedding_dimension: int = 1024 + + # Ingestion Settings + default_batch_size: int = Field(default=50, gt=0, le=500) + max_file_size: int = 1_000_000 + max_crawl_depth: int = Field(default=5, ge=1, le=20) + max_crawl_pages: int = Field(default=100, ge=1, le=1000) + + # Storage Settings + default_storage_backend: Literal["weaviate", "open_webui"] = "weaviate" + default_collection_prefix: str = "docs" + + # Prefect Settings + prefect_api_url: HttpUrl | None = None + prefect_api_key: str | None = None + prefect_work_pool: str = "default" + + # Scheduling Defaults + default_schedule_interval: int = Field(default=60, ge=1, le=10080) # Max 1 week + + # Performance Settings + max_concurrent_tasks: int = Field(default=5, ge=1, le=20) + request_timeout: int = Field(default=60, ge=10, le=300) + + # Logging + log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO" + + def get_storage_endpoint(self, backend: str) -> HttpUrl: + """ + Get endpoint for storage backend. + + Args: + backend: Storage backend name + + Returns: + Endpoint URL + """ + if backend == "weaviate": + return self.weaviate_endpoint + elif backend == "open_webui": + return self.openwebui_endpoint + else: + raise ValueError(f"Unknown backend: {backend}") + + def get_api_key(self, service: str) -> str | None: + """ + Get API key for service. + + Args: + service: Service name + + Returns: + API key or None + """ + service_map = { + "firecrawl": self.firecrawl_api_key, + "openwebui": self.openwebui_api_key, + "weaviate": self.weaviate_api_key, + } + return service_map.get(service) + + +@lru_cache +def get_settings() -> Settings: + """ + Get cached settings instance. + + Returns: + Settings instance + """ + return Settings() diff --git a/ingest_pipeline/core/__init__.py b/ingest_pipeline/core/__init__.py new file mode 100644 index 0000000..43c732d --- /dev/null +++ b/ingest_pipeline/core/__init__.py @@ -0,0 +1,27 @@ +"""Core module for ingestion pipeline.""" + +from .exceptions import ( + IngestionError, + StorageError, + VectorizationError, +) +from .models import ( + Document, + IngestionJob, + IngestionResult, + IngestionSource, + IngestionStatus, + StorageBackend, +) + +__all__ = [ + "Document", + "IngestionJob", + "IngestionResult", + "IngestionSource", + "IngestionStatus", + "StorageBackend", + "IngestionError", + "StorageError", + "VectorizationError", +] diff --git a/ingest_pipeline/core/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d2cabdb6716a6dd924e5e2e742186bcb001da30 GIT binary patch literal 560 zcmaKoy-ve05XbE#P13Xl0WVOADGM1{5CfC}1_mk^yjUSQN`xI(c2ZF`2A%=2vhgGg zJOLhIV&h{#V#1~$LJZvS)BVqP=hOe8(}|FYtDF1NCydau6|-=r$w$v5*CYVOI+>|58Il{8r&y74@kg667nW#@`yydMOqjQQ10Dg(jGTr|6+ApNa%8r*9^KR zLUxOC0IDiP*&P-`U_}XQFXj*wyLw@F_O>IXkhxX6UTaSt`0!etol{xi&|#zyHKT7t@Oh5yR9@kgEEhs(l>X>z#_D0T3GvJ z%fhwrEE)#;aYqM~<|3n1M{u6O(B7&3v}xaX0ZjeB+ipr3qx24anBC}WJvme$RZ_`x zl;kiSOHXQ44W8(d$TrqpB(Q4J;)x{V;qWIJEu=9ot TKBK?@8N4q?E$1?jOQ!D|VZEC> literal 0 HcmV?d00001 diff --git a/ingest_pipeline/core/__pycache__/exceptions.cpython-312.pyc b/ingest_pipeline/core/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a37de5fef6834ed5598f67e590d3f86d6f8fdd2d GIT binary patch literal 1215 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 DOgO(j literal 0 HcmV?d00001 diff --git a/ingest_pipeline/core/exceptions.py b/ingest_pipeline/core/exceptions.py new file mode 100644 index 0000000..08696c3 --- /dev/null +++ b/ingest_pipeline/core/exceptions.py @@ -0,0 +1,31 @@ +"""Custom exceptions for the ingestion pipeline.""" + + +class IngestionError(Exception): + """Base exception for ingestion errors.""" + + pass + + +class StorageError(IngestionError): + """Exception for storage-related errors.""" + + pass + + +class VectorizationError(IngestionError): + """Exception for vectorization errors.""" + + pass + + +class ConfigurationError(IngestionError): + """Exception for configuration errors.""" + + pass + + +class SourceNotFoundError(IngestionError): + """Exception when source cannot be found or accessed.""" + + pass diff --git a/ingest_pipeline/core/models.py b/ingest_pipeline/core/models.py new file mode 100644 index 0000000..b5b21f8 --- /dev/null +++ b/ingest_pipeline/core/models.py @@ -0,0 +1,149 @@ +"""Core data models with strict typing.""" + +from collections.abc import Callable +from datetime import UTC, datetime +from enum import Enum +from typing import TypedDict +from uuid import UUID, uuid4 + +from pydantic import BaseModel, Field, HttpUrl + + +class IngestionStatus(str, Enum): + """Status of an ingestion job.""" + + PENDING = "pending" + IN_PROGRESS = "in_progress" + COMPLETED = "completed" + PARTIAL = "partial" # Some documents succeeded, some failed + FAILED = "failed" + CANCELLED = "cancelled" + + +class StorageBackend(str, Enum): + """Available storage backends.""" + + WEAVIATE = "weaviate" + OPEN_WEBUI = "open_webui" + + +class IngestionSource(str, Enum): + """Types of ingestion sources.""" + + WEB = "web" + REPOSITORY = "repository" + DOCUMENTATION = "documentation" + + +class VectorConfig(BaseModel): + """Configuration for vectorization.""" + + model: str = Field(default="ollama/bge-m3:latest") + embedding_endpoint: HttpUrl = Field(default=HttpUrl("http://llm.lab")) + dimension: int = Field(default=1024) + batch_size: int = Field(default=100, gt=0, le=1000) + + +class StorageConfig(BaseModel): + """Configuration for storage backend.""" + + backend: StorageBackend + endpoint: HttpUrl + api_key: str | None = Field(default=None) + collection_name: str = Field(default="documents") + batch_size: int = Field(default=100, gt=0, le=1000) + + +class FirecrawlConfig(BaseModel): + """Configuration for Firecrawl ingestion (operational parameters only).""" + + formats: list[str] = Field(default_factory=lambda: ["markdown", "html"]) + max_depth: int = Field(default=5, ge=1, le=20) + limit: int = Field(default=100, ge=1, le=1000) + only_main_content: bool = Field(default=True) + include_subdomains: bool = Field(default=False) + + +class RepomixConfig(BaseModel): + """Configuration for Repomix ingestion.""" + + include_patterns: list[str] = Field( + default_factory=lambda: ["*.py", "*.js", "*.ts", "*.md", "*.yaml", "*.json"] + ) + exclude_patterns: list[str] = Field( + default_factory=lambda: ["**/node_modules/**", "**/__pycache__/**", "**/.git/**"] + ) + max_file_size: int = Field(default=1_000_000) # 1MB + respect_gitignore: bool = Field(default=True) + + +class DocumentMetadata(TypedDict): + """Metadata for a document.""" + + source_url: str + title: str | None + description: str | None + timestamp: datetime + content_type: str + word_count: int + char_count: int + + +class Document(BaseModel): + """Represents a single document.""" + + id: UUID = Field(default_factory=uuid4) + content: str + metadata: DocumentMetadata + vector: list[float] | None = Field(default=None) + source: IngestionSource + collection: str = Field(default="documents") + + class Config: + """Pydantic configuration.""" + + json_encoders: dict[type, Callable[[UUID | datetime], str]] = { + UUID: lambda v: str(v) if isinstance(v, UUID) else str(v), + datetime: lambda v: v.isoformat() if isinstance(v, datetime) else str(v), + } + + +class IngestionJob(BaseModel): + """Represents an ingestion job.""" + + id: UUID = Field(default_factory=uuid4) + source_type: IngestionSource + source_url: HttpUrl | str + status: IngestionStatus = Field(default=IngestionStatus.PENDING) + created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) + updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) + completed_at: datetime | None = Field(default=None) + error_message: str | None = Field(default=None) + document_count: int = Field(default=0) + storage_backend: StorageBackend + + class Config: + """Pydantic configuration.""" + + json_encoders: dict[type, Callable[[UUID | datetime], str]] = { + UUID: lambda v: str(v) if isinstance(v, UUID) else str(v), + datetime: lambda v: v.isoformat() if isinstance(v, datetime) else str(v), + } + + +class IngestionResult(BaseModel): + """Result of an ingestion operation.""" + + job_id: UUID + status: IngestionStatus + documents_processed: int + documents_failed: int + duration_seconds: float + error_messages: list[str] = Field(default_factory=list) + + class Config: + """Pydantic configuration.""" + + json_encoders: dict[type, Callable[[UUID], str]] = { + UUID: lambda v: str(v), + } diff --git a/ingest_pipeline/flows/__init__.py b/ingest_pipeline/flows/__init__.py new file mode 100644 index 0000000..1a30ea4 --- /dev/null +++ b/ingest_pipeline/flows/__init__.py @@ -0,0 +1,9 @@ +"""Prefect flows for orchestration.""" + +from .ingestion import create_ingestion_flow +from .scheduler import create_scheduled_deployment + +__all__ = [ + "create_ingestion_flow", + "create_scheduled_deployment", +] diff --git a/ingest_pipeline/flows/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/flows/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9607e75086b34223cbd54718448955179d746d09 GIT binary patch literal 326 zcmX|7y-LJD5Z=Eeo_d9a)hWbA49E%A;v>k_X@-#9+yyq-W3wW$^A&7-2A{;j2T%i6 zVqq7!&c>S!cTO?$4d3_84D&i3?-Go^$Ct$$)-T^|gKfa-48aqTBqfqcrfJ1eMm^@5 zS0WXNg$(8_4cah??&FzNh00xAXmi)Zg|V@*`9d|$X09~#^oQcWgm#CyRhd&z)^jA0 z0q;O!vLPCD$y==?NVU|atyJwSN3#`}Zj!Kz0HnzQy3zlTLC}r1UOQ;LFw+{~nXJ%C zeh-G@E&!L84q&=$yPd1b_-)sA9~J(_<``4YDWxCe^qZW2lFL3M!O^=o=>xvU9wO`C Fw+5QdTUP)8 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/flows/__pycache__/ingestion.cpython-312.pyc b/ingest_pipeline/flows/__pycache__/ingestion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..926a5db3aa99de2a7d210a438dfce4031b221d2f GIT binary patch literal 10116 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 bool: + """ + Validate that a source is accessible. + + Args: + source_url: URL or path to source + source_type: Type of source + + Returns: + True if valid + """ + if source_type == IngestionSource.WEB: + ingestor = FirecrawlIngestor() + elif source_type == IngestionSource.REPOSITORY: + ingestor = RepomixIngestor() + else: + raise ValueError(f"Unsupported source type: {source_type}") + + result = await ingestor.validate_source(source_url) + return bool(result) + + +@task(name="initialize_storage", retries=3, retry_delay_seconds=5, tags=["storage"]) +async def initialize_storage_task(config: StorageConfig) -> BaseStorage: + """ + Initialize storage backend. + + Args: + config: Storage configuration + + Returns: + Initialized storage adapter + """ + if config.backend == StorageBackend.WEAVIATE: + storage = WeaviateStorage(config) + elif config.backend == StorageBackend.OPEN_WEBUI: + storage = OpenWebUIStorage(config) + else: + raise ValueError(f"Unsupported backend: {config.backend}") + + await storage.initialize() + return storage + + +@task(name="ingest_documents", retries=2, retry_delay_seconds=30, tags=["ingestion"]) +async def ingest_documents_task(job: IngestionJob, collection_name: str | None = None, batch_size: int = 50) -> tuple[int, int]: + """ + Ingest documents from source. + + Args: + job: Ingestion job configuration + batch_size: Number of documents per batch + + Returns: + Tuple of (processed_count, failed_count) + """ + # Select ingestor + if job.source_type == IngestionSource.WEB: + config = FirecrawlConfig() + ingestor = FirecrawlIngestor(config) + elif job.source_type == IngestionSource.REPOSITORY: + config = RepomixConfig() + ingestor = RepomixIngestor(config) + else: + raise ValueError(f"Unsupported source: {job.source_type}") + + processed = 0 + failed = 0 + batch = [] + + # Initialize storage + from pydantic import HttpUrl + + # Use provided collection name or generate default + if collection_name is None: + collection_name = f"docs_{job.source_type.value}" + + storage_config = StorageConfig( + backend=job.storage_backend, + endpoint=HttpUrl("http://weaviate.yo") + if job.storage_backend == StorageBackend.WEAVIATE + else HttpUrl("http://chat.lab"), + collection_name=collection_name, + ) + + if job.storage_backend == StorageBackend.WEAVIATE: + storage = WeaviateStorage(storage_config) + else: + storage = OpenWebUIStorage(storage_config) + + await storage.initialize() + + # Process documents + async for document in ingestor.ingest(job): + batch.append(document) + + if len(batch) >= batch_size: + try: + stored_ids = await storage.store_batch(batch) + print(f"Successfully stored {len(stored_ids)} documents in batch") + processed += len(stored_ids) + failed += len(batch) - len(stored_ids) + except Exception as e: + print(f"Batch storage failed: {e}") + failed += len(batch) + batch = [] + + # Process remaining batch + if batch: + try: + stored_ids = await storage.store_batch(batch) + print(f"Successfully stored {len(stored_ids)} documents in final batch") + processed += len(stored_ids) + failed += len(batch) - len(stored_ids) + except Exception as e: + print(f"Final batch storage failed: {e}") + failed += len(batch) + + return processed, failed + + +@task(name="update_job_status", tags=["tracking"]) +async def update_job_status_task( + job: IngestionJob, + status: IngestionStatus, + processed: int = 0, + failed: int = 0, + error: str | None = None, +) -> IngestionJob: + """ + Update job status. + + Args: + job: Ingestion job + status: New status + processed: Documents processed + failed: Documents failed + error: Error message if any + + Returns: + Updated job + """ + job.status = status + job.updated_at = datetime.now(UTC) + job.document_count = processed + + if status == IngestionStatus.COMPLETED: + job.completed_at = datetime.now(UTC) + + if error: + job.error_message = error + + return job + + +@flow( + name="ingestion_pipeline", + description="Main ingestion pipeline for documents", + retries=1, + retry_delay_seconds=60, + persist_result=True, + log_prints=True, +) +async def create_ingestion_flow( + source_url: str, + source_type: Literal["web", "repository", "documentation"], + storage_backend: Literal["weaviate", "open_webui"] = "weaviate", + collection_name: str | None = None, + validate_first: bool = True, +) -> IngestionResult: + """ + Main ingestion flow. + + Args: + source_url: URL or path to source + source_type: Type of source + storage_backend: Storage backend to use + validate_first: Whether to validate source first + + Returns: + Ingestion result + """ + print(f"Starting ingestion from {source_url}") + + # Create job + job = IngestionJob( + source_url=source_url, + source_type=IngestionSource(source_type), + storage_backend=StorageBackend(storage_backend), + status=IngestionStatus.PENDING, + ) + + start_time = datetime.now(UTC) + error_messages = [] + processed = 0 + failed = 0 + + try: + # Validate source if requested + if validate_first: + print("Validating source...") + is_valid = await validate_source_task(source_url, job.source_type) + + if not is_valid: + raise IngestionError(f"Source validation failed: {source_url}") + + # Update status to in progress + job = await update_job_status_task(job, IngestionStatus.IN_PROGRESS) + + # Run ingestion + print("Ingesting documents...") + processed, failed = await ingest_documents_task(job, collection_name) + + # Update final status + if failed > 0: + error_messages.append(f"{failed} documents failed to process") + + # Set status based on results + if processed == 0 and failed > 0: + final_status = IngestionStatus.FAILED + elif failed > 0: + final_status = IngestionStatus.PARTIAL + else: + final_status = IngestionStatus.COMPLETED + + job = await update_job_status_task(job, final_status, processed=processed, failed=failed) + + print(f"Ingestion completed: {processed} processed, {failed} failed") + + except Exception as e: + print(f"Ingestion failed: {e}") + error_messages.append(str(e)) + + # Don't reset counts - keep whatever was processed before the error + job = await update_job_status_task(job, IngestionStatus.FAILED, + processed=processed, + failed=failed, + error=str(e)) + + # Calculate duration + duration = (datetime.now(UTC) - start_time).total_seconds() + + return IngestionResult( + job_id=job.id, + status=job.status, + documents_processed=processed, + documents_failed=failed, + duration_seconds=duration, + error_messages=error_messages, + ) diff --git a/ingest_pipeline/flows/scheduler.py b/ingest_pipeline/flows/scheduler.py new file mode 100644 index 0000000..e325f5f --- /dev/null +++ b/ingest_pipeline/flows/scheduler.py @@ -0,0 +1,89 @@ +"""Scheduler for Prefect deployments.""" + +from datetime import timedelta +from typing import TYPE_CHECKING, Literal, Protocol + +from prefect import serve +from prefect.deployments.runner import RunnerDeployment +from prefect.schedules import Cron, Interval + +from .ingestion import create_ingestion_flow + + +class FlowWithDeployment(Protocol): + """Protocol for flows that have deployment methods.""" + + def to_deployment( + self, + name: str, + **kwargs: object, + ) -> RunnerDeployment: + """Create a deployment from this flow.""" + ... + + +def create_scheduled_deployment( + name: str, + source_url: str, + source_type: Literal["web", "repository", "documentation"], + storage_backend: Literal["weaviate", "open_webui"] = "weaviate", + schedule_type: Literal["cron", "interval"] = "interval", + cron_expression: str | None = None, + interval_minutes: int = 60, + tags: list[str] | None = None, +) -> RunnerDeployment: + """ + Create a scheduled deployment for ingestion. + + Args: + name: Deployment name + source_url: Source to ingest from + source_type: Type of source + storage_backend: Storage backend + schedule_type: Type of schedule + cron_expression: Cron expression if using cron + interval_minutes: Interval in minutes if using interval + tags: Optional tags for deployment + + Returns: + Deployment configuration + """ + # Create schedule + if schedule_type == "cron" and cron_expression: + schedule = Cron(cron_expression, timezone="UTC") + else: + schedule = Interval(timedelta(minutes=interval_minutes), timezone="UTC") + + # Default tags + if tags is None: + tags = [source_type, storage_backend] + + # Create deployment + # The flow decorator adds the to_deployment method at runtime + to_deployment = create_ingestion_flow.to_deployment + deployment = to_deployment( + name=name, + schedule=schedule, + parameters={ + "source_url": source_url, + "source_type": source_type, + "storage_backend": storage_backend, + "validate_first": True, + }, + tags=tags, + description=f"Scheduled ingestion from {source_url}", + ) + + from typing import cast + + return cast("RunnerDeployment", deployment) + + +def serve_deployments(deployments: list[RunnerDeployment]) -> None: + """ + Serve multiple deployments. + + Args: + deployments: List of deployment configurations + """ + serve(*deployments, limit=10) diff --git a/ingest_pipeline/ingestors/__init__.py b/ingest_pipeline/ingestors/__init__.py new file mode 100644 index 0000000..3056048 --- /dev/null +++ b/ingest_pipeline/ingestors/__init__.py @@ -0,0 +1,11 @@ +"""Ingestors module for different data sources.""" + +from .base import BaseIngestor +from .firecrawl import FirecrawlIngestor +from .repomix import RepomixIngestor + +__all__ = [ + "BaseIngestor", + "FirecrawlIngestor", + "RepomixIngestor", +] diff --git a/ingest_pipeline/ingestors/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/ingestors/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9062dc00f98d8eb5196e8d0d779ef8dd619e7e3e GIT binary patch literal 365 zcmX@j%ge<81dNl8XM6$Dk3k$5V1hC}n*bTp8B!Rc7*ZHhm~t3%nWC5&L2Tw6=3JI2 z7BHJ7hc%Ziij9#WogsyF5n~j4C7ULDm5yg#dTMb=eo?VPZhlH>PO3s$evv{-W?EWm zQEFa^LP}yuqC#njE*-`U7W1Ij0 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/ingestors/__pycache__/base.cpython-312.pyc b/ingest_pipeline/ingestors/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..247c2568983dfe37004a3a21dec320c7e3df492a GIT binary patch literal 1824 zcmb_d&x;&I6t4a;Gd;62v+8P$i-r;tWjxG81d&CAjY=@e3Lz2fOH+b2DT|@9-!Bo9h@6~(n`|5Sg z54~Q8z}G$J|Esew}F{k+ZSHT z`)sUJ#r!np=|lztBkynPY2shx30Djb-Z4vW=XL34F;CLG`(_a{lF21G_RE~*+^Uqt z1cYmZ6W0JhW?;A$w*!+m0*g0!E3iZJ3GfI3%z=HQI^;Uw)8uXN>42rRVp*##U9dPS zmfqAJwDT@PEvu4h_Uv{AiH?y0Uofp{2ox|OYCiNZx89fIys3Dasbt4Tn9G5(1=FGn z!dt`uEK}o4>qBE;&OYzZYLp3Sz*0s-C1c8{mYD*EJGIZY+SOaNh6f?(7QHfA-VCsa zThy0H80}_2IqV#re=p)9I1yoa5daFarVy*lTj$xij`wS8E<&{i5Htd!^zWBPS2b69 z1Q)(P3i$qri>Z#-sB98%FWTcGN_eG9@I(fOd(*sGy396KnI4v#NRq}hzmdN_BJ)$E zy?$>2eNH|$Z6oiLwRxEm|Kj))aJnDI-_%InWg-HCfvs=_D2+5_zR$Ie#^S+rA7$U7 zS1-R!p$+y}I-#jtiFd^J<)Rx-5Pn5vMFC|jOumn0QI^23E~Bn_NA&^MbyKawqBdZv z_*V~soUa#$0h3q8n>~4q(-YQBII=iKZttFY1UCOOncvohy`j@6Ml!Kwk}Mu`MP*2X z>ciIXajcN?z%KqjXkC^Q+^?3p=#owK*hw0D@T?VRJRQyWZ1Wh2$868RQS~@Z$lIWw zJD!)Yn0sE{@w`|DnLxSgc^_mgi<>JA;mT3Y6Sp zV_)H4MgcKj_oWcr$LB{6(S&gGHD4+|1dh1SCH8`f0upw48Mo+b=arJmSJgQ%81U1W hXKopWaf_U}O`iFgJb#;<|D!oDdY``dCxN9n{tuE8)7}68 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/ingestors/__pycache__/firecrawl.cpython-312.pyc b/ingest_pipeline/ingestors/__pycache__/firecrawl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5451218670e6a0ed49f84087f66c6ab074a23e6 GIT binary patch literal 8837 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/ingestors/__pycache__/repomix.cpython-312.pyc b/ingest_pipeline/ingestors/__pycache__/repomix.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c1e72281fb9fcc7db12d140f919571a131259c0 GIT binary patch literal 13308 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 HA$|NW082Q? literal 0 HcmV?d00001 diff --git a/ingest_pipeline/ingestors/base.py b/ingest_pipeline/ingestors/base.py new file mode 100644 index 0000000..4b604f1 --- /dev/null +++ b/ingest_pipeline/ingestors/base.py @@ -0,0 +1,50 @@ +"""Base ingestor interface.""" + +from abc import ABC, abstractmethod +from collections.abc import AsyncGenerator + +from ..core.models import Document, IngestionJob + + +class BaseIngestor(ABC): + """Abstract base class for all ingestors.""" + + @abstractmethod + async def ingest(self, job: IngestionJob) -> AsyncGenerator[Document, None]: + """ + Ingest data from a source. + + Args: + job: The ingestion job configuration + + Yields: + Documents from the source + """ + return # type: ignore # pragma: no cover + yield # pragma: no cover + + @abstractmethod + async def validate_source(self, source_url: str) -> bool: + """ + Validate if the source is accessible. + + Args: + source_url: URL or path to the source + + Returns: + True if source is valid and accessible + """ + pass # pragma: no cover + + @abstractmethod + async def estimate_size(self, source_url: str) -> int: + """ + Estimate the number of documents in the source. + + Args: + source_url: URL or path to the source + + Returns: + Estimated number of documents + """ + pass # pragma: no cover diff --git a/ingest_pipeline/ingestors/firecrawl.py b/ingest_pipeline/ingestors/firecrawl.py new file mode 100644 index 0000000..b8c0d82 --- /dev/null +++ b/ingest_pipeline/ingestors/firecrawl.py @@ -0,0 +1,229 @@ +"""Firecrawl ingestor for web and documentation sites.""" + +import asyncio +from collections.abc import AsyncGenerator +from datetime import UTC, datetime +from typing import Any +from uuid import uuid4 + +from firecrawl import AsyncFirecrawl +from typing_extensions import override + +from ..config import get_settings +from ..core.models import ( + Document, + DocumentMetadata, + FirecrawlConfig, + IngestionJob, + IngestionSource, +) +from .base import BaseIngestor + + +class FirecrawlIngestor(BaseIngestor): + """Ingestor for web and documentation sites using Firecrawl.""" + + config: FirecrawlConfig + client: Any # AsyncFirecrawl client instance + + def __init__(self, config: FirecrawlConfig | None = None): + """ + Initialize Firecrawl ingestor. + + Args: + config: Firecrawl configuration (for operational params only) + """ + self.config = config or FirecrawlConfig() + settings = get_settings() + + # All connection details come from settings/.env + # For self-hosted instances, use a dummy API key if none is provided + # The SDK requires an API key even for self-hosted instances + api_key = settings.firecrawl_api_key or "no-key-required" + + # AsyncFirecrawl automatically uses v2 endpoints + self.client = AsyncFirecrawl(api_key=api_key, api_url=str(settings.firecrawl_endpoint)) + + @override + async def ingest(self, job: IngestionJob) -> AsyncGenerator[Document, None]: + """ + Ingest documents from a web source. + + Args: + job: The ingestion job configuration + + Yields: + Documents from the web source + """ + url = str(job.source_url) + + # First, map the site to understand its structure + site_map = await self._map_site(url) + + # If map returns empty, just use the main URL + if not site_map: + site_map = [url] + + # Process pages in batches + batch_size = 10 + for i in range(0, len(site_map), batch_size): + batch_urls = site_map[i : i + batch_size] + documents = await self._scrape_batch(batch_urls) + + for doc_data in documents: + yield self._create_document(doc_data, job) + + @override + async def validate_source(self, source_url: str) -> bool: + """ + Validate if the web source is accessible. + + Args: + source_url: URL to validate + + Returns: + True if source is accessible + """ + try: + # Use SDK v2 endpoints for both self-hosted and cloud + result = await self.client.scrape(source_url, formats=["markdown"]) + return result is not None and hasattr(result, "markdown") + except Exception: + return False + + @override + async def estimate_size(self, source_url: str) -> int: + """ + Estimate the number of pages in the website. + + Args: + source_url: URL of the website + + Returns: + Estimated number of pages + """ + try: + site_map = await self._map_site(source_url) + return len(site_map) if site_map else 0 + except Exception: + return 0 + + async def _map_site(self, url: str) -> list[str]: + """ + Map a website to get all URLs. + + Args: + url: Base URL to map + + Returns: + List of URLs found + """ + try: + # Use SDK v2 map endpoint + result = await self.client.map(url=url, limit=self.config.limit) + + if result and hasattr(result, "links"): + # Extract URLs from the result + return [ + link if isinstance(link, str) else getattr(link, "url", str(link)) + for link in result.links + ] + return [] + except Exception as e: + # If map fails (might not be available in all versions), fall back to single URL + import logging + + logging.warning(f"Map endpoint not available or failed: {e}. Using single URL.") + return [url] + + async def _scrape_batch(self, urls: list[str]) -> list[dict[str, str]]: + """ + Scrape a batch of URLs. + + Args: + urls: List of URLs to scrape + + Returns: + List of scraped documents + """ + tasks = [] + for url in urls: + task = self._scrape_single(url) + tasks.append(task) + + results = await asyncio.gather(*tasks, return_exceptions=True) + + documents = [] + for result in results: + if isinstance(result, Exception): + continue + if result and isinstance(result, dict) and "markdown" in result: + documents.append(result) + + return documents + + async def _scrape_single(self, url: str) -> dict[str, str]: + """ + Scrape a single URL. + + Args: + url: URL to scrape + + Returns: + Scraped document data + """ + try: + # Use SDK v2 scrape endpoint + result = await self.client.scrape(url, formats=self.config.formats) + + # Extract data from the result + if result: + # The SDK returns a ScrapeResult object with markdown and metadata + metadata = getattr(result, "metadata", {}) + return { + "markdown": getattr(result, "markdown", ""), + "sourceURL": url, + "title": metadata.get("title", "") + if isinstance(metadata, dict) + else getattr(metadata, "title", ""), + "description": metadata.get("description", "") + if isinstance(metadata, dict) + else getattr(metadata, "description", ""), + } + return {} + except Exception as e: + import logging + + logging.debug(f"Failed to scrape {url}: {e}") + return {} + + def _create_document(self, doc_data: dict[str, str], job: IngestionJob) -> Document: + """ + Create a Document from scraped data. + + Args: + doc_data: Scraped document data + job: The ingestion job + + Returns: + Document instance + """ + content = doc_data.get("markdown", "") + + metadata: DocumentMetadata = { + "source_url": doc_data.get("sourceURL", str(job.source_url)), + "title": doc_data.get("title"), + "description": doc_data.get("description"), + "timestamp": datetime.now(UTC), + "content_type": "text/markdown", + "word_count": len(content.split()), + "char_count": len(content), + } + + return Document( + id=uuid4(), + content=content, + metadata=metadata, + source=IngestionSource.WEB, + collection=job.storage_backend.value, + ) diff --git a/ingest_pipeline/ingestors/repomix.py b/ingest_pipeline/ingestors/repomix.py new file mode 100644 index 0000000..2559b4c --- /dev/null +++ b/ingest_pipeline/ingestors/repomix.py @@ -0,0 +1,339 @@ +"""Repomix ingestor for Git repositories.""" + +import asyncio +import subprocess +import tempfile +from collections.abc import AsyncGenerator +from datetime import UTC, datetime +from pathlib import Path +from uuid import uuid4 + +from typing_extensions import override + +from ..core.exceptions import IngestionError, SourceNotFoundError +from ..core.models import ( + Document, + DocumentMetadata, + IngestionJob, + IngestionSource, + RepomixConfig, +) +from .base import BaseIngestor + + +class RepomixIngestor(BaseIngestor): + """Ingestor for Git repositories using Repomix.""" + + config: RepomixConfig + + def __init__(self, config: RepomixConfig | None = None): + """ + Initialize Repomix ingestor. + + Args: + config: Repomix configuration + """ + self.config = config or RepomixConfig() + + @override + async def ingest(self, job: IngestionJob) -> AsyncGenerator[Document, None]: + """ + Ingest documents from a Git repository. + + Args: + job: The ingestion job configuration + + Yields: + Documents from the repository + """ + repo_url = str(job.source_url) + + with tempfile.TemporaryDirectory() as temp_dir: + # Clone the repository + repo_path = await self._clone_repository(repo_url, temp_dir) + + # Run repomix to generate output + output_file = await self._run_repomix(repo_path) + + # Parse and yield documents + documents = await self._parse_repomix_output(output_file, job) + for doc in documents: + yield doc + + @override + async def validate_source(self, source_url: str) -> bool: + """ + Validate if the Git repository is accessible. + + Args: + source_url: Git repository URL + + Returns: + True if repository is accessible + """ + try: + # Test if we can list remote refs + result = await self._run_command( + ["git", "ls-remote", "--heads", source_url], timeout=10 + ) + return result.returncode == 0 + except Exception: + return False + + @override + async def estimate_size(self, source_url: str) -> int: + """ + Estimate the number of files in the repository. + + Args: + source_url: Git repository URL + + Returns: + Estimated number of files + """ + try: + with tempfile.TemporaryDirectory() as temp_dir: + # Shallow clone to get file count + repo_path = await self._clone_repository(source_url, temp_dir, shallow=True) + + # Count files matching patterns + file_count = 0 + for pattern in self.config.include_patterns: + files = list(Path(repo_path).rglob(pattern)) + file_count += len(files) + + return file_count + except Exception: + return 0 + + async def _clone_repository( + self, repo_url: str, target_dir: str, shallow: bool = False + ) -> Path: + """ + Clone a Git repository. + + Args: + repo_url: Repository URL + target_dir: Directory to clone into + shallow: Whether to do a shallow clone + + Returns: + Path to cloned repository + """ + repo_name = repo_url.split("/")[-1].replace(".git", "") + repo_path = Path(target_dir) / repo_name + + cmd = ["git", "clone"] + if shallow: + cmd.extend(["--depth", "1"]) + cmd.extend([repo_url, str(repo_path)]) + + result = await self._run_command(cmd, timeout=300) + + if result.returncode != 0: + raise SourceNotFoundError(f"Failed to clone repository: {repo_url}") + + return repo_path + + async def _run_repomix(self, repo_path: Path) -> Path: + """ + Run repomix on a repository. + + Args: + repo_path: Path to the repository + + Returns: + Path to repomix output file + """ + output_file = repo_path / "repomix-output.md" + + # Build repomix command + cmd = ["npx", "repomix", "--output", str(output_file)] + + # Add include patterns + if self.config.include_patterns: + for pattern in self.config.include_patterns: + cmd.extend(["--include", pattern]) + + # Add exclude patterns + if self.config.exclude_patterns: + for pattern in self.config.exclude_patterns: + cmd.extend(["--exclude", pattern]) + + if self.config.respect_gitignore: + cmd.append("--respect-gitignore") + + result = await self._run_command(cmd, cwd=str(repo_path), timeout=120) + + if result.returncode != 0: + stderr_text = ( + result.stderr.decode() if isinstance(result.stderr, bytes) else result.stderr + ) + raise IngestionError(f"Repomix failed: {stderr_text}") + + return output_file + + async def _parse_repomix_output(self, output_file: Path, job: IngestionJob) -> list[Document]: + """ + Parse repomix output into documents. + + Args: + output_file: Path to repomix output + job: The ingestion job + + Returns: + List of documents + """ + documents = [] + + try: + content = output_file.read_text() + + # Split by file markers (repomix uses specific delimiters) + file_sections = self._split_by_files(content) + + for file_path, file_content in file_sections.items(): + if len(file_content) > self.config.max_file_size: + # Split large files into chunks + chunks = self._chunk_content(file_content) + for i, chunk in enumerate(chunks): + doc = self._create_document(file_path, chunk, job, chunk_index=i) + documents.append(doc) + else: + doc = self._create_document(file_path, file_content, job) + documents.append(doc) + + except Exception as e: + raise IngestionError(f"Failed to parse repomix output: {e}") from e + + return documents + + def _split_by_files(self, content: str) -> dict[str, str]: + """ + Split repomix output by files. + + Args: + content: Repomix output content + + Returns: + Dictionary of file paths to content + """ + files: dict[str, str] = {} + current_file: str | None = None + current_content: list[str] = [] + + for line in content.split("\n"): + # Look for file markers (adjust based on actual repomix format) + if line.startswith("## File:") or line.startswith("### "): + if current_file: + files[current_file] = "\n".join(current_content) + current_file = line.replace("## File:", "").replace("### ", "").strip() + current_content = [] + else: + current_content.append(line) + + # Add last file + if current_file: + files[current_file] = "\n".join(current_content) + + # If no file markers found, treat as single document + if not files: + files["repository"] = content + + return files + + def _chunk_content(self, content: str, chunk_size: int = 500000) -> list[str]: + """ + Split content into chunks. + + Args: + content: Content to chunk + chunk_size: Maximum size per chunk + + Returns: + List of content chunks + """ + chunks: list[str] = [] + lines = content.split("\n") + current_chunk: list[str] = [] + current_size = 0 + + for line in lines: + line_size = len(line) + 1 # +1 for newline + + if current_size + line_size > chunk_size and current_chunk: + chunks.append("\n".join(current_chunk)) + current_chunk = [] + current_size = 0 + + current_chunk.append(line) + current_size += line_size + + if current_chunk: + chunks.append("\n".join(current_chunk)) + + return chunks + + def _create_document( + self, file_path: str, content: str, job: IngestionJob, chunk_index: int = 0 + ) -> Document: + """ + Create a Document from repository content. + + Args: + file_path: Path to the file in repository + content: File content + job: The ingestion job + chunk_index: Index if content is chunked + + Returns: + Document instance + """ + metadata: DocumentMetadata = { + "source_url": str(job.source_url), + "title": f"{file_path}" + (f" (chunk {chunk_index})" if chunk_index > 0 else ""), + "description": f"Repository file: {file_path}", + "timestamp": datetime.now(UTC), + "content_type": "text/plain", + "word_count": len(content.split()), + "char_count": len(content), + } + + return Document( + id=uuid4(), + content=content, + metadata=metadata, + source=IngestionSource.REPOSITORY, + collection=job.storage_backend.value, + ) + + async def _run_command( + self, cmd: list[str], cwd: str | None = None, timeout: int = 60 + ) -> subprocess.CompletedProcess[bytes]: + """ + Run a shell command asynchronously. + + Args: + cmd: Command and arguments + cwd: Working directory + timeout: Command timeout in seconds + + Returns: + Completed process result + """ + proc = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd + ) + + try: + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout) + + return subprocess.CompletedProcess( + cmd, + proc.returncode or 0, + stdout, + stderr, + ) + except TimeoutError as e: + proc.kill() + raise IngestionError(f"Command timed out: {' '.join(cmd)}") from e diff --git a/ingest_pipeline/storage/__init__.py b/ingest_pipeline/storage/__init__.py new file mode 100644 index 0000000..43b8168 --- /dev/null +++ b/ingest_pipeline/storage/__init__.py @@ -0,0 +1,11 @@ +"""Storage adapters for different backends.""" + +from .base import BaseStorage +from .openwebui import OpenWebUIStorage +from .weaviate import WeaviateStorage + +__all__ = [ + "BaseStorage", + "WeaviateStorage", + "OpenWebUIStorage", +] diff --git a/ingest_pipeline/storage/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/storage/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fa438923f75a319aa8f03a37cbed1163218f8ed GIT binary patch literal 397 zcmYLEJx{|h5VaGx36ugO3sM#YUFwAiAqF-U7*JIyi{;eUV&Qzqc3YK=f!}~w+4v<4 z`~o6%LJSN@h;B?cp}-B#@6PYN(>=A@tH{;O-Q&f(dta*Nx4b1;>^pLc1d5Ts!c#c) zVh>l`SAI%jQgfmjX)|tOG(w_rkK;zYP=Qh`T1Q8d!u%Jye%ylH>a0nVQ8zp>j z37Ih6H|&;laM?QM29_q*Zk`p8^&#n<{LeP}z$cR1Kbfz6XV#4hh=bEkSO$!Ve85;q z5*Jtt%9Y%OO(7Y}T2_YBWl(LcEL8m-B+|dN4s;h^GQg{yAJf4 G`}_vkoo}K5 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/storage/__pycache__/base.cpython-312.pyc b/ingest_pipeline/storage/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b84a9c2dd919d198f8f1736e4c4ec3fa3923934 GIT binary patch literal 3415 zcmb7GO^6&t6t4c8-<_TP-z1vY$p+2jFoQ$_u3>ePg%~w+Fv-CIX?nVAb{l(oHdQ^* z86t>@Ac!D%$R0f8m?a3&Q@nZe;7Pog1~DKQbDQiaXimOY{WHDGCaeuzzpq}s_r0%P z)sLl8fxx%)?GJ0$@`U`3o%qwD#_$bj+#oh-5L>ckS8B@*xvex53HyqxwzY-^ZPnJ? zOgr1iwsVb~L}YT7*qQ6Z&Qkfd66$K?d$~%!KY!ZfROfz&nX6QHJfE_bY0>IKl&Z*q zvV8hXFm5)v&rHj2Q-7^vSEOKknfE;FEcGZeVeLaXl+LYmtZtineo(p)syx&2TF$B! z?irS2QD^uV8aD_f4GA2QFM$gUg{oAu)~ckJD_P*fVwsWX5UP zF4GB+okZ!DEcE5SEZ}Oi04R8s>uCTL65$3p5r^F z>-0yFkCayniO@1z<%>xVSBAST>XF4!zY7d-I$k1KuSl#2I|F%CPTiL1a5PWXAD^sU z;^0CJ*mR|4(9QV{28W$AM{1Eg0T-t@v z_&hE_C;lY=fFBu>Fa0}Iq5rY0NQD0Ivm_)KFshq62bf)|$C{oR>{w_~ zkI%I3cLW#dh$C+caCguokw3SRSWsuv;7COlxG>>q$cPXf4dYGwlWAbY(6g{=IKpHO zF=2Bs802FE`JMr}m6#I(((byxvz`J3!sx;2^R2Z#LBkjIw;b;4omK)V-z!Kv7wYF$ zcw*jmw4MQXqw^D@RTR(|!q6~*qd$fAjp1=4*#wt+kmNtG%?7BcrX03_L9Ye*R zijvqGvTC?a^b%#5c7;ug`g!vMr`>JqUbo$(3`qwz0)t1iLE*%5Pzb)bCUgAWXmo`9 zI}Ua29l)PQ>O+o5Vm#6vQBn;L@DV_QEnsmJ3XUM*<{>>ZTzJx-+IE`7*cp@_9p^(N zUk;D+o+xrklhHuA{E!r$E>>IoA{R#E9&SF`Gd@QdgHI z6mL(?VyKCNqi9Yf$r3knMF9_S!Vdr#pOAa%iTh*we^&Po z)U0-JaZ zb=Us>#COh{{)Q69T^~qO6eLp|Hk-K?xWBjN5 z3tJipM)p8!a3rxu33pL<+hHJQwbI6gEpR+^<`E8rupwggN`cj&4ivL#F$FQiFa_n* zL#c{W7>A%}!IfVH3Q(7eAc}B@&~Fb%kUJGJWr@8EE0LP~IVc8-BuV$k!3SjFS91IT TdGW7|DM_U-7XBt!3&Z{cZ%jm` literal 0 HcmV?d00001 diff --git a/ingest_pipeline/storage/__pycache__/openwebui.cpython-312.pyc b/ingest_pipeline/storage/__pycache__/openwebui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d972a49afd4385f198488819786dcf64e72e502b GIT binary patch literal 12998 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/storage/__pycache__/weaviate.cpython-312.pyc b/ingest_pipeline/storage/__pycache__/weaviate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2dc006148dbc2360c716c96b75edf48753385130 GIT binary patch literal 29165 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/storage/base.py b/ingest_pipeline/storage/base.py new file mode 100644 index 0000000..eb3487b --- /dev/null +++ b/ingest_pipeline/storage/base.py @@ -0,0 +1,106 @@ +"""Base storage interface.""" + +from abc import ABC, abstractmethod +from collections.abc import AsyncGenerator + +from ..core.models import Document, StorageConfig + + +class BaseStorage(ABC): + """Abstract base class for storage adapters.""" + + config: StorageConfig + + def __init__(self, config: StorageConfig): + """ + Initialize storage adapter. + + Args: + config: Storage configuration + """ + self.config = config + + @abstractmethod + async def initialize(self) -> None: + """Initialize the storage backend and create collections if needed.""" + pass # pragma: no cover + + @abstractmethod + async def store(self, document: Document) -> str: + """ + Store a single document. + + Args: + document: Document to store + + Returns: + Document ID + """ + pass # pragma: no cover + + @abstractmethod + async def store_batch(self, documents: list[Document]) -> list[str]: + """ + Store multiple documents in batch. + + Args: + documents: List of documents to store + + Returns: + List of document IDs + """ + pass # pragma: no cover + + @abstractmethod + async def retrieve(self, document_id: str) -> Document | None: + """ + Retrieve a document by ID. + + Args: + document_id: Document ID + + Returns: + Document or None if not found + """ + pass # pragma: no cover + + @abstractmethod + async def search( + self, query: str, limit: int = 10, threshold: float = 0.7 + ) -> AsyncGenerator[Document, None]: + """ + Search for documents. + + Args: + query: Search query + limit: Maximum number of results + threshold: Similarity threshold + + Yields: + Matching documents + """ + return # type: ignore # pragma: no cover + yield # pragma: no cover + + @abstractmethod + async def delete(self, document_id: str) -> bool: + """ + Delete a document. + + Args: + document_id: Document ID + + Returns: + True if deleted successfully + """ + pass # pragma: no cover + + @abstractmethod + async def count(self) -> int: + """ + Get total document count. + + Returns: + Number of documents + """ + pass # pragma: no cover diff --git a/ingest_pipeline/storage/openwebui.py b/ingest_pipeline/storage/openwebui.py new file mode 100644 index 0000000..f7a8c96 --- /dev/null +++ b/ingest_pipeline/storage/openwebui.py @@ -0,0 +1,296 @@ +"""Open WebUI storage adapter.""" + +from collections.abc import AsyncGenerator +from uuid import UUID + +import httpx +from typing_extensions import override + +from ..core.exceptions import StorageError +from ..core.models import Document, StorageConfig +from ..utils.vectorizer import Vectorizer +from .base import BaseStorage + + +class OpenWebUIStorage(BaseStorage): + """Storage adapter for Open WebUI knowledge endpoints.""" + + client: httpx.AsyncClient + vectorizer: Vectorizer + + def __init__(self, config: StorageConfig): + """ + Initialize Open WebUI storage. + + Args: + config: Storage configuration + """ + super().__init__(config) + + self.client = httpx.AsyncClient( + base_url=str(config.endpoint), + headers={ + "Authorization": f"Bearer {config.api_key}" if config.api_key else "", + "Content-Type": "application/json", + }, + timeout=30.0, + ) + self.vectorizer = Vectorizer(config) + + @override + async def initialize(self) -> None: + """Initialize Open WebUI connection.""" + try: + # Test connection with OpenWebUI knowledge API + response = await self.client.get("/api/v1/knowledge/") + response.raise_for_status() + + # Check if collection (knowledge base) exists, create if not + knowledge_bases = response.json() + collection_exists = any( + kb.get("name") == self.config.collection_name for kb in knowledge_bases + ) + + if not collection_exists: + await self._create_collection() + + except Exception as e: + raise StorageError(f"Failed to initialize Open WebUI: {e}") from e + + async def _create_collection(self) -> None: + """Create knowledge base in Open WebUI.""" + try: + response = await self.client.post( + "/api/v1/knowledge/create", + json={ + "name": self.config.collection_name, + "description": "Documents ingested from various sources" + }, + ) + response.raise_for_status() + except Exception as e: + raise StorageError(f"Failed to create knowledge base: {e}") from e + + @override + async def store(self, document: Document) -> str: + """ + Store a document in Open WebUI. + + Args: + document: Document to store + + Returns: + Document ID + """ + try: + # Vectorize if needed + if document.vector is None: + document.vector = await self.vectorizer.vectorize(document.content) + + # Prepare document data + doc_data = { + "id": str(document.id), + "collection": self.config.collection_name, + "content": document.content, + "metadata": { + **document.metadata, + "timestamp": document.metadata["timestamp"].isoformat(), + "source": document.source.value, + }, + "embedding": document.vector, + } + + # Store document + response = await self.client.post( + f"/api/knowledge/collections/{self.config.collection_name}/documents", json=doc_data + ) + response.raise_for_status() + + result = response.json() + document_id = result.get("id") if isinstance(result, dict) else None + return str(document_id) if document_id else str(document.id) + + except Exception as e: + raise StorageError(f"Failed to store document: {e}") from e + + @override + async def store_batch(self, documents: list[Document]) -> list[str]: + """ + Store multiple documents in batch. + + Args: + documents: List of documents + + Returns: + List of document IDs + """ + try: + # Vectorize documents without vectors + for doc in documents: + if doc.vector is None: + doc.vector = await self.vectorizer.vectorize(doc.content) + + # Prepare batch data + batch_data = [] + for doc in documents: + batch_data.append( + { + "id": str(doc.id), + "content": doc.content, + "metadata": { + **doc.metadata, + "timestamp": doc.metadata["timestamp"].isoformat(), + "source": doc.source.value, + }, + "embedding": doc.vector, + } + ) + + # Store batch + response = await self.client.post( + f"/api/knowledge/collections/{self.config.collection_name}/documents/batch", + json={"documents": batch_data}, + ) + response.raise_for_status() + + result = response.json() + ids = result.get("ids") if isinstance(result, dict) else None + return ids if isinstance(ids, list) else [str(doc.id) for doc in documents] + + except Exception as e: + raise StorageError(f"Failed to store batch: {e}") from e + + @override + async def retrieve(self, document_id: str) -> Document | None: + """ + Retrieve a document from Open WebUI. + + Args: + document_id: Document ID + + Returns: + Document or None + """ + try: + response = await self.client.get( + f"/api/knowledge/collections/{self.config.collection_name}/documents/{document_id}" + ) + + if response.status_code == 404: + return None + + response.raise_for_status() + data = response.json() + + # Reconstruct document + metadata = data.get("metadata", {}) + return Document( + id=UUID(document_id), + content=data["content"], + metadata=metadata, + vector=data.get("embedding"), + source=metadata.get("source", "unknown"), + collection=self.config.collection_name, + ) + + except Exception: + return None + + @override + async def search( + self, query: str, limit: int = 10, threshold: float = 0.7 + ) -> AsyncGenerator[Document, None]: + """ + Search for documents in Open WebUI. + + Args: + query: Search query + limit: Maximum results + threshold: Similarity threshold + + Yields: + Matching documents + """ + try: + # Vectorize query + query_vector = await self.vectorizer.vectorize(query) + + # Perform search + response = await self.client.post( + f"/api/knowledge/collections/{self.config.collection_name}/search", + json={ + "query": query, + "embedding": query_vector, + "limit": limit, + "threshold": threshold, + }, + ) + response.raise_for_status() + + results = response.json() + + for result in results.get("documents", []): + metadata = result.get("metadata", {}) + doc = Document( + id=result["id"], + content=result["content"], + metadata=metadata, + vector=result.get("embedding"), + source=metadata.get("source", "unknown"), + collection=self.config.collection_name, + ) + yield doc + + except Exception as e: + raise StorageError(f"Search failed: {e}") from e + + async def delete(self, document_id: str) -> bool: + """ + Delete a document from Open WebUI. + + Args: + document_id: Document ID + + Returns: + True if deleted + """ + try: + response = await self.client.delete( + f"/api/knowledge/collections/{self.config.collection_name}/documents/{document_id}" + ) + return response.status_code in [200, 204] + except Exception: + return False + + async def count(self) -> int: + """ + Get document count in collection. + + Returns: + Number of documents + """ + try: + response = await self.client.get( + f"/api/knowledge/collections/{self.config.collection_name}/stats" + ) + response.raise_for_status() + + stats = response.json() + count = stats.get("document_count") if isinstance(stats, dict) else None + return int(count) if isinstance(count, (int, str)) else 0 + except Exception: + return 0 + + async def __aenter__(self) -> "OpenWebUIStorage": + """Async context manager entry.""" + await self.initialize() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: object | None, + ) -> None: + """Async context manager exit.""" + await self.client.aclose() diff --git a/ingest_pipeline/storage/weaviate.py b/ingest_pipeline/storage/weaviate.py new file mode 100644 index 0000000..1173f31 --- /dev/null +++ b/ingest_pipeline/storage/weaviate.py @@ -0,0 +1,703 @@ +"""Weaviate storage adapter.""" + +from collections.abc import AsyncGenerator +from datetime import UTC, datetime +from typing import cast +from uuid import UUID + +import weaviate +from typing_extensions import override +from weaviate.classes.config import Configure, DataType, Property + +from ..core.exceptions import StorageError +from ..core.models import Document, DocumentMetadata, IngestionSource, StorageConfig +from ..utils.vectorizer import Vectorizer +from .base import BaseStorage + + +class WeaviateStorage(BaseStorage): + """Storage adapter for Weaviate.""" + + client: weaviate.WeaviateClient | None + vectorizer: Vectorizer + collection_name: str + + def __init__(self, config: StorageConfig): + """ + Initialize Weaviate storage. + + Args: + config: Storage configuration + """ + super().__init__(config) + self.client = None + self.vectorizer = Vectorizer(config) + self.collection_name = config.collection_name.capitalize() + + @override + async def initialize(self) -> None: + """Initialize Weaviate client and create collection if needed.""" + try: + # Connect to Weaviate + # Parse endpoint - Weaviate expects just the hostname without protocol + endpoint_str = str(self.config.endpoint).replace("http://", "").replace("https://", "") + + # Split host and port if port is specified in the URL + if ":" in endpoint_str and "/" not in endpoint_str: + # Only split if it's a port number (no path) + host, port_str = endpoint_str.rsplit(":", 1) + http_port = int(port_str) if port_str.isdigit() else 80 + else: + # Remove any path if present + host = endpoint_str.split("/")[0] + # For reverse proxy setups, use port 80 + http_port = 80 + + # For reverse proxy setups, use HTTP-only connection + self.client = weaviate.WeaviateClient( + connection_params=weaviate.connect.ConnectionParams.from_url( + url=f"http://{host}:{http_port}", + grpc_port=50051, # Default gRPC port but will be ignored + ), + skip_init_checks=True, # Skip gRPC health checks + additional_config=weaviate.classes.init.AdditionalConfig( + timeout=weaviate.classes.init.Timeout(init=30, query=60, insert=120), + ) + ) + + # Connect to the client + self.client.connect() + + # Check if collection exists + collections = self.client.collections.list_all() + + if self.collection_name not in collections: + await self._create_collection() + + except Exception as e: + raise StorageError(f"Failed to initialize Weaviate: {e}") from e + + async def _create_collection(self) -> None: + """Create Weaviate collection with schema.""" + if not self.client: + raise StorageError("Weaviate client not initialized") + try: + self.client.collections.create( + name=self.collection_name, + properties=[ + Property( + name="content", data_type=DataType.TEXT, description="Document content" + ), + Property(name="source_url", data_type=DataType.TEXT, description="Source URL"), + Property(name="title", data_type=DataType.TEXT, description="Document title"), + Property( + name="description", + data_type=DataType.TEXT, + description="Document description", + ), + Property( + name="timestamp", data_type=DataType.DATE, description="Ingestion timestamp" + ), + Property( + name="content_type", data_type=DataType.TEXT, description="Content type" + ), + Property(name="word_count", data_type=DataType.INT, description="Word count"), + Property( + name="char_count", data_type=DataType.INT, description="Character count" + ), + Property( + name="source", data_type=DataType.TEXT, description="Ingestion source" + ), + ], + vectorizer_config=Configure.Vectorizer.none(), + ) + except Exception as e: + raise StorageError(f"Failed to create collection: {e}") from e + + @override + async def store(self, document: Document) -> str: + """ + Store a document in Weaviate. + + Args: + document: Document to store + + Returns: + Document ID + """ + try: + # Vectorize content if no vector provided + if document.vector is None: + document.vector = await self.vectorizer.vectorize(document.content) + + if not self.client: + raise StorageError("Weaviate client not initialized") + collection = self.client.collections.get(self.collection_name) + + # Prepare properties + properties = { + "content": document.content, + "source_url": document.metadata["source_url"], + "title": document.metadata.get("title", ""), + "description": document.metadata.get("description", ""), + "timestamp": document.metadata["timestamp"].isoformat(), + "content_type": document.metadata["content_type"], + "word_count": document.metadata["word_count"], + "char_count": document.metadata["char_count"], + "source": document.source.value, + } + + # Insert with vector + result = collection.data.insert( + properties=properties, vector=document.vector, uuid=str(document.id) + ) + + return str(result) + + except Exception as e: + raise StorageError(f"Failed to store document: {e}") from e + + @override + async def store_batch(self, documents: list[Document]) -> list[str]: + """ + Store multiple documents in batch. + + Args: + documents: List of documents + + Returns: + List of successfully stored document IDs + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + collection = self.client.collections.get(self.collection_name) + + # Vectorize documents without vectors + for doc in documents: + if doc.vector is None: + doc.vector = await self.vectorizer.vectorize(doc.content) + + # Try individual inserts to avoid gRPC batch issues + successful_ids: list[str] = [] + + for doc in documents: + try: + properties = { + "content": doc.content, + "source_url": doc.metadata["source_url"], + "title": doc.metadata.get("title", ""), + "description": doc.metadata.get("description", ""), + "timestamp": doc.metadata["timestamp"].isoformat(), + "content_type": doc.metadata["content_type"], + "word_count": doc.metadata["word_count"], + "char_count": doc.metadata["char_count"], + "source": doc.source.value, + } + + # Insert individual document + collection.data.insert( + properties=properties, + vector=doc.vector, + uuid=str(doc.id) + ) + successful_ids.append(str(doc.id)) + + except Exception as e: + print(f"Failed to store document {doc.id}: {e}") + continue + + if not successful_ids: + raise StorageError("All documents in batch failed to store") + + return successful_ids + + except Exception as e: + raise StorageError(f"Failed to store batch: {e}") from e + + @override + async def retrieve(self, document_id: str) -> Document | None: + """ + Retrieve a document from Weaviate. + + Args: + document_id: Document ID + + Returns: + Document or None + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + collection = self.client.collections.get(self.collection_name) + result = collection.query.fetch_object_by_id(document_id) + + if not result: + return None + + # Reconstruct document + props = result.properties + metadata_dict = { + "source_url": str(props["source_url"]), + "title": str(props.get("title")) if props.get("title") else None, + "description": str(props.get("description")) if props.get("description") else None, + "timestamp": str(props["timestamp"]), + "content_type": str(props["content_type"]), + "word_count": int(str(props["word_count"])), + "char_count": int(str(props["char_count"])), + } + metadata = cast(DocumentMetadata, cast(object, metadata_dict)) + + vector_raw = result.vector.get("default") if result.vector else None + vector: list[float] | None = None + if isinstance(vector_raw, list) and vector_raw: + first_elem = vector_raw[0] + if isinstance(first_elem, list): + # Nested list - take first one and ensure all elements are numbers + nested_vector = first_elem + try: + vector = [float(x) for x in nested_vector if isinstance(x, (int, float))] + except (ValueError, TypeError): + vector = None + else: + # Flat list - ensure all elements are numbers + try: + vector = [float(x) for x in vector_raw if isinstance(x, (int, float))] + except (ValueError, TypeError): + vector = None + + return Document( + id=UUID(document_id), + content=str(props["content"]), + metadata=metadata, + vector=vector, + source=IngestionSource.WEB, # Default to WEB + collection=self.collection_name, + ) + + except Exception: + return None + + @override + async def search( + self, query: str, limit: int = 10, threshold: float = 0.7 + ) -> AsyncGenerator[Document, None]: + """ + Search for documents in Weaviate. + + Args: + query: Search query + limit: Maximum results + threshold: Similarity threshold + + Yields: + Matching documents + """ + try: + # Vectorize query + query_vector = await self.vectorizer.vectorize(query) + + if not self.client: + raise StorageError("Weaviate client not initialized") + collection = self.client.collections.get(self.collection_name) + + # Perform vector search + results = collection.query.near_vector( + near_vector=query_vector, + limit=limit, + distance=1 - threshold, # Convert similarity to distance + return_metadata=["distance"], + ) + + for result in results.objects: + props = result.properties + metadata_dict = { + "source_url": str(props["source_url"]), + "title": str(props.get("title")) if props.get("title") else None, + "description": str(props.get("description")) + if props.get("description") + else None, + "timestamp": str(props["timestamp"]), + "content_type": str(props["content_type"]), + "word_count": int(str(props["word_count"])), + "char_count": int(str(props["char_count"])), + } + metadata = cast(DocumentMetadata, cast(object, metadata_dict)) + + vector_raw = result.vector.get("default") if result.vector else None + vector: list[float] | None = None + if isinstance(vector_raw, list) and vector_raw: + first_elem = vector_raw[0] + if isinstance(first_elem, list): + # Nested list - take first one and ensure all elements are numbers + nested_vector = first_elem + try: + vector = [ + float(x) for x in nested_vector if isinstance(x, (int, float)) + ] + except (ValueError, TypeError): + vector = None + else: + # Flat list - ensure all elements are numbers + try: + vector = [float(x) for x in vector_raw if isinstance(x, (int, float))] + except (ValueError, TypeError): + vector = None + + doc = Document( + id=result.uuid, + content=str(props["content"]), + metadata=metadata, + vector=vector, + source=IngestionSource.WEB, # Default to WEB + collection=self.collection_name, + ) + yield doc + + except Exception as e: + raise StorageError(f"Search failed: {e}") from e + + @override + async def delete(self, document_id: str) -> bool: + """ + Delete a document from Weaviate. + + Args: + document_id: Document ID + + Returns: + True if deleted + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + collection = self.client.collections.get(self.collection_name) + collection.data.delete_by_id(document_id) + return True + except Exception: + return False + + @override + async def count(self) -> int: + """ + Get document count in collection. + + Returns: + Number of documents + """ + try: + if not self.client: + return 0 + collection = self.client.collections.get(self.collection_name) + result = collection.aggregate.over_all(total_count=True) + return result.total_count or 0 + except Exception: + return 0 + + async def list_collections(self) -> list[str]: + """ + List all available collections. + + Returns: + List of collection names + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + return list(self.client.collections.list_all()) + + except Exception as e: + raise StorageError(f"Failed to list collections: {e}") from e + + async def sample_documents(self, limit: int = 5) -> list[Document]: + """ + Get sample documents from the collection. + + Args: + limit: Maximum number of documents to return + + Returns: + List of sample documents + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + collection = self.client.collections.get(self.collection_name) + + # Query for sample documents + response = collection.query.fetch_objects(limit=limit) + + documents = [] + for obj in response.objects: + # Convert back to Document format + props = obj.properties + # Safely convert WeaviateField values + word_count_val = props.get("word_count") + if isinstance(word_count_val, (int, float)): + word_count = int(word_count_val) + elif word_count_val: + word_count = int(str(word_count_val)) + else: + word_count = 0 + + char_count_val = props.get("char_count") + if isinstance(char_count_val, (int, float)): + char_count = int(char_count_val) + elif char_count_val: + char_count = int(str(char_count_val)) + else: + char_count = 0 + + doc = Document( + id=obj.uuid, + content=str(props.get("content", "")), + source=IngestionSource(str(props.get("source", "web"))), + metadata={ + "source_url": str(props.get("source_url", "")), + "title": str(props.get("title", "")) if props.get("title") else None, + "description": str(props.get("description", "")) if props.get("description") else None, + "timestamp": datetime.fromisoformat(str(props.get("timestamp", datetime.now(UTC).isoformat()))), + "content_type": str(props.get("content_type", "text/plain")), + "word_count": word_count, + "char_count": char_count, + } + ) + documents.append(doc) + + return documents + + except Exception as e: + raise StorageError(f"Failed to sample documents: {e}") from e + + async def search_documents(self, query: str, limit: int = 10) -> list[Document]: + """ + Search documents in the collection. + + Args: + query: Search query + limit: Maximum number of results + + Returns: + List of matching documents + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + collection = self.client.collections.get(self.collection_name) + + # Try hybrid search first, fall back to BM25 keyword search + try: + response = collection.query.hybrid( + query=query, + limit=limit, + return_metadata=["score"] + ) + except Exception: + # Fall back to BM25 keyword search if hybrid search fails + response = collection.query.bm25( + query=query, + limit=limit, + return_metadata=["score"] + ) + + documents = [] + for obj in response.objects: + # Convert back to Document format + props = obj.properties + + # Safely convert WeaviateField values + word_count_val = props.get("word_count") + if isinstance(word_count_val, (int, float)): + word_count = int(word_count_val) + elif word_count_val: + word_count = int(str(word_count_val)) + else: + word_count = 0 + + char_count_val = props.get("char_count") + if isinstance(char_count_val, (int, float)): + char_count = int(char_count_val) + elif char_count_val: + char_count = int(str(char_count_val)) + else: + char_count = 0 + + # Build metadata - note that search_score is not part of DocumentMetadata + metadata: DocumentMetadata = { + "source_url": str(props.get("source_url", "")), + "title": str(props.get("title", "")) if props.get("title") else None, + "description": str(props.get("description", "")) if props.get("description") else None, + "timestamp": datetime.fromisoformat(str(props.get("timestamp", datetime.now(UTC).isoformat()))), + "content_type": str(props.get("content_type", "text/plain")), + "word_count": word_count, + "char_count": char_count, + } + + doc = Document( + id=obj.uuid, + content=str(props.get("content", "")), + source=IngestionSource(str(props.get("source", "web"))), + metadata=metadata + ) + documents.append(doc) + + return documents + + except Exception as e: + raise StorageError(f"Failed to search documents: {e}") from e + + async def list_documents(self, limit: int = 100, offset: int = 0) -> list[dict[str, str | int]]: + """ + List documents in the collection with pagination. + + Args: + limit: Maximum number of documents to return + offset: Number of documents to skip + + Returns: + List of document dictionaries with id, title, source_url, and content preview + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + collection = self.client.collections.get(self.collection_name) + + # Query documents with pagination + response = collection.query.fetch_objects( + limit=limit, + offset=offset, + return_metadata=["creation_time"] + ) + + documents = [] + for obj in response.objects: + props = obj.properties + content = str(props.get("content", "")) + word_count_value = props.get("word_count", 0) + # Convert WeaviateField to int + if isinstance(word_count_value, (int, float)): + word_count = int(word_count_value) + elif word_count_value: + word_count = int(str(word_count_value)) + else: + word_count = 0 + + doc_info: dict[str, str | int] = { + "id": str(obj.uuid), + "title": str(props.get("title", "Untitled")), + "source_url": str(props.get("source_url", "")), + "content_preview": content[:200] + "..." if len(content) > 200 else content, + "word_count": word_count, + "timestamp": str(props.get("timestamp", "")), + } + documents.append(doc_info) + + return documents + + except Exception as e: + raise StorageError(f"Failed to list documents: {e}") from e + + async def delete_documents(self, document_ids: list[str]) -> dict[str, bool]: + """ + Delete multiple documents from Weaviate. + + Args: + document_ids: List of document IDs to delete + + Returns: + Dictionary mapping document IDs to deletion success status + """ + results = {} + + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + collection = self.client.collections.get(self.collection_name) + + for doc_id in document_ids: + try: + collection.data.delete_by_id(doc_id) + results[doc_id] = True + except Exception: + results[doc_id] = False + + return results + + except Exception as e: + raise StorageError(f"Failed to delete documents: {e}") from e + + async def delete_by_filter(self, filter_dict: dict[str, str]) -> int: + """ + Delete documents matching a filter. + + Args: + filter_dict: Filter criteria (e.g., {"source_url": "example.com"}) + + Returns: + Number of documents deleted + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + collection = self.client.collections.get(self.collection_name) + + # Build where filter + where_filter = None + if "source_url" in filter_dict: + from weaviate.classes.query import Filter + where_filter = Filter.by_property("source_url").equal(filter_dict["source_url"]) + + # Get documents matching filter + if where_filter: + response = collection.query.fetch_objects( + filters=where_filter, + limit=1000 # Max batch size + ) + else: + response = collection.query.fetch_objects( + limit=1000 # Max batch size + ) + + # Delete matching documents + deleted_count = 0 + for obj in response.objects: + try: + collection.data.delete_by_id(obj.uuid) + deleted_count += 1 + except Exception: + continue + + return deleted_count + + except Exception as e: + raise StorageError(f"Failed to delete by filter: {e}") from e + + async def delete_collection(self) -> bool: + """ + Delete the entire collection. + + Returns: + True if successful + """ + try: + if not self.client: + raise StorageError("Weaviate client not initialized") + + # Delete the collection using the client's collections API + self.client.collections.delete(self.collection_name) + + return True + + except Exception as e: + raise StorageError(f"Failed to delete collection: {e}") from e + + def __del__(self) -> None: + """Clean up client connection.""" + if self.client: + self.client.close() diff --git a/ingest_pipeline/utils/__init__.py b/ingest_pipeline/utils/__init__.py new file mode 100644 index 0000000..0e31335 --- /dev/null +++ b/ingest_pipeline/utils/__init__.py @@ -0,0 +1,6 @@ +"""Utility modules.""" + +from .metadata_tagger import MetadataTagger +from .vectorizer import Vectorizer + +__all__ = ["MetadataTagger", "Vectorizer"] diff --git a/ingest_pipeline/utils/__pycache__/__init__.cpython-312.pyc b/ingest_pipeline/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c9b4764eaf1e128ae4aa0aebb52bb4e7ac71747 GIT binary patch literal 327 zcmX@j%ge<81l#@}&zKCPAA>kBzyxJ{)&Mf5Go&y?F{Ci2Fy%1jGDR^lg4oPC%(*O4 zEI>9(3Trx36l*1$CVQ1YXh~*HW=W+&ZhlH>PHM5Y@_!6)tu(DfRWe9yB zEg)b@izR@>2WCb_#+wXcPZ_lDGAKRZ QmYxxFflI5Ay$BS30ChK9e*gdg literal 0 HcmV?d00001 diff --git a/ingest_pipeline/utils/__pycache__/metadata_tagger.cpython-312.pyc b/ingest_pipeline/utils/__pycache__/metadata_tagger.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f9fe7019a7faf6cb08c501b7ce3c15f3bef2c8c GIT binary patch literal 10607 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 literal 0 HcmV?d00001 diff --git a/ingest_pipeline/utils/metadata_tagger.py b/ingest_pipeline/utils/metadata_tagger.py new file mode 100644 index 0000000..594f6a2 --- /dev/null +++ b/ingest_pipeline/utils/metadata_tagger.py @@ -0,0 +1,269 @@ +"""Metadata tagger for enriching documents with AI-generated tags and metadata.""" + +import json +from datetime import UTC, datetime +from typing import TypedDict + +import httpx + +from ..core.exceptions import IngestionError +from ..core.models import Document + + +class DocumentMetadata(TypedDict, total=False): + """Structured metadata for documents.""" + + tags: list[str] + category: str + summary: str + key_topics: list[str] + document_type: str + language: str + technical_level: str + + +class MetadataTagger: + """Generates metadata tags for documents using language models.""" + + endpoint: str + model: str + client: httpx.AsyncClient + + def __init__( + self, + llm_endpoint: str = "http://llm.lab", + model: str = "openai/gpt-4o-mini", + ): + """ + Initialize metadata tagger. + + Args: + llm_endpoint: LLM API endpoint + model: Model to use for tagging + """ + self.endpoint = llm_endpoint + self.model = model + + # Get API key from environment + import os + from pathlib import Path + + from dotenv import load_dotenv + + # Load .env from the project root + env_path = Path(__file__).parent.parent.parent / ".env" + load_dotenv(env_path) + + api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY") or "" + + headers = {"Content-Type": "application/json"} + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + self.client = httpx.AsyncClient(timeout=60.0, headers=headers) + + async def tag_document( + self, document: Document, custom_instructions: str | None = None + ) -> Document: + """ + Analyze document and generate metadata tags. + + Args: + document: Document to tag + custom_instructions: Optional custom instructions for tagging + + Returns: + Document with enriched metadata + """ + if not document.content: + return document + + try: + # Generate metadata using LLM + metadata = await self._generate_metadata( + document.content, + document.metadata.get("title") if document.metadata else None, + custom_instructions + ) + + # Merge with existing metadata - preserve required fields + from ..core.models import DocumentMetadata as CoreDocumentMetadata + + updated_metadata: CoreDocumentMetadata = { + "source_url": document.metadata.get("source_url", ""), + "title": metadata.get("title") or document.metadata.get("title"), + "description": metadata.get("summary") or document.metadata.get("description"), + "timestamp": document.metadata.get("timestamp", datetime.now(UTC)), + "content_type": document.metadata.get("content_type", "text/plain"), + "word_count": document.metadata.get("word_count", len(document.content.split())), + "char_count": document.metadata.get("char_count", len(document.content)), + } + + # Store additional metadata as extra fields in the document's metadata + # Note: Since DocumentMetadata is a TypedDict, we can only include the defined fields + # Additional metadata like tags, category, etc. would need to be stored separately + # or the DocumentMetadata model would need to be extended + + document.metadata = updated_metadata + + return document + + except Exception as e: + raise IngestionError(f"Failed to tag document: {e}") from e + + async def tag_batch( + self, + documents: list[Document], + custom_instructions: str | None = None, + ) -> list[Document]: + """ + Tag multiple documents with metadata. + + Args: + documents: Documents to tag + custom_instructions: Optional custom instructions + + Returns: + Documents with enriched metadata + """ + tagged_docs: list[Document] = [] + + for doc in documents: + tagged_doc = await self.tag_document(doc, custom_instructions) + tagged_docs.append(tagged_doc) + + return tagged_docs + + async def _generate_metadata( + self, + content: str, + title: str | None = None, + custom_instructions: str | None = None, + ) -> DocumentMetadata: + """ + Generate metadata using LLM. + + Args: + content: Document content + title: Document title + custom_instructions: Optional custom instructions + + Returns: + Generated metadata dictionary + """ + # Prepare the prompt + system_prompt = """You are a document metadata tagger. Analyze the given content and generate relevant metadata. + +Return a JSON object with the following structure: +{ + "tags": ["tag1", "tag2", ...], # 3-7 relevant topic tags + "category": "string", # Main category + "summary": "string", # 1-2 sentence summary + "key_topics": ["topic1", "topic2", ...], # Main topics discussed + "document_type": "string", # Type of document (e.g., "technical", "tutorial", "reference") + "language": "string", # Primary language (e.g., "en", "es") + "technical_level": "string" # One of: "beginner", "intermediate", "advanced" +}""" + + if custom_instructions: + system_prompt += f"\n\nAdditional instructions: {custom_instructions}" + + # Prepare user prompt + user_prompt = "Document to analyze:\n" + if title: + user_prompt += f"Title: {title}\n" + user_prompt += f"Content:\n{content[:3000]}" # Limit content length + + # Call LLM + response = await self.client.post( + f"{self.endpoint}/v1/chat/completions", + json={ + "model": self.model, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + "temperature": 0.3, + "max_tokens": 500, + "response_format": {"type": "json_object"}, + }, + ) + response.raise_for_status() + + result = response.json() + if not isinstance(result, dict): + raise IngestionError("Invalid response format from LLM") + + # Extract content from response + choices = result.get("choices", []) + if not choices: + raise IngestionError("No response from LLM") + + message = choices[0].get("message", {}) + content_str = message.get("content", "{}") + + try: + metadata = json.loads(content_str) + except json.JSONDecodeError as e: + raise IngestionError(f"Failed to parse LLM response: {e}") from e + + # Validate and sanitize metadata + return self._sanitize_metadata(metadata) + + def _sanitize_metadata(self, metadata: dict[str, object]) -> DocumentMetadata: + """ + Sanitize and validate metadata. + + Args: + metadata: Raw metadata from LLM + + Returns: + Sanitized metadata + """ + sanitized: DocumentMetadata = {} + + # Tags + if "tags" in metadata and isinstance(metadata["tags"], list): + tags = [str(tag).lower().strip() for tag in metadata["tags"][:10]] + sanitized["tags"] = [tag for tag in tags if tag] + + # Category + if "category" in metadata: + sanitized["category"] = str(metadata["category"]).strip() + + # Summary + if "summary" in metadata: + summary = str(metadata["summary"]).strip() + if summary: + sanitized["summary"] = summary[:500] # Limit length + + # Key topics + if "key_topics" in metadata and isinstance(metadata["key_topics"], list): + topics = [str(topic).strip() for topic in metadata["key_topics"][:10]] + sanitized["key_topics"] = [topic for topic in topics if topic] + + # Document type + if "document_type" in metadata: + sanitized["document_type"] = str(metadata["document_type"]).strip() + + # Language + if "language" in metadata: + lang = str(metadata["language"]).strip().lower() + if len(lang) == 2: # Basic validation for ISO 639-1 + sanitized["language"] = lang + + # Technical level + if "technical_level" in metadata: + level = str(metadata["technical_level"]).strip().lower() + if level in ["beginner", "intermediate", "advanced"]: + sanitized["technical_level"] = level + + return sanitized + + async def __aenter__(self) -> "MetadataTagger": + """Async context manager entry.""" + return self + + async def __aexit__(self, *args: object) -> None: + """Async context manager exit.""" + await self.client.aclose() diff --git a/ingest_pipeline/utils/vectorizer.py b/ingest_pipeline/utils/vectorizer.py new file mode 100644 index 0000000..0803550 --- /dev/null +++ b/ingest_pipeline/utils/vectorizer.py @@ -0,0 +1,220 @@ +"""Vectorizer utility for generating embeddings.""" + +from types import TracebackType +from typing import Self + +import httpx + +from ..core.exceptions import VectorizationError +from ..core.models import StorageConfig, VectorConfig + + +class Vectorizer: + """Handles text vectorization using LLM endpoints.""" + + endpoint: str + model: str + dimension: int + client: httpx.AsyncClient + + def __init__(self, config: StorageConfig | VectorConfig): + """ + Initialize vectorizer. + + Args: + config: Configuration with embedding details + """ + if isinstance(config, StorageConfig): + # Extract vector config from storage config + self.endpoint = "http://llm.lab" + self.model = "ollama/bge-m3:latest" + self.dimension = 1024 + else: + self.endpoint = str(config.embedding_endpoint) + self.model = config.model + self.dimension = config.dimension + + # Get API key from environment + import os + from dotenv import load_dotenv + from pathlib import Path + + # Load .env from the project root + env_path = Path(__file__).parent.parent.parent / ".env" + load_dotenv(env_path) + + api_key = os.getenv("LLM_API_KEY") or os.getenv("OPENAI_API_KEY") or "" + + headers = {"Content-Type": "application/json"} + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + self.client = httpx.AsyncClient(timeout=60.0, headers=headers) + + async def vectorize(self, text: str) -> list[float]: + """ + Generate embedding vector for text. + + Args: + text: Text to vectorize + + Returns: + Embedding vector + """ + if not text: + raise VectorizationError("Cannot vectorize empty text") + + try: + # Prepare request based on model type + if "ollama" in self.model: + response = await self._ollama_embed(text) + else: + response = await self._openai_embed(text) + + return response + + except Exception as e: + raise VectorizationError(f"Vectorization failed: {e}") from e + + async def vectorize_batch(self, texts: list[str]) -> list[list[float]]: + """ + Generate embeddings for multiple texts. + + Args: + texts: List of texts to vectorize + + Returns: + List of embedding vectors + """ + vectors: list[list[float]] = [] + + for text in texts: + vector = await self.vectorize(text) + vectors.append(vector) + + return vectors + + async def _ollama_embed(self, text: str) -> list[float]: + """ + Generate embedding using Ollama via OpenAI-compatible endpoint. + + Args: + text: Text to embed + + Returns: + Embedding vector + """ + # Keep the full model name for OpenAI-compatible endpoints + model_name = self.model + + # Use OpenAI-compatible endpoint for ollama models + response = await self.client.post( + f"{self.endpoint}/v1/embeddings", + json={ + "model": model_name, + "input": text, + }, + ) + _ = response.raise_for_status() + + response_data = response.json() + if not isinstance(response_data, dict): + raise VectorizationError("Invalid response format from OpenAI-compatible API") + + # Parse OpenAI-compatible response format + embeddings_raw = response_data.get("data", []) + if not isinstance(embeddings_raw, list) or not embeddings_raw: + raise VectorizationError("No embeddings returned") + + first_embedding_data = embeddings_raw[0] + if not isinstance(first_embedding_data, dict): + raise VectorizationError("Invalid embedding data format") + + embedding_raw = first_embedding_data.get("embedding") + if not isinstance(embedding_raw, list): + raise VectorizationError("Invalid embedding format") + + # Convert to float list and validate + embedding: list[float] = [] + for item in embedding_raw: + if isinstance(item, (int, float)): + embedding.append(float(item)) + else: + raise VectorizationError(f"Invalid embedding value type: {type(item)}") + + # Ensure correct dimension + if len(embedding) != self.dimension: + # Truncate or pad as needed + if len(embedding) > self.dimension: + embedding = embedding[: self.dimension] + else: + embedding.extend([0.0] * (self.dimension - len(embedding))) + + return embedding + + async def _openai_embed(self, text: str) -> list[float]: + """ + Generate embedding using OpenAI-compatible API. + + Args: + text: Text to embed + + Returns: + Embedding vector + """ + response = await self.client.post( + f"{self.endpoint}/v1/embeddings", + json={ + "model": self.model, + "input": text, + }, + ) + _ = response.raise_for_status() + + response_data = response.json() + if not isinstance(response_data, dict): + raise VectorizationError("Invalid response format from OpenAI API") + + data: dict[str, list[dict[str, list[float]]]] = response_data + + embeddings_raw = data.get("data", []) + if not isinstance(embeddings_raw, list) or not embeddings_raw: + raise VectorizationError("No embeddings returned") + + first_embedding_data = embeddings_raw[0] + if not isinstance(first_embedding_data, dict): + raise VectorizationError("Invalid embedding data format") + + embedding_raw = first_embedding_data.get("embedding") + if not isinstance(embedding_raw, list): + raise VectorizationError("Invalid embedding format") + + # Convert to float list and validate + embedding: list[float] = [] + for item in embedding_raw: + if isinstance(item, (int, float)): + embedding.append(float(item)) + else: + raise VectorizationError(f"Invalid embedding value type: {type(item)}") + + # Ensure correct dimension + if len(embedding) != self.dimension: + if len(embedding) > self.dimension: + embedding = embedding[: self.dimension] + else: + embedding.extend([0.0] * (self.dimension - len(embedding))) + + return embedding + + async def __aenter__(self) -> Self: + """Async context manager entry.""" + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + """Async context manager exit.""" + await self.client.aclose() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..68ee5da --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,78 @@ +[project] +name = "ingest-pipeline" +version = "0.1.0" +description = "Document ingestion pipeline with Prefect orchestration" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "prefect>=2.14.0", + "pydantic>=2.5.0", + "pydantic-settings>=2.1.0", + "firecrawl-py>=0.0.1", + "gitpython>=3.1.40", + "weaviate-client>=4.4.0", + "httpx>=0.25.0", + "typer>=0.9.0", + "rich>=13.7.0", + "textual>=0.50.0", + "python-dotenv>=1.0.0", +] + +[project.scripts] +ingest = "ingest_pipeline.cli.main:app" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["ingest_pipeline"] + +[tool.uv] +dev-dependencies = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "pytest-cov>=4.1.0", + "mypy>=1.7.0", + "ruff>=0.1.0", + "basedpyright>=1.31.4", +] + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade +] +ignore = [ + "E501", # line too long (handled by formatter) +] + +[tool.ruff.lint.per-file-ignores] +"ingest_pipeline/cli/main.py" = ["B008"] # Typer uses function calls in defaults + +[tool.mypy] +python_version = "3.11" +strict = true +warn_return_any = true +warn_unused_configs = true +ignore_missing_imports = true +# Allow AsyncGenerator types in overrides +disable_error_code = ["override"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +pythonpath = ["."] + +[tool.coverage.run] +source = ["ingest_pipeline"] +omit = ["*/tests/*", "*/__main__.py"] diff --git a/tui b/tui new file mode 100755 index 0000000..1091765 --- /dev/null +++ b/tui @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +cd "$(dirname "$0")" +uv run python -m ingest_pipeline tui \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1c85793 --- /dev/null +++ b/uv.lock @@ -0,0 +1,2771 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, + { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, + { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, + { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, + { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, + { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, + { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, + { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, + { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, + { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + +[[package]] +name = "alembic" +version = "1.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/ca/4dc52902cf3491892d464f5265a81e9dff094692c8a049a3ed6a05fe7ee8/alembic-1.16.5.tar.gz", hash = "sha256:a88bb7f6e513bd4301ecf4c7f2206fe93f9913f9b48dac3b78babde2d6fe765e", size = 1969868, upload-time = "2025-08-27T18:02:05.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/4a/4c61d4c84cfd9befb6fa08a702535b27b21fff08c946bc2f6139decbf7f7/alembic-1.16.5-py3-none-any.whl", hash = "sha256:e845dfe090c5ffa7b92593ae6687c5cb1a101e91fa53868497dbd79847f9dbe3", size = 247355, upload-time = "2025-08-27T18:02:07.37Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, +] + +[[package]] +name = "apprise" +version = "1.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "click" }, + { name = "markdown" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/f9/bda66afaf393f6914f4d6c035964936cadd98ee1fef44e4e77cba3b5828c/apprise-1.9.4.tar.gz", hash = "sha256:483122aee19a89a7b075ecd48ef11ae37d79744f7aeb450bcf985a9a6c28c988", size = 1855012, upload-time = "2025-08-02T18:13:28.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/fa/7875ad63088b2d7dea538ffe60fba85786c228c7349d258891c54d0416a0/apprise-1.9.4-py3-none-any.whl", hash = "sha256:17dca8ad0a5b2063f6bae707979a51ca2cb374fcc66b0dd5c05a9d286dd40069", size = 1402630, upload-time = "2025-08-02T18:13:26.263Z" }, +] + +[[package]] +name = "asgi-lifespan" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/da/e7908b54e0f8043725a990bf625f2041ecf6bfe8eb7b19407f1c00b630f7/asgi-lifespan-2.1.0.tar.gz", hash = "sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308", size = 15627, upload-time = "2023-03-28T17:35:49.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895, upload-time = "2023-03-28T17:35:47.772Z" }, +] + +[[package]] +name = "asyncpg" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746, upload-time = "2024-10-20T00:30:41.127Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506, upload-time = "2024-10-20T00:29:27.988Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922, upload-time = "2024-10-20T00:29:29.391Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565, upload-time = "2024-10-20T00:29:30.832Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962, upload-time = "2024-10-20T00:29:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791, upload-time = "2024-10-20T00:29:34.677Z" }, + { url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696, upload-time = "2024-10-20T00:29:36.389Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358, upload-time = "2024-10-20T00:29:37.915Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375, upload-time = "2024-10-20T00:29:39.987Z" }, + { url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162, upload-time = "2024-10-20T00:29:41.88Z" }, + { url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025, upload-time = "2024-10-20T00:29:43.352Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243, upload-time = "2024-10-20T00:29:44.922Z" }, + { url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059, upload-time = "2024-10-20T00:29:46.891Z" }, + { url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596, upload-time = "2024-10-20T00:29:49.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632, upload-time = "2024-10-20T00:29:50.768Z" }, + { url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186, upload-time = "2024-10-20T00:29:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064, upload-time = "2024-10-20T00:29:53.757Z" }, + { url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373, upload-time = "2024-10-20T00:29:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745, upload-time = "2024-10-20T00:29:57.14Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103, upload-time = "2024-10-20T00:29:58.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471, upload-time = "2024-10-20T00:30:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253, upload-time = "2024-10-20T00:30:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720, upload-time = "2024-10-20T00:30:04.501Z" }, + { url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404, upload-time = "2024-10-20T00:30:06.537Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623, upload-time = "2024-10-20T00:30:09.024Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "authlib" +version = "1.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/c6/d9a9db2e71957827e23a34322bde8091b51cb778dcc38885b84c772a1ba9/authlib-1.6.3.tar.gz", hash = "sha256:9f7a982cc395de719e4c2215c5707e7ea690ecf84f1ab126f28c053f4219e610", size = 160836, upload-time = "2025-08-26T12:13:25.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/2f/efa9d26dbb612b774990741fd8f13c7cf4cfd085b870e4a5af5c82eaf5f1/authlib-1.6.3-py2.py3-none-any.whl", hash = "sha256:7ea0f082edd95a03b7b72edac65ec7f8f68d703017d7e37573aee4fc603f2a48", size = 240105, upload-time = "2025-08-26T12:13:23.889Z" }, +] + +[[package]] +name = "basedpyright" +version = "1.31.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodejs-wheel-binaries" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/53/570b03ec0445a9b2cc69788482c1d12902a9b88a9b159e449c4c537c4e3a/basedpyright-1.31.4.tar.gz", hash = "sha256:2450deb16530f7c88c1a7da04530a079f9b0b18ae1c71cb6f812825b3b82d0b1", size = 22494467, upload-time = "2025-09-03T13:05:55.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/40/d1047a5addcade9291685d06ef42a63c1347517018bafd82747af9da0294/basedpyright-1.31.4-py3-none-any.whl", hash = "sha256:055e4a38024bd653be12d6216c1cfdbee49a1096d342b4d5f5b4560f7714b6fc", size = 11731440, upload-time = "2025-09-03T13:05:52.308Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/e4fad8155db4a04bfb4734c7c8ff0882f078f24294d42798b3568eb63bff/cachetools-6.2.0.tar.gz", hash = "sha256:38b328c0889450f05f5e120f56ab68c8abaf424e1275522b138ffc93253f7e32", size = 30988, upload-time = "2025-08-25T18:57:30.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/56/3124f61d37a7a4e7cc96afc5492c78ba0cb551151e530b54669ddd1436ef/cachetools-6.2.0-py3-none-any.whl", hash = "sha256:1c76a8960c0041fcc21097e357f882197c79da0dbff766e7317890a65d7d8ba6", size = 11276, upload-time = "2025-08-25T18:57:29.684Z" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coolname" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/c6/1eaa4495ff4640e80d9af64f540e427ba1596a20f735d4c4750fe0386d07/coolname-2.2.0.tar.gz", hash = "sha256:6c5d5731759104479e7ca195a9b64f7900ac5bead40183c09323c7d0be9e75c7", size = 59006, upload-time = "2023-01-09T14:50:41.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/b1/5745d7523d8ce53b87779f46ef6cf5c5c342997939c2fe967e607b944e43/coolname-2.2.0-py2.py3-none-any.whl", hash = "sha256:4d1563186cfaf71b394d5df4c744f8c41303b6846413645e31d31915cdeb13e8", size = 37849, upload-time = "2023-01-09T14:50:39.897Z" }, +] + +[[package]] +name = "coverage" +version = "7.10.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/70/025b179c993f019105b79575ac6edb5e084fb0f0e63f15cdebef4e454fb5/coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", size = 823736, upload-time = "2025-08-29T15:35:16.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/16/2bea27e212c4980753d6d563a0803c150edeaaddb0771a50d2afc410a261/coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", size = 217129, upload-time = "2025-08-29T15:33:13.575Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/e7159e068831ab37e31aac0969d47b8c5ee25b7d307b51e310ec34869315/coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", size = 217532, upload-time = "2025-08-29T15:33:14.872Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c0/246ccbea53d6099325d25cd208df94ea435cd55f0db38099dd721efc7a1f/coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", size = 247931, upload-time = "2025-08-29T15:33:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/7435ef8ab9b2594a6e3f58505cc30e98ae8b33265d844007737946c59389/coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", size = 249864, upload-time = "2025-08-29T15:33:17.434Z" }, + { url = "https://files.pythonhosted.org/packages/51/f8/d9d64e8da7bcddb094d511154824038833c81e3a039020a9d6539bf303e9/coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", size = 251969, upload-time = "2025-08-29T15:33:18.822Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/c43ba0ef19f446d6463c751315140d8f2a521e04c3e79e5c5fe211bfa430/coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", size = 249659, upload-time = "2025-08-29T15:33:20.407Z" }, + { url = "https://files.pythonhosted.org/packages/79/3e/53635bd0b72beaacf265784508a0b386defc9ab7fad99ff95f79ce9db555/coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", size = 247714, upload-time = "2025-08-29T15:33:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/4c/55/0964aa87126624e8c159e32b0bc4e84edef78c89a1a4b924d28dd8265625/coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", size = 248351, upload-time = "2025-08-29T15:33:23.105Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ab/6cfa9dc518c6c8e14a691c54e53a9433ba67336c760607e299bfcf520cb1/coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", size = 219562, upload-time = "2025-08-29T15:33:24.717Z" }, + { url = "https://files.pythonhosted.org/packages/5b/18/99b25346690cbc55922e7cfef06d755d4abee803ef335baff0014268eff4/coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", size = 220453, upload-time = "2025-08-29T15:33:26.482Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ed/81d86648a07ccb124a5cf1f1a7788712b8d7216b593562683cd5c9b0d2c1/coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", size = 219127, upload-time = "2025-08-29T15:33:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/26/06/263f3305c97ad78aab066d116b52250dd316e74fcc20c197b61e07eb391a/coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", size = 217324, upload-time = "2025-08-29T15:33:29.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/60/1e1ded9a4fe80d843d7d53b3e395c1db3ff32d6c301e501f393b2e6c1c1f/coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", size = 217560, upload-time = "2025-08-29T15:33:30.748Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/52136173c14e26dfed8b106ed725811bb53c30b896d04d28d74cb64318b3/coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", size = 249053, upload-time = "2025-08-29T15:33:32.041Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1d/ae25a7dc58fcce8b172d42ffe5313fc267afe61c97fa872b80ee72d9515a/coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", size = 251802, upload-time = "2025-08-29T15:33:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7a/1f561d47743710fe996957ed7c124b421320f150f1d38523d8d9102d3e2a/coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", size = 252935, upload-time = "2025-08-29T15:33:34.909Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ad/8b97cd5d28aecdfde792dcbf646bac141167a5cacae2cd775998b45fabb5/coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", size = 250855, upload-time = "2025-08-29T15:33:36.922Z" }, + { url = "https://files.pythonhosted.org/packages/33/6a/95c32b558d9a61858ff9d79580d3877df3eb5bc9eed0941b1f187c89e143/coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", size = 248974, upload-time = "2025-08-29T15:33:38.175Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9c/8ce95dee640a38e760d5b747c10913e7a06554704d60b41e73fdea6a1ffd/coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", size = 250409, upload-time = "2025-08-29T15:33:39.447Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/7a55b0bdde78a98e2eb2356771fd2dcddb96579e8342bb52aa5bc52e96f0/coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", size = 219724, upload-time = "2025-08-29T15:33:41.172Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/32b185b8b8e327802c9efce3d3108d2fe2d9d31f153a0f7ecfd59c773705/coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", size = 220536, upload-time = "2025-08-29T15:33:42.524Z" }, + { url = "https://files.pythonhosted.org/packages/08/3a/d5d8dc703e4998038c3099eaf77adddb00536a3cec08c8dcd556a36a3eb4/coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", size = 219171, upload-time = "2025-08-29T15:33:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e7/917e5953ea29a28c1057729c1d5af9084ab6d9c66217523fd0e10f14d8f6/coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6", size = 217351, upload-time = "2025-08-29T15:33:45.438Z" }, + { url = "https://files.pythonhosted.org/packages/eb/86/2e161b93a4f11d0ea93f9bebb6a53f113d5d6e416d7561ca41bb0a29996b/coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", size = 217600, upload-time = "2025-08-29T15:33:47.269Z" }, + { url = "https://files.pythonhosted.org/packages/0e/66/d03348fdd8df262b3a7fb4ee5727e6e4936e39e2f3a842e803196946f200/coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", size = 248600, upload-time = "2025-08-29T15:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", size = 251206, upload-time = "2025-08-29T15:33:50.697Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1f/9020135734184f439da85c70ea78194c2730e56c2d18aee6e8ff1719d50d/coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", size = 252478, upload-time = "2025-08-29T15:33:52.303Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/3d228f3942bb5a2051fde28c136eea23a761177dc4ff4ef54533164ce255/coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", size = 250637, upload-time = "2025-08-29T15:33:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/36/e3/293dce8cdb9a83de971637afc59b7190faad60603b40e32635cbd15fbf61/coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", size = 248529, upload-time = "2025-08-29T15:33:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/90/26/64eecfa214e80dd1d101e420cab2901827de0e49631d666543d0e53cf597/coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", size = 250143, upload-time = "2025-08-29T15:33:56.386Z" }, + { url = "https://files.pythonhosted.org/packages/3e/70/bd80588338f65ea5b0d97e424b820fb4068b9cfb9597fbd91963086e004b/coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", size = 219770, upload-time = "2025-08-29T15:33:58.063Z" }, + { url = "https://files.pythonhosted.org/packages/a7/14/0b831122305abcc1060c008f6c97bbdc0a913ab47d65070a01dc50293c2b/coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", size = 220566, upload-time = "2025-08-29T15:33:59.766Z" }, + { url = "https://files.pythonhosted.org/packages/83/c6/81a83778c1f83f1a4a168ed6673eeedc205afb562d8500175292ca64b94e/coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", size = 219195, upload-time = "2025-08-29T15:34:01.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/ccccf4bf116f9517275fa85047495515add43e41dfe8e0bef6e333c6b344/coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", size = 218059, upload-time = "2025-08-29T15:34:02.91Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/8a3ceff833d27c7492af4f39d5da6761e9ff624831db9e9f25b3886ddbca/coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", size = 218287, upload-time = "2025-08-29T15:34:05.106Z" }, + { url = "https://files.pythonhosted.org/packages/92/d8/50b4a32580cf41ff0423777a2791aaf3269ab60c840b62009aec12d3970d/coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", size = 259625, upload-time = "2025-08-29T15:34:06.575Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7e/6a7df5a6fb440a0179d94a348eb6616ed4745e7df26bf2a02bc4db72c421/coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", size = 261801, upload-time = "2025-08-29T15:34:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/3a/4c/a270a414f4ed5d196b9d3d67922968e768cd971d1b251e1b4f75e9362f75/coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", size = 264027, upload-time = "2025-08-29T15:34:09.806Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/3210d663d594926c12f373c5370bf1e7c5c3a427519a8afa65b561b9a55c/coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", size = 261576, upload-time = "2025-08-29T15:34:11.585Z" }, + { url = "https://files.pythonhosted.org/packages/72/d0/e1961eff67e9e1dba3fc5eb7a4caf726b35a5b03776892da8d79ec895775/coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", size = 259341, upload-time = "2025-08-29T15:34:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/d6478d152cd189b33eac691cba27a40704990ba95de49771285f34a5861e/coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", size = 260468, upload-time = "2025-08-29T15:34:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/ed/73/737440247c914a332f0b47f7598535b29965bf305e19bbc22d4c39615d2b/coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", size = 220429, upload-time = "2025-08-29T15:34:16.394Z" }, + { url = "https://files.pythonhosted.org/packages/bd/76/b92d3214740f2357ef4a27c75a526eb6c28f79c402e9f20a922c295c05e2/coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", size = 221493, upload-time = "2025-08-29T15:34:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/6dcb29c599c8a1f654ec6cb68d76644fe635513af16e932d2d4ad1e5ac6e/coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", size = 219757, upload-time = "2025-08-29T15:34:19.248Z" }, + { url = "https://files.pythonhosted.org/packages/d3/aa/76cf0b5ec00619ef208da4689281d48b57f2c7fde883d14bf9441b74d59f/coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", size = 217331, upload-time = "2025-08-29T15:34:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/65/91/8e41b8c7c505d398d7730206f3cbb4a875a35ca1041efc518051bfce0f6b/coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", size = 217607, upload-time = "2025-08-29T15:34:22.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/7f/f718e732a423d442e6616580a951b8d1ec3575ea48bcd0e2228386805e79/coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", size = 248663, upload-time = "2025-08-29T15:34:24.425Z" }, + { url = "https://files.pythonhosted.org/packages/e6/52/c1106120e6d801ac03e12b5285e971e758e925b6f82ee9b86db3aa10045d/coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", size = 251197, upload-time = "2025-08-29T15:34:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ec/3a8645b1bb40e36acde9c0609f08942852a4af91a937fe2c129a38f2d3f5/coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", size = 252551, upload-time = "2025-08-29T15:34:27.337Z" }, + { url = "https://files.pythonhosted.org/packages/a1/70/09ecb68eeb1155b28a1d16525fd3a9b65fbe75337311a99830df935d62b6/coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", size = 250553, upload-time = "2025-08-29T15:34:29.065Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/47df374b893fa812e953b5bc93dcb1427a7b3d7a1a7d2db33043d17f74b9/coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", size = 248486, upload-time = "2025-08-29T15:34:30.897Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/9f98640979ecee1b0d1a7164b589de720ddf8100d1747d9bbdb84be0c0fb/coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", size = 249981, upload-time = "2025-08-29T15:34:32.365Z" }, + { url = "https://files.pythonhosted.org/packages/1f/55/eeb6603371e6629037f47bd25bef300387257ed53a3c5fdb159b7ac8c651/coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", size = 220054, upload-time = "2025-08-29T15:34:34.124Z" }, + { url = "https://files.pythonhosted.org/packages/15/d1/a0912b7611bc35412e919a2cd59ae98e7ea3b475e562668040a43fb27897/coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", size = 220851, upload-time = "2025-08-29T15:34:35.651Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2d/11880bb8ef80a45338e0b3e0725e4c2d73ffbb4822c29d987078224fd6a5/coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", size = 219429, upload-time = "2025-08-29T15:34:37.16Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/1f00caad775c03a700146f55536ecd097a881ff08d310a58b353a1421be0/coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", size = 218080, upload-time = "2025-08-29T15:34:38.919Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c4/b1c5d2bd7cc412cbeb035e257fd06ed4e3e139ac871d16a07434e145d18d/coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", size = 218293, upload-time = "2025-08-29T15:34:40.425Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/4468d37c94724bf6ec354e4ec2f205fda194343e3e85fd2e59cec57e6a54/coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", size = 259800, upload-time = "2025-08-29T15:34:41.996Z" }, + { url = "https://files.pythonhosted.org/packages/82/d8/f8fb351be5fee31690cd8da768fd62f1cfab33c31d9f7baba6cd8960f6b8/coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", size = 261965, upload-time = "2025-08-29T15:34:43.61Z" }, + { url = "https://files.pythonhosted.org/packages/e8/70/65d4d7cfc75c5c6eb2fed3ee5cdf420fd8ae09c4808723a89a81d5b1b9c3/coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", size = 264220, upload-time = "2025-08-29T15:34:45.387Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/069df106d19024324cde10e4ec379fe2fb978017d25e97ebee23002fbadf/coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", size = 261660, upload-time = "2025-08-29T15:34:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8a/2974d53904080c5dc91af798b3a54a4ccb99a45595cc0dcec6eb9616a57d/coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", size = 259417, upload-time = "2025-08-29T15:34:48.779Z" }, + { url = "https://files.pythonhosted.org/packages/30/38/9616a6b49c686394b318974d7f6e08f38b8af2270ce7488e879888d1e5db/coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", size = 260567, upload-time = "2025-08-29T15:34:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/3ed2d6312b371a8cf804abf4e14895b70e4c3491c6e53536d63fd0958a8d/coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", size = 220831, upload-time = "2025-08-29T15:34:52.653Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e5/d38d0cb830abede2adb8b147770d2a3d0e7fecc7228245b9b1ae6c24930a/coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", size = 221950, upload-time = "2025-08-29T15:34:54.212Z" }, + { url = "https://files.pythonhosted.org/packages/f4/51/e48e550f6279349895b0ffcd6d2a690e3131ba3a7f4eafccc141966d4dea/coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", size = 219969, upload-time = "2025-08-29T15:34:55.83Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/50db5379b615854b5cf89146f8f5bd1d5a9693d7f3a987e269693521c404/coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", size = 208986, upload-time = "2025-08-29T15:35:14.506Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cryptography" +version = "45.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980, upload-time = "2025-09-01T11:15:03.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105, upload-time = "2025-09-01T11:13:59.684Z" }, + { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799, upload-time = "2025-09-01T11:14:02.517Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504, upload-time = "2025-09-01T11:14:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542, upload-time = "2025-09-01T11:14:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e3/e7de4771a08620eef2389b86cd87a2c50326827dea5528feb70595439ce4/cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", size = 3889244, upload-time = "2025-09-01T11:14:08.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/b8/bca71059e79a0bb2f8e4ec61d9c205fbe97876318566cde3b5092529faa9/cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", size = 4461975, upload-time = "2025-09-01T11:14:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/58/67/3f5b26937fe1218c40e95ef4ff8d23c8dc05aa950d54200cc7ea5fb58d28/cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", size = 4209082, upload-time = "2025-09-01T11:14:11.229Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397, upload-time = "2025-09-01T11:14:12.924Z" }, + { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244, upload-time = "2025-09-01T11:14:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862, upload-time = "2025-09-01T11:14:16.185Z" }, + { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578, upload-time = "2025-09-01T11:14:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400, upload-time = "2025-09-01T11:14:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824, upload-time = "2025-09-01T11:14:20.954Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233, upload-time = "2025-09-01T11:14:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075, upload-time = "2025-09-01T11:14:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517, upload-time = "2025-09-01T11:14:25.679Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/924a723299848b4c741c1059752c7cfe09473b6fd77d2920398fc26bfb53/cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", size = 3882893, upload-time = "2025-09-01T11:14:27.1Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/4dab2ff0a871cc2d81d3ae6d780991c0192b259c35e4d83fe1de18b20c70/cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", size = 4450132, upload-time = "2025-09-01T11:14:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/b2882b65db8fc944585d7fb00d67cf84a9cef4e77d9ba8f69082e911d0de/cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", size = 4204086, upload-time = "2025-09-01T11:14:30.572Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383, upload-time = "2025-09-01T11:14:32.046Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186, upload-time = "2025-09-01T11:14:33.95Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639, upload-time = "2025-09-01T11:14:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552, upload-time = "2025-09-01T11:14:36.929Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742, upload-time = "2025-09-01T11:14:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/99/4e/49199a4c82946938a3e05d2e8ad9482484ba48bbc1e809e3d506c686d051/cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde", size = 3584634, upload-time = "2025-09-01T11:14:50.593Z" }, + { url = "https://files.pythonhosted.org/packages/16/ce/5f6ff59ea9c7779dba51b84871c19962529bdcc12e1a6ea172664916c550/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34", size = 4149533, upload-time = "2025-09-01T11:14:52.091Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/b3cfbd257ac96da4b88b46372e662009b7a16833bfc5da33bb97dd5631ae/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9", size = 4385557, upload-time = "2025-09-01T11:14:53.551Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c5/8c59d6b7c7b439ba4fc8d0cab868027fd095f215031bc123c3a070962912/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae", size = 4149023, upload-time = "2025-09-01T11:14:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/05385c86d6ca9ab0b4d5bb442d2e3d85e727939a11f3e163fc776ce5eb40/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b", size = 4385722, upload-time = "2025-09-01T11:14:57.319Z" }, + { url = "https://files.pythonhosted.org/packages/23/87/7ce86f3fa14bc11a5a48c30d8103c26e09b6465f8d8e9d74cf7a0714f043/cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63", size = 3332908, upload-time = "2025-09-01T11:14:58.78Z" }, +] + +[[package]] +name = "dateparser" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "regex" }, + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/30/064144f0df1749e7bb5faaa7f52b007d7c2d08ec08fed8411aba87207f68/dateparser-1.2.2.tar.gz", hash = "sha256:986316f17cb8cdc23ea8ce563027c5ef12fc725b6fb1d137c14ca08777c5ecf7", size = 329840, upload-time = "2025-06-26T09:29:23.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/22/f020c047ae1346613db9322638186468238bcfa8849b4668a22b97faad65/dateparser-1.2.2-py3-none-any.whl", hash = "sha256:5a5d7211a09013499867547023a2a0c91d5a27d15dd4dbcea676ea9fe66f2482", size = 315453, upload-time = "2025-06-26T09:29:21.412Z" }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "fastapi" +version = "0.116.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/d7/6c8b3bfe33eeffa208183ec037fee0cce9f7f024089ab1c5d12ef04bd27c/fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143", size = 296485, upload-time = "2025-07-11T16:22:32.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" }, +] + +[[package]] +name = "firecrawl-py" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "httpx" }, + { name = "nest-asyncio" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/c8/623ededf2d6cb6d9076e006fa0e6945f199a2376f6b9ed9490b578780090/firecrawl_py-4.3.6.tar.gz", hash = "sha256:303827a86d0f6237a8ddcaa0bcdaa4c5ee11d9a4880b0685302b8d9a0e191ee0", size = 133431, upload-time = "2025-09-07T19:07:11.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/ac/f129e839a0de542473f8e264b741ab4922fabda23146ffd19298fedbffa4/firecrawl_py-4.3.6-py3-none-any.whl", hash = "sha256:9b5dffdf5ed08fdbf0966f17e18c1a034d59f42a20b2bf9a6291a83190d7eb0f", size = 168702, upload-time = "2025-09-07T19:07:10.556Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, + { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, + { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, + { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, + { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, + { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, + { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, + { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "griffe" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, +] + +[[package]] +name = "grpcio" +version = "1.74.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/35feb8f7cab7239c5b94bd2db71abb3d6adb5f335ad8f131abb6060840b6/grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1", size = 12756048, upload-time = "2025-07-24T18:54:23.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/77/b2f06db9f240a5abeddd23a0e49eae2b6ac54d85f0e5267784ce02269c3b/grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31", size = 5487368, upload-time = "2025-07-24T18:53:03.548Z" }, + { url = "https://files.pythonhosted.org/packages/48/99/0ac8678a819c28d9a370a663007581744a9f2a844e32f0fa95e1ddda5b9e/grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4", size = 10999804, upload-time = "2025-07-24T18:53:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/45/c6/a2d586300d9e14ad72e8dc211c7aecb45fe9846a51e558c5bca0c9102c7f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce", size = 5987667, upload-time = "2025-07-24T18:53:07.157Z" }, + { url = "https://files.pythonhosted.org/packages/c9/57/5f338bf56a7f22584e68d669632e521f0de460bb3749d54533fc3d0fca4f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3", size = 6655612, upload-time = "2025-07-24T18:53:09.244Z" }, + { url = "https://files.pythonhosted.org/packages/82/ea/a4820c4c44c8b35b1903a6c72a5bdccec92d0840cf5c858c498c66786ba5/grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182", size = 6219544, upload-time = "2025-07-24T18:53:11.221Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/0537630a921365928f5abb6d14c79ba4dcb3e662e0dbeede8af4138d9dcf/grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d", size = 6334863, upload-time = "2025-07-24T18:53:12.925Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a6/85ca6cb9af3f13e1320d0a806658dca432ff88149d5972df1f7b51e87127/grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f", size = 7019320, upload-time = "2025-07-24T18:53:15.002Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/fe2beab970a1e25d2eff108b3cf4f7d9a53c185106377a3d1989216eba45/grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4", size = 6514228, upload-time = "2025-07-24T18:53:16.999Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/2f9c945c8a248cebc3ccda1b7a1bf1775b9d7d59e444dbb18c0014e23da6/grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b", size = 3817216, upload-time = "2025-07-24T18:53:20.564Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d1/a9cf9c94b55becda2199299a12b9feef0c79946b0d9d34c989de6d12d05d/grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11", size = 4495380, upload-time = "2025-07-24T18:53:22.058Z" }, + { url = "https://files.pythonhosted.org/packages/4c/5d/e504d5d5c4469823504f65687d6c8fb97b7f7bf0b34873b7598f1df24630/grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8", size = 5445551, upload-time = "2025-07-24T18:53:23.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/01/730e37056f96f2f6ce9f17999af1556df62ee8dab7fa48bceeaab5fd3008/grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6", size = 10979810, upload-time = "2025-07-24T18:53:25.349Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/09fd100473ea5c47083889ca47ffd356576173ec134312f6aa0e13111dee/grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5", size = 5941946, upload-time = "2025-07-24T18:53:27.387Z" }, + { url = "https://files.pythonhosted.org/packages/8a/99/12d2cca0a63c874c6d3d195629dcd85cdf5d6f98a30d8db44271f8a97b93/grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49", size = 6621763, upload-time = "2025-07-24T18:53:29.193Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2c/930b0e7a2f1029bbc193443c7bc4dc2a46fedb0203c8793dcd97081f1520/grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7", size = 6180664, upload-time = "2025-07-24T18:53:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/ff8a2442180ad0867717e670f5ec42bfd8d38b92158ad6bcd864e6d4b1ed/grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3", size = 6301083, upload-time = "2025-07-24T18:53:32.454Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ba/b361d390451a37ca118e4ec7dccec690422e05bc85fba2ec72b06cefec9f/grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707", size = 6994132, upload-time = "2025-07-24T18:53:34.506Z" }, + { url = "https://files.pythonhosted.org/packages/3b/0c/3a5fa47d2437a44ced74141795ac0251bbddeae74bf81df3447edd767d27/grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b", size = 6489616, upload-time = "2025-07-24T18:53:36.217Z" }, + { url = "https://files.pythonhosted.org/packages/ae/95/ab64703b436d99dc5217228babc76047d60e9ad14df129e307b5fec81fd0/grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c", size = 3807083, upload-time = "2025-07-24T18:53:37.911Z" }, + { url = "https://files.pythonhosted.org/packages/84/59/900aa2445891fc47a33f7d2f76e00ca5d6ae6584b20d19af9c06fa09bf9a/grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc", size = 4490123, upload-time = "2025-07-24T18:53:39.528Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d8/1004a5f468715221450e66b051c839c2ce9a985aa3ee427422061fcbb6aa/grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89", size = 5449488, upload-time = "2025-07-24T18:53:41.174Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/33731a03f63740d7743dced423846c831d8e6da808fcd02821a4416df7fa/grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01", size = 10974059, upload-time = "2025-07-24T18:53:43.066Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c6/3d2c14d87771a421205bdca991467cfe473ee4c6a1231c1ede5248c62ab8/grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e", size = 5945647, upload-time = "2025-07-24T18:53:45.269Z" }, + { url = "https://files.pythonhosted.org/packages/c5/83/5a354c8aaff58594eef7fffebae41a0f8995a6258bbc6809b800c33d4c13/grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91", size = 6626101, upload-time = "2025-07-24T18:53:47.015Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ca/4fdc7bf59bf6994aa45cbd4ef1055cd65e2884de6113dbd49f75498ddb08/grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249", size = 6182562, upload-time = "2025-07-24T18:53:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/fd/48/2869e5b2c1922583686f7ae674937986807c2f676d08be70d0a541316270/grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362", size = 6303425, upload-time = "2025-07-24T18:53:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0e/bac93147b9a164f759497bc6913e74af1cb632c733c7af62c0336782bd38/grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f", size = 6996533, upload-time = "2025-07-24T18:53:52.747Z" }, + { url = "https://files.pythonhosted.org/packages/84/35/9f6b2503c1fd86d068b46818bbd7329db26a87cdd8c01e0d1a9abea1104c/grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20", size = 6491489, upload-time = "2025-07-24T18:53:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/75/33/a04e99be2a82c4cbc4039eb3a76f6c3632932b9d5d295221389d10ac9ca7/grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa", size = 3805811, upload-time = "2025-07-24T18:53:56.798Z" }, + { url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "humanize" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/1d/3062fcc89ee05a715c0b9bfe6490c00c576314f27ffee3a704122c6fd259/humanize-4.13.0.tar.gz", hash = "sha256:78f79e68f76f0b04d711c4e55d32bebef5be387148862cb1ef83d2b58e7935a0", size = 81884, upload-time = "2025-08-25T09:39:20.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/c7/316e7ca04d26695ef0635dc81683d628350810eb8e9b2299fc08ba49f366/humanize-4.13.0-py3-none-any.whl", hash = "sha256:b810820b31891813b1673e8fec7f1ed3312061eab2f26e3fa192c393d11ed25f", size = 128869, upload-time = "2025-08-25T09:39:18.54Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "ingest-pipeline" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "firecrawl-py" }, + { name = "gitpython" }, + { name = "httpx" }, + { name = "prefect" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "textual" }, + { name = "typer" }, + { name = "weaviate-client" }, +] + +[package.dev-dependencies] +dev = [ + { name = "basedpyright" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "firecrawl-py", specifier = ">=0.0.1" }, + { name = "gitpython", specifier = ">=3.1.40" }, + { name = "httpx", specifier = ">=0.25.0" }, + { name = "prefect", specifier = ">=2.14.0" }, + { name = "pydantic", specifier = ">=2.5.0" }, + { name = "pydantic-settings", specifier = ">=2.1.0" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, + { name = "rich", specifier = ">=13.7.0" }, + { name = "textual", specifier = ">=0.50.0" }, + { name = "typer", specifier = ">=0.9.0" }, + { name = "weaviate-client", specifier = ">=4.4.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "basedpyright", specifier = ">=1.31.4" }, + { name = "mypy", specifier = ">=1.7.0" }, + { name = "pytest", specifier = ">=7.4.0" }, + { name = "pytest-asyncio", specifier = ">=0.21.0" }, + { name = "pytest-cov", specifier = ">=4.1.0" }, + { name = "ruff", specifier = ">=0.1.0" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jinja2-humanize-extension" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanize" }, + { name = "jinja2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/77/0bba383819dd4e67566487c11c49479ced87e77c3285d8e7f7a3401cf882/jinja2_humanize_extension-0.4.0.tar.gz", hash = "sha256:e7d69b1c20f32815bbec722330ee8af14b1287bb1c2b0afa590dbf031cadeaa0", size = 4746, upload-time = "2023-09-01T12:52:42.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/b4/08c9d297edd5e1182506edecccbb88a92e1122a057953068cadac420ca5d/jinja2_humanize_extension-0.4.0-py3-none-any.whl", hash = "sha256:b6326e2da0f7d425338bebf58848e830421defbce785f12ae812e65128518156", size = 4769, upload-time = "2023-09-01T12:52:41.098Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "linkify-it-py" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "uc-micro-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[package.optional-dependencies] +linkify = [ + { name = "linkify-it-py" }, +] +plugins = [ + { name = "mdit-py-plugins" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/7f/90a7f01e2d005d6653c689039977f6856718c75c5579445effb7e60923d1/multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c", size = 76472, upload-time = "2025-08-11T12:06:29.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb", size = 44634, upload-time = "2025-08-11T12:06:30.374Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e", size = 44282, upload-time = "2025-08-11T12:06:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/03/35/436a5da8702b06866189b69f655ffdb8f70796252a8772a77815f1812679/multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded", size = 229696, upload-time = "2025-08-11T12:06:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0e/915160be8fecf1fca35f790c08fb74ca684d752fcba62c11daaf3d92c216/multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683", size = 246665, upload-time = "2025-08-11T12:06:34.448Z" }, + { url = "https://files.pythonhosted.org/packages/08/ee/2f464330acd83f77dcc346f0b1a0eaae10230291450887f96b204b8ac4d3/multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a", size = 225485, upload-time = "2025-08-11T12:06:35.672Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/9a117f828b4d7fbaec6adeed2204f211e9caf0a012692a1ee32169f846ae/multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9", size = 257318, upload-time = "2025-08-11T12:06:36.98Z" }, + { url = "https://files.pythonhosted.org/packages/25/77/62752d3dbd70e27fdd68e86626c1ae6bccfebe2bb1f84ae226363e112f5a/multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50", size = 254689, upload-time = "2025-08-11T12:06:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52", size = 246709, upload-time = "2025-08-11T12:06:39.517Z" }, + { url = "https://files.pythonhosted.org/packages/01/ef/4698d6842ef5e797c6db7744b0081e36fb5de3d00002cc4c58071097fac3/multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6", size = 243185, upload-time = "2025-08-11T12:06:40.796Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c9/d82e95ae1d6e4ef396934e9b0e942dfc428775f9554acf04393cce66b157/multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e", size = 237838, upload-time = "2025-08-11T12:06:42.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/f94af5c36baaa75d44fab9f02e2a6bcfa0cd90acb44d4976a80960759dbc/multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3", size = 246368, upload-time = "2025-08-11T12:06:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fe/29f23460c3d995f6a4b678cb2e9730e7277231b981f0b234702f0177818a/multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c", size = 253339, upload-time = "2025-08-11T12:06:45.597Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/fd59449204426187b82bf8a75f629310f68c6adc9559dc922d5abe34797b/multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b", size = 246933, upload-time = "2025-08-11T12:06:46.841Z" }, + { url = "https://files.pythonhosted.org/packages/19/52/d5d6b344f176a5ac3606f7a61fb44dc746e04550e1a13834dff722b8d7d6/multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f", size = 242225, upload-time = "2025-08-11T12:06:48.588Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d3/5b2281ed89ff4d5318d82478a2a2450fcdfc3300da48ff15c1778280ad26/multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2", size = 41306, upload-time = "2025-08-11T12:06:49.95Z" }, + { url = "https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e", size = 46029, upload-time = "2025-08-11T12:06:51.082Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5e/553d67d24432c5cd52b49047f2d248821843743ee6d29a704594f656d182/multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf", size = 43017, upload-time = "2025-08-11T12:06:52.243Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, + { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, + { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, + { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, + { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, + { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, + { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/a3/931e09fc02d7ba96da65266884da4e4a8806adcdb8a57faaacc6edf1d538/mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9", size = 3448447, upload-time = "2025-09-11T23:00:47.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/28/47709d5d9e7068b26c0d5189c8137c8783e81065ad1102b505214a08b548/mypy-1.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c903857b3e28fc5489e54042684a9509039ea0aedb2a619469438b544ae1961", size = 12734635, upload-time = "2025-09-11T23:00:24.983Z" }, + { url = "https://files.pythonhosted.org/packages/7c/12/ee5c243e52497d0e59316854041cf3b3130131b92266d0764aca4dec3c00/mypy-1.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a0c8392c19934c2b6c65566d3a6abdc6b51d5da7f5d04e43f0eb627d6eeee65", size = 11817287, upload-time = "2025-09-11T22:59:07.38Z" }, + { url = "https://files.pythonhosted.org/packages/48/bd/2aeb950151005fe708ab59725afed7c4aeeb96daf844f86a05d4b8ac34f8/mypy-1.18.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f85eb7efa2ec73ef63fc23b8af89c2fe5bf2a4ad985ed2d3ff28c1bb3c317c92", size = 12430464, upload-time = "2025-09-11T22:58:48.084Z" }, + { url = "https://files.pythonhosted.org/packages/71/e8/7a20407aafb488acb5734ad7fb5e8c2ef78d292ca2674335350fa8ebef67/mypy-1.18.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82ace21edf7ba8af31c3308a61dc72df30500f4dbb26f99ac36b4b80809d7e94", size = 13164555, upload-time = "2025-09-11T23:00:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c9/5f39065252e033b60f397096f538fb57c1d9fd70a7a490f314df20dd9d64/mypy-1.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a2dfd53dfe632f1ef5d161150a4b1f2d0786746ae02950eb3ac108964ee2975a", size = 13359222, upload-time = "2025-09-11T23:00:33.469Z" }, + { url = "https://files.pythonhosted.org/packages/85/b6/d54111ef3c1e55992cd2ec9b8b6ce9c72a407423e93132cae209f7e7ba60/mypy-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:320f0ad4205eefcb0e1a72428dde0ad10be73da9f92e793c36228e8ebf7298c0", size = 9760441, upload-time = "2025-09-11T23:00:44.826Z" }, + { url = "https://files.pythonhosted.org/packages/e7/14/1c3f54d606cb88a55d1567153ef3a8bc7b74702f2ff5eb64d0994f9e49cb/mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9", size = 12911082, upload-time = "2025-09-11T23:00:41.465Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/235606c8b6d50a8eba99773add907ce1d41c068edb523f81eb0d01603a83/mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e", size = 11919107, upload-time = "2025-09-11T22:58:40.903Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/4e2ce00f8d15b99d0c68a2536ad63e9eac033f723439ef80290ec32c1ff5/mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2", size = 12472551, upload-time = "2025-09-11T22:58:37.272Z" }, + { url = "https://files.pythonhosted.org/packages/32/bb/92642a9350fc339dd9dcefcf6862d171b52294af107d521dce075f32f298/mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d", size = 13340554, upload-time = "2025-09-11T22:59:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/38d01db91c198fb6350025d28f9719ecf3c8f2c55a0094bfbf3ef478cc9a/mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5", size = 13530933, upload-time = "2025-09-11T22:59:20.228Z" }, + { url = "https://files.pythonhosted.org/packages/da/8d/6d991ae631f80d58edbf9d7066e3f2a96e479dca955d9a968cd6e90850a3/mypy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:2657654d82fcd2a87e02a33e0d23001789a554059bbf34702d623dafe353eabf", size = 9828426, upload-time = "2025-09-11T23:00:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ec/ef4a7260e1460a3071628a9277a7579e7da1b071bc134ebe909323f2fbc7/mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f", size = 12918671, upload-time = "2025-09-11T22:58:29.814Z" }, + { url = "https://files.pythonhosted.org/packages/a1/82/0ea6c3953f16223f0b8eda40c1aeac6bd266d15f4902556ae6e91f6fca4c/mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce", size = 11913023, upload-time = "2025-09-11T23:00:29.049Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ef/5e2057e692c2690fc27b3ed0a4dbde4388330c32e2576a23f0302bc8358d/mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e", size = 12473355, upload-time = "2025-09-11T23:00:04.544Z" }, + { url = "https://files.pythonhosted.org/packages/98/43/b7e429fc4be10e390a167b0cd1810d41cb4e4add4ae50bab96faff695a3b/mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71", size = 13346944, upload-time = "2025-09-11T22:58:23.024Z" }, + { url = "https://files.pythonhosted.org/packages/89/4e/899dba0bfe36bbd5b7c52e597de4cf47b5053d337b6d201a30e3798e77a6/mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746", size = 13512574, upload-time = "2025-09-11T22:59:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f8/7661021a5b0e501b76440454d786b0f01bb05d5c4b125fcbda02023d0250/mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d", size = 9837684, upload-time = "2025-09-11T22:58:44.454Z" }, + { url = "https://files.pythonhosted.org/packages/bf/87/7b173981466219eccc64c107cf8e5ab9eb39cc304b4c07df8e7881533e4f/mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61", size = 12900265, upload-time = "2025-09-11T22:59:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/ae/cc/b10e65bae75b18a5ac8f81b1e8e5867677e418f0dd2c83b8e2de9ba96ebd/mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5", size = 11942890, upload-time = "2025-09-11T23:00:00.607Z" }, + { url = "https://files.pythonhosted.org/packages/39/d4/aeefa07c44d09f4c2102e525e2031bc066d12e5351f66b8a83719671004d/mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8", size = 12472291, upload-time = "2025-09-11T22:59:43.425Z" }, + { url = "https://files.pythonhosted.org/packages/c6/07/711e78668ff8e365f8c19735594ea95938bff3639a4c46a905e3ed8ff2d6/mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d", size = 13318610, upload-time = "2025-09-11T23:00:17.604Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/df3b2d39339c31d360ce299b418c55e8194ef3205284739b64962f6074e7/mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d", size = 13513697, upload-time = "2025-09-11T22:58:59.534Z" }, + { url = "https://files.pythonhosted.org/packages/b1/df/462866163c99ea73bb28f0eb4d415c087e30de5d36ee0f5429d42e28689b/mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce", size = 9985739, upload-time = "2025-09-11T22:58:51.644Z" }, + { url = "https://files.pythonhosted.org/packages/e0/1d/4b97d3089b48ef3d904c9ca69fab044475bd03245d878f5f0b3ea1daf7ce/mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e", size = 2352212, upload-time = "2025-09-11T22:59:26.576Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "nodejs-wheel-binaries" +version = "22.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/ca/6033f80b7aebc23cb31ed8b09608b6308c5273c3522aedd043e8a0644d83/nodejs_wheel_binaries-22.19.0.tar.gz", hash = "sha256:e69b97ef443d36a72602f7ed356c6a36323873230f894799f4270a853932fdb3", size = 8060, upload-time = "2025-09-12T10:33:46.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/a2/0d055fd1d8c9a7a971c4db10cf42f3bba57c964beb6cf383ca053f2cdd20/nodejs_wheel_binaries-22.19.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:43eca1526455a1fb4cb777095198f7ebe5111a4444749c87f5c2b84645aaa72a", size = 50902454, upload-time = "2025-09-12T10:33:18.3Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f5/446f7b3c5be1d2f5145ffa3c9aac3496e06cdf0f436adeb21a1f95dd79a7/nodejs_wheel_binaries-22.19.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:feb06709e1320790d34babdf71d841ec7f28e4c73217d733e7f5023060a86bfc", size = 51837860, upload-time = "2025-09-12T10:33:21.599Z" }, + { url = "https://files.pythonhosted.org/packages/1e/4e/d0a036f04fd0f5dc3ae505430657044b8d9853c33be6b2d122bb171aaca3/nodejs_wheel_binaries-22.19.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9f5777292491430457c99228d3a267decf12a09d31246f0692391e3513285e", size = 57841528, upload-time = "2025-09-12T10:33:25.433Z" }, + { url = "https://files.pythonhosted.org/packages/e2/11/4811d27819f229cc129925c170db20c12d4f01ad366a0066f06d6eb833cf/nodejs_wheel_binaries-22.19.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1392896f1a05a88a8a89b26e182d90fdf3020b4598a047807b91b65731e24c00", size = 58368815, upload-time = "2025-09-12T10:33:29.083Z" }, + { url = "https://files.pythonhosted.org/packages/6e/94/df41416856b980e38a7ff280cfb59f142a77955ccdbec7cc4260d8ab2e78/nodejs_wheel_binaries-22.19.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9164c876644f949cad665e3ada00f75023e18f381e78a1d7b60ccbbfb4086e73", size = 59690937, upload-time = "2025-09-12T10:33:32.771Z" }, + { url = "https://files.pythonhosted.org/packages/d1/39/8d0d5f84b7616bdc4eca725f5d64a1cfcac3d90cf3f30cae17d12f8e987f/nodejs_wheel_binaries-22.19.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6b4b75166134010bc9cfebd30dc57047796a27049fef3fc22316216d76bc0af7", size = 60751996, upload-time = "2025-09-12T10:33:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/41/93/2d66b5b60055dd1de6e37e35bef563c15e4cafa5cfe3a6990e0ab358e515/nodejs_wheel_binaries-22.19.0-py2.py3-none-win_amd64.whl", hash = "sha256:3f271f5abfc71b052a6b074225eca8c1223a0f7216863439b86feaca814f6e5a", size = 40026140, upload-time = "2025-09-12T10:33:40.33Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/c9cf7ff7e3c71f07ca8331c939afd09b6e59fc85a2944ea9411e8b29ce50/nodejs_wheel_binaries-22.19.0-py2.py3-none-win_arm64.whl", hash = "sha256:666a355fe0c9bde44a9221cd543599b029045643c8196b8eedb44f28dc192e06", size = 38804500, upload-time = "2025-09-12T10:33:43.302Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923, upload-time = "2025-09-11T10:29:01.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/8b/360674cd817faef32e49276187922a946468579fcaf37afdfb6c07046e92/orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f", size = 238238, upload-time = "2025-08-26T17:44:54.214Z" }, + { url = "https://files.pythonhosted.org/packages/05/3d/5fa9ea4b34c1a13be7d9046ba98d06e6feb1d8853718992954ab59d16625/orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91", size = 127713, upload-time = "2025-08-26T17:44:55.596Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5f/e18367823925e00b1feec867ff5f040055892fc474bf5f7875649ecfa586/orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904", size = 123241, upload-time = "2025-08-26T17:44:57.185Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bd/3c66b91c4564759cf9f473251ac1650e446c7ba92a7c0f9f56ed54f9f0e6/orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6", size = 127895, upload-time = "2025-08-26T17:44:58.349Z" }, + { url = "https://files.pythonhosted.org/packages/82/b5/dc8dcd609db4766e2967a85f63296c59d4722b39503e5b0bf7fd340d387f/orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d", size = 130303, upload-time = "2025-08-26T17:44:59.491Z" }, + { url = "https://files.pythonhosted.org/packages/48/c2/d58ec5fd1270b2aa44c862171891adc2e1241bd7dab26c8f46eb97c6c6f1/orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038", size = 132366, upload-time = "2025-08-26T17:45:00.654Z" }, + { url = "https://files.pythonhosted.org/packages/73/87/0ef7e22eb8dd1ef940bfe3b9e441db519e692d62ed1aae365406a16d23d0/orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb", size = 135180, upload-time = "2025-08-26T17:45:02.424Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2", size = 132741, upload-time = "2025-08-26T17:45:03.663Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0c/4577fd860b6386ffaa56440e792af01c7882b56d2766f55384b5b0e9d39b/orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55", size = 131104, upload-time = "2025-08-26T17:45:04.939Z" }, + { url = "https://files.pythonhosted.org/packages/66/4b/83e92b2d67e86d1c33f2ea9411742a714a26de63641b082bdbf3d8e481af/orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1", size = 403887, upload-time = "2025-08-26T17:45:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e5/9eea6a14e9b5ceb4a271a1fd2e1dec5f2f686755c0fab6673dc6ff3433f4/orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824", size = 145855, upload-time = "2025-08-26T17:45:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/45/78/8d4f5ad0c80ba9bf8ac4d0fc71f93a7d0dc0844989e645e2074af376c307/orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f", size = 135361, upload-time = "2025-08-26T17:45:09.625Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5f/16386970370178d7a9b438517ea3d704efcf163d286422bae3b37b88dbb5/orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204", size = 136190, upload-time = "2025-08-26T17:45:10.962Z" }, + { url = "https://files.pythonhosted.org/packages/09/60/db16c6f7a41dd8ac9fb651f66701ff2aeb499ad9ebc15853a26c7c152448/orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b", size = 131389, upload-time = "2025-08-26T17:45:12.285Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2a/bb811ad336667041dea9b8565c7c9faf2f59b47eb5ab680315eea612ef2e/orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e", size = 126120, upload-time = "2025-08-26T17:45:13.515Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" }, + { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" }, + { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" }, + { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" }, + { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" }, + { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" }, + { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" }, + { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" }, + { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" }, + { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" }, + { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" }, + { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" }, + { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pendulum" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/7c/009c12b86c7cc6c403aec80f8a4308598dfc5995e5c523a5491faaa3952e/pendulum-3.1.0.tar.gz", hash = "sha256:66f96303560f41d097bee7d2dc98ffca716fbb3a832c4b3062034c2d45865015", size = 85930, upload-time = "2025-04-19T14:30:01.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/6e/d28d3c22e6708b819a94c05bd05a3dfaed5c685379e8b6dc4b34b473b942/pendulum-3.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:61a03d14f8c64d13b2f7d5859e4b4053c4a7d3b02339f6c71f3e4606bfd67423", size = 338596, upload-time = "2025-04-19T14:01:11.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/43324d58021d463c2eeb6146b169d2c935f2f840f9e45ac2d500453d954c/pendulum-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e674ed2d158afa5c361e60f1f67872dc55b492a10cacdaa7fcd7b7da5f158f24", size = 325854, upload-time = "2025-04-19T14:01:13.156Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a7/d2ae79b960bfdea94dab67e2f118697b08bc9e98eb6bd8d32c4d99240da3/pendulum-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c75377eb16e58bbe7e03ea89eeea49be6fc5de0934a4aef0e263f8b4fa71bc2", size = 344334, upload-time = "2025-04-19T14:01:15.151Z" }, + { url = "https://files.pythonhosted.org/packages/96/94/941f071212e23c29aae7def891fb636930c648386e059ce09ea0dcd43933/pendulum-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:656b8b0ce070f0f2e5e2668247d3c783c55336534aa1f13bd0969535878955e1", size = 382259, upload-time = "2025-04-19T14:01:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/a78a701656aec00d16fee636704445c23ca11617a0bfe7c3848d1caa5157/pendulum-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48962903e6c1afe1f13548cb6252666056086c107d59e3d64795c58c9298bc2e", size = 436361, upload-time = "2025-04-19T14:01:18.796Z" }, + { url = "https://files.pythonhosted.org/packages/da/93/83f59ccbf4435c29dca8c63a6560fcbe4783079a468a5f91d9f886fd21f0/pendulum-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d364ec3f8e65010fefd4b0aaf7be5eb97e5df761b107a06f5e743b7c3f52c311", size = 353653, upload-time = "2025-04-19T14:01:20.159Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0f/42d6644ec6339b41066f594e52d286162aecd2e9735aaf994d7e00c9e09d/pendulum-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd52caffc2afb86612ec43bbeb226f204ea12ebff9f3d12f900a7d3097210fcc", size = 524567, upload-time = "2025-04-19T14:01:21.457Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/d84d909202755ab9d3379e5481fdf70f53344ebefbd68d6f5803ddde98a6/pendulum-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d439fccaa35c91f686bd59d30604dab01e8b5c1d0dd66e81648c432fd3f8a539", size = 525571, upload-time = "2025-04-19T14:01:23.329Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e0/4de160773ce3c2f7843c310db19dd919a0cd02cc1c0384866f63b18a6251/pendulum-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:43288773a86d9c5c0ddb645f88f615ff6bd12fd1410b34323662beccb18f3b49", size = 260259, upload-time = "2025-04-19T14:01:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7f/ffa278f78112c6c6e5130a702042f52aab5c649ae2edf814df07810bbba5/pendulum-3.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:569ea5072ae0f11d625e03b36d865f8037b76e838a3b621f6967314193896a11", size = 253899, upload-time = "2025-04-19T14:01:26.442Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/b1bfe15a742f2c2713acb1fdc7dc3594ff46ef9418ac6a96fcb12a6ba60b/pendulum-3.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4dfd53e7583ccae138be86d6c0a0b324c7547df2afcec1876943c4d481cf9608", size = 336209, upload-time = "2025-04-19T14:01:27.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/87/0392da0c603c828b926d9f7097fbdddaafc01388cb8a00888635d04758c3/pendulum-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a6e06a28f3a7d696546347805536f6f38be458cb79de4f80754430696bea9e6", size = 323130, upload-time = "2025-04-19T14:01:29.336Z" }, + { url = "https://files.pythonhosted.org/packages/c0/61/95f1eec25796be6dddf71440ee16ec1fd0c573fc61a73bd1ef6daacd529a/pendulum-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e68d6a51880708084afd8958af42dc8c5e819a70a6c6ae903b1c4bfc61e0f25", size = 341509, upload-time = "2025-04-19T14:01:31.1Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7b/eb0f5e6aa87d5e1b467a1611009dbdc92f0f72425ebf07669bfadd8885a6/pendulum-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e3f1e5da39a7ea7119efda1dd96b529748c1566f8a983412d0908455d606942", size = 378674, upload-time = "2025-04-19T14:01:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/5a4c1b5de3e54e16cab21d2ec88f9cd3f18599e96cc90a441c0b0ab6b03f/pendulum-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9af1e5eeddb4ebbe1b1c9afb9fd8077d73416ade42dd61264b3f3b87742e0bb", size = 436133, upload-time = "2025-04-19T14:01:34.349Z" }, + { url = "https://files.pythonhosted.org/packages/87/5d/f7a1d693e5c0f789185117d5c1d5bee104f5b0d9fbf061d715fb61c840a8/pendulum-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f74aa8029a42e327bfc150472e0e4d2358fa5d795f70460160ba81b94b6945", size = 351232, upload-time = "2025-04-19T14:01:35.669Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/c97617eb31f1d0554edb073201a294019b9e0a9bd2f73c68e6d8d048cd6b/pendulum-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cf6229e5ee70c2660148523f46c472e677654d0097bec010d6730f08312a4931", size = 521562, upload-time = "2025-04-19T14:01:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/0d0ef3393303877e757b848ecef8a9a8c7627e17e7590af82d14633b2cd1/pendulum-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:350cabb23bf1aec7c7694b915d3030bff53a2ad4aeabc8c8c0d807c8194113d6", size = 523221, upload-time = "2025-04-19T14:01:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/99/f3/aefb579aa3cebd6f2866b205fc7a60d33e9a696e9e629024752107dc3cf5/pendulum-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:42959341e843077c41d47420f28c3631de054abd64da83f9b956519b5c7a06a7", size = 260502, upload-time = "2025-04-19T14:01:39.814Z" }, + { url = "https://files.pythonhosted.org/packages/02/74/4332b5d6e34c63d4df8e8eab2249e74c05513b1477757463f7fdca99e9be/pendulum-3.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:006758e2125da2e624493324dfd5d7d1b02b0c44bc39358e18bf0f66d0767f5f", size = 253089, upload-time = "2025-04-19T14:01:41.171Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1f/af928ba4aa403dac9569f787adcf024005e7654433d71f7a84e608716837/pendulum-3.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:28658b0baf4b30eb31d096a375983cfed033e60c0a7bbe94fa23f06cd779b50b", size = 336209, upload-time = "2025-04-19T14:01:42.775Z" }, + { url = "https://files.pythonhosted.org/packages/b6/16/b010643007ba964c397da7fa622924423883c1bbff1a53f9d1022cd7f024/pendulum-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b114dcb99ce511cb8f5495c7b6f0056b2c3dba444ef1ea6e48030d7371bd531a", size = 323132, upload-time = "2025-04-19T14:01:44.577Z" }, + { url = "https://files.pythonhosted.org/packages/64/19/c3c47aeecb5d9bceb0e89faafd800d39809b696c5b7bba8ec8370ad5052c/pendulum-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2404a6a54c80252ea393291f0b7f35525a61abae3d795407f34e118a8f133a18", size = 341509, upload-time = "2025-04-19T14:01:46.084Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/c06921ff6b860ff7e62e70b8e5d4dc70e36f5abb66d168bd64d51760bc4e/pendulum-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d06999790d9ee9962a1627e469f98568bf7ad1085553fa3c30ed08b3944a14d7", size = 378674, upload-time = "2025-04-19T14:01:47.727Z" }, + { url = "https://files.pythonhosted.org/packages/62/0b/a43953b9eba11e82612b033ac5133f716f1b76b6108a65da6f408b3cc016/pendulum-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94751c52f6b7c306734d1044c2c6067a474237e1e5afa2f665d1fbcbbbcf24b3", size = 436133, upload-time = "2025-04-19T14:01:49.126Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a0/ec3d70b3b96e23ae1d039f132af35e17704c22a8250d1887aaefea4d78a6/pendulum-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5553ac27be05e997ec26d7f004cf72788f4ce11fe60bb80dda604a64055b29d0", size = 351232, upload-time = "2025-04-19T14:01:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/f4/97/aba23f1716b82f6951ba2b1c9178a2d107d1e66c102762a9bf19988547ea/pendulum-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f8dee234ca6142bf0514368d01a72945a44685aaa2fc4c14c98d09da9437b620", size = 521563, upload-time = "2025-04-19T14:01:51.9Z" }, + { url = "https://files.pythonhosted.org/packages/01/33/2c0d5216cc53d16db0c4b3d510f141ee0a540937f8675948541190fbd48b/pendulum-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7378084fe54faab4ee481897a00b710876f2e901ded6221671e827a253e643f2", size = 523221, upload-time = "2025-04-19T14:01:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/51/89/8de955c339c31aeae77fd86d3225509b998c81875e9dba28cb88b8cbf4b3/pendulum-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8539db7ae2c8da430ac2515079e288948c8ebf7eb1edd3e8281b5cdf433040d6", size = 260501, upload-time = "2025-04-19T14:01:54.749Z" }, + { url = "https://files.pythonhosted.org/packages/15/c3/226a3837363e94f8722461848feec18bfdd7d5172564d53aa3c3397ff01e/pendulum-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:1ce26a608e1f7387cd393fba2a129507c4900958d4f47b90757ec17656856571", size = 253087, upload-time = "2025-04-19T14:01:55.998Z" }, + { url = "https://files.pythonhosted.org/packages/6e/23/e98758924d1b3aac11a626268eabf7f3cf177e7837c28d47bf84c64532d0/pendulum-3.1.0-py3-none-any.whl", hash = "sha256:f9178c2a8e291758ade1e8dd6371b1d26d08371b4c7730a6e9a3ef8b16ebae0f", size = 111799, upload-time = "2025-04-19T14:02:34.739Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prefect" +version = "3.4.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiosqlite" }, + { name = "alembic" }, + { name = "anyio" }, + { name = "apprise" }, + { name = "asgi-lifespan" }, + { name = "asyncpg" }, + { name = "cachetools" }, + { name = "click" }, + { name = "cloudpickle" }, + { name = "coolname" }, + { name = "cryptography" }, + { name = "dateparser" }, + { name = "docker" }, + { name = "exceptiongroup" }, + { name = "fastapi" }, + { name = "fsspec" }, + { name = "graphviz" }, + { name = "griffe" }, + { name = "httpcore" }, + { name = "httpx", extra = ["http2"] }, + { name = "humanize" }, + { name = "jinja2" }, + { name = "jinja2-humanize-extension" }, + { name = "jsonpatch" }, + { name = "jsonschema" }, + { name = "opentelemetry-api" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pendulum", marker = "python_full_version < '3.13'" }, + { name = "prometheus-client" }, + { name = "pydantic" }, + { name = "pydantic-core" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, + { name = "python-dateutil" }, + { name = "python-slugify" }, + { name = "python-socks" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "readchar" }, + { name = "rfc3339-validator" }, + { name = "rich" }, + { name = "ruamel-yaml" }, + { name = "semver" }, + { name = "sniffio" }, + { name = "sqlalchemy", extra = ["asyncio"] }, + { name = "toml" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uv" }, + { name = "uvicorn" }, + { name = "websockets" }, + { name = "whenever", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f6/e581bb9e43b43f212e08879e522a60e785b43b2678037b2e4bc9f8661594/prefect-3.4.18.tar.gz", hash = "sha256:04a5af7b5d0fcd1202315e32d46f6067dcf912bfd58d3ed113c74c0f58abd1cf", size = 5599981, upload-time = "2025-09-12T16:22:02.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/f9/c5b61846b8fb33541c07e08170504c46e44a0ed45ad2b649fa15d7af11ec/prefect-3.4.18-py3-none-any.whl", hash = "sha256:a98824b91eb8de8d4fac84bd1ef08ce64564b1904b89f4c55059d30523b533ed", size = 6112304, upload-time = "2025-09-12T16:21:59.394Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/cf/40dde0a2be27cc1eb41e333d1a674a74ce8b8b0457269cc640fd42b07cf7/prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28", size = 69746, upload-time = "2025-06-02T14:29:01.152Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/ae/ec06af4fe3ee72d16973474f122541746196aaa16cea6f66d18b963c6177/prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094", size = 58694, upload-time = "2025-06-02T14:29:00.068Z" }, +] + +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, + { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, + { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "protobuf" +version = "6.32.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, + { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, + { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, + { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, + { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, + { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, + { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, +] + +[[package]] +name = "pydantic-extra-types" +version = "2.10.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/ba/4178111ec4116c54e1dc7ecd2a1ff8f54256cdbd250e576882911e8f710a/pydantic_extra_types-2.10.5.tar.gz", hash = "sha256:1dcfa2c0cf741a422f088e0dbb4690e7bfadaaf050da3d6f80d6c3cf58a2bad8", size = 138429, upload-time = "2025-06-02T09:31:52.713Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/1a/5f4fd9e7285f10c44095a4f9fe17d0f358d1702a7c74a9278c794e8a7537/pydantic_extra_types-2.10.5-py3-none-any.whl", hash = "sha256:b60c4e23d573a69a4f1a16dd92888ecc0ef34fb0e655b4f305530377fa70e7a8", size = 38315, upload-time = "2025-06-02T09:31:51.229Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + +[[package]] +name = "python-socks" +version = "2.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/fb/49fc4c3d61dbc8404879bed6c94c0595e654951ac9145645b057c4883966/python_socks-2.7.2.tar.gz", hash = "sha256:4c845d4700352bc7e7382f302dfc6baf0af0de34d2a6d70ba356b2539d4dbb62", size = 229950, upload-time = "2025-08-01T06:47:05.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/e6/1fdebffa733e79e67b43ee8930e4e5049eb51eae3608caeafc83518798aa/python_socks-2.7.2-py3-none-any.whl", hash = "sha256:d311aefbacc0ddfaa1fa1c32096c436d4fe75b899c24d78e677e1b0623c52c48", size = 55048, upload-time = "2025-08-01T06:47:03.734Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "readchar" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/f8/8657b8cbb4ebeabfbdf991ac40eca8a1d1bd012011bd44ad1ed10f5cb494/readchar-4.2.1.tar.gz", hash = "sha256:91ce3faf07688de14d800592951e5575e9c7a3213738ed01d394dcc949b79adb", size = 9685, upload-time = "2024-11-04T18:28:07.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/10/e4b1e0e5b6b6745c8098c275b69bc9d73e9542d5c7da4f137542b499ed44/readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77", size = 9350, upload-time = "2024-11-04T18:28:02.859Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "regex" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/5a/4c63457fbcaf19d138d72b2e9b39405954f98c0349b31c601bfcb151582c/regex-2025.9.1.tar.gz", hash = "sha256:88ac07b38d20b54d79e704e38aa3bd2c0f8027432164226bdee201a1c0c9c9ff", size = 400852, upload-time = "2025-09-01T22:10:10.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/4d/f741543c0c59f96c6625bc6c11fea1da2e378b7d293ffff6f318edc0ce14/regex-2025.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e5bcf112b09bfd3646e4db6bf2e598534a17d502b0c01ea6550ba4eca780c5e6", size = 484811, upload-time = "2025-09-01T22:08:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bd/27e73e92635b6fbd51afc26a414a3133243c662949cd1cda677fe7bb09bd/regex-2025.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:67a0295a3c31d675a9ee0238d20238ff10a9a2fdb7a1323c798fc7029578b15c", size = 288977, upload-time = "2025-09-01T22:08:14.499Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7d/7dc0c6efc8bc93cd6e9b947581f5fde8a5dbaa0af7c4ec818c5729fdc807/regex-2025.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea8267fbadc7d4bd7c1301a50e85c2ff0de293ff9452a1a9f8d82c6cafe38179", size = 286606, upload-time = "2025-09-01T22:08:15.881Z" }, + { url = "https://files.pythonhosted.org/packages/d1/01/9b5c6dd394f97c8f2c12f6e8f96879c9ac27292a718903faf2e27a0c09f6/regex-2025.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aeff21de7214d15e928fb5ce757f9495214367ba62875100d4c18d293750cc1", size = 792436, upload-time = "2025-09-01T22:08:17.38Z" }, + { url = "https://files.pythonhosted.org/packages/fc/24/b7430cfc6ee34bbb3db6ff933beb5e7692e5cc81e8f6f4da63d353566fb0/regex-2025.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d89f1bbbbbc0885e1c230f7770d5e98f4f00b0ee85688c871d10df8b184a6323", size = 858705, upload-time = "2025-09-01T22:08:19.037Z" }, + { url = "https://files.pythonhosted.org/packages/d6/98/155f914b4ea6ae012663188545c4f5216c11926d09b817127639d618b003/regex-2025.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca3affe8ddea498ba9d294ab05f5f2d3b5ad5d515bc0d4a9016dd592a03afe52", size = 905881, upload-time = "2025-09-01T22:08:20.377Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/a470e7bc8259c40429afb6d6a517b40c03f2f3e455c44a01abc483a1c512/regex-2025.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91892a7a9f0a980e4c2c85dd19bc14de2b219a3a8867c4b5664b9f972dcc0c78", size = 798968, upload-time = "2025-09-01T22:08:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/33f6fec4d41449fea5f62fdf5e46d668a1c046730a7f4ed9f478331a8e3a/regex-2025.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e1cb40406f4ae862710615f9f636c1e030fd6e6abe0e0f65f6a695a2721440c6", size = 781884, upload-time = "2025-09-01T22:08:23.832Z" }, + { url = "https://files.pythonhosted.org/packages/42/de/2b45f36ab20da14eedddf5009d370625bc5942d9953fa7e5037a32d66843/regex-2025.9.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94f6cff6f7e2149c7e6499a6ecd4695379eeda8ccbccb9726e8149f2fe382e92", size = 852935, upload-time = "2025-09-01T22:08:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f9/878f4fc92c87e125e27aed0f8ee0d1eced9b541f404b048f66f79914475a/regex-2025.9.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6c0226fb322b82709e78c49cc33484206647f8a39954d7e9de1567f5399becd0", size = 844340, upload-time = "2025-09-01T22:08:27.141Z" }, + { url = "https://files.pythonhosted.org/packages/90/c2/5b6f2bce6ece5f8427c718c085eca0de4bbb4db59f54db77aa6557aef3e9/regex-2025.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a12f59c7c380b4fcf7516e9cbb126f95b7a9518902bcf4a852423ff1dcd03e6a", size = 787238, upload-time = "2025-09-01T22:08:28.75Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/1ef1081c831c5b611f6f55f6302166cfa1bc9574017410ba5595353f846a/regex-2025.9.1-cp311-cp311-win32.whl", hash = "sha256:49865e78d147a7a4f143064488da5d549be6bfc3f2579e5044cac61f5c92edd4", size = 264118, upload-time = "2025-09-01T22:08:30.388Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e0/8adc550d7169df1d6b9be8ff6019cda5291054a0107760c2f30788b6195f/regex-2025.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:d34b901f6f2f02ef60f4ad3855d3a02378c65b094efc4b80388a3aeb700a5de7", size = 276151, upload-time = "2025-09-01T22:08:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/cb/bd/46fef29341396d955066e55384fb93b0be7d64693842bf4a9a398db6e555/regex-2025.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:47d7c2dab7e0b95b95fd580087b6ae196039d62306a592fa4e162e49004b6299", size = 268460, upload-time = "2025-09-01T22:08:33.281Z" }, + { url = "https://files.pythonhosted.org/packages/39/ef/a0372febc5a1d44c1be75f35d7e5aff40c659ecde864d7fa10e138f75e74/regex-2025.9.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84a25164bd8dcfa9f11c53f561ae9766e506e580b70279d05a7946510bdd6f6a", size = 486317, upload-time = "2025-09-01T22:08:34.529Z" }, + { url = "https://files.pythonhosted.org/packages/b5/25/d64543fb7eb41a1024786d518cc57faf1ce64aa6e9ddba097675a0c2f1d2/regex-2025.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:645e88a73861c64c1af558dd12294fb4e67b5c1eae0096a60d7d8a2143a611c7", size = 289698, upload-time = "2025-09-01T22:08:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dc/fbf31fc60be317bd9f6f87daa40a8a9669b3b392aa8fe4313df0a39d0722/regex-2025.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10a450cba5cd5409526ee1d4449f42aad38dd83ac6948cbd6d7f71ca7018f7db", size = 287242, upload-time = "2025-09-01T22:08:37.794Z" }, + { url = "https://files.pythonhosted.org/packages/0f/74/f933a607a538f785da5021acf5323961b4620972e2c2f1f39b6af4b71db7/regex-2025.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9dc5991592933a4192c166eeb67b29d9234f9c86344481173d1bc52f73a7104", size = 797441, upload-time = "2025-09-01T22:08:39.108Z" }, + { url = "https://files.pythonhosted.org/packages/89/d0/71fc49b4f20e31e97f199348b8c4d6e613e7b6a54a90eb1b090c2b8496d7/regex-2025.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a32291add816961aab472f4fad344c92871a2ee33c6c219b6598e98c1f0108f2", size = 862654, upload-time = "2025-09-01T22:08:40.586Z" }, + { url = "https://files.pythonhosted.org/packages/59/05/984edce1411a5685ba9abbe10d42cdd9450aab4a022271f9585539788150/regex-2025.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:588c161a68a383478e27442a678e3b197b13c5ba51dbba40c1ccb8c4c7bee9e9", size = 910862, upload-time = "2025-09-01T22:08:42.416Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/5c891bb5fe0691cc1bad336e3a94b9097fbcf9707ec8ddc1dce9f0397289/regex-2025.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47829ffaf652f30d579534da9085fe30c171fa2a6744a93d52ef7195dc38218b", size = 801991, upload-time = "2025-09-01T22:08:44.072Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ae/fd10d6ad179910f7a1b3e0a7fde1ef8bb65e738e8ac4fd6ecff3f52252e4/regex-2025.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e978e5a35b293ea43f140c92a3269b6ab13fe0a2bf8a881f7ac740f5a6ade85", size = 786651, upload-time = "2025-09-01T22:08:46.079Z" }, + { url = "https://files.pythonhosted.org/packages/30/cf/9d686b07bbc5bf94c879cc168db92542d6bc9fb67088d03479fef09ba9d3/regex-2025.9.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf09903e72411f4bf3ac1eddd624ecfd423f14b2e4bf1c8b547b72f248b7bf7", size = 856556, upload-time = "2025-09-01T22:08:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/91/9d/302f8a29bb8a49528abbab2d357a793e2a59b645c54deae0050f8474785b/regex-2025.9.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d016b0f77be63e49613c9e26aaf4a242f196cd3d7a4f15898f5f0ab55c9b24d2", size = 849001, upload-time = "2025-09-01T22:08:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/93/fa/b4c6dbdedc85ef4caec54c817cd5f4418dbfa2453214119f2538082bf666/regex-2025.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:656563e620de6908cd1c9d4f7b9e0777e3341ca7db9d4383bcaa44709c90281e", size = 788138, upload-time = "2025-09-01T22:08:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1b/91ee17a3cbf87f81e8c110399279d0e57f33405468f6e70809100f2ff7d8/regex-2025.9.1-cp312-cp312-win32.whl", hash = "sha256:df33f4ef07b68f7ab637b1dbd70accbf42ef0021c201660656601e8a9835de45", size = 264524, upload-time = "2025-09-01T22:08:53.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/28/6ba31cce05b0f1ec6b787921903f83bd0acf8efde55219435572af83c350/regex-2025.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5aba22dfbc60cda7c0853516104724dc904caa2db55f2c3e6e984eb858d3edf3", size = 275489, upload-time = "2025-09-01T22:08:55.037Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ed/ea49f324db00196e9ef7fe00dd13c6164d5173dd0f1bbe495e61bb1fb09d/regex-2025.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:ec1efb4c25e1849c2685fa95da44bfde1b28c62d356f9c8d861d4dad89ed56e9", size = 268589, upload-time = "2025-09-01T22:08:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/98/25/b2959ce90c6138c5142fe5264ee1f9b71a0c502ca4c7959302a749407c79/regex-2025.9.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc6834727d1b98d710a63e6c823edf6ffbf5792eba35d3fa119531349d4142ef", size = 485932, upload-time = "2025-09-01T22:08:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/49/2e/6507a2a85f3f2be6643438b7bd976e67ad73223692d6988eb1ff444106d3/regex-2025.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c3dc05b6d579875719bccc5f3037b4dc80433d64e94681a0061845bd8863c025", size = 289568, upload-time = "2025-09-01T22:08:59.258Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d8/de4a4b57215d99868f1640e062a7907e185ec7476b4b689e2345487c1ff4/regex-2025.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22213527df4c985ec4a729b055a8306272d41d2f45908d7bacb79be0fa7a75ad", size = 286984, upload-time = "2025-09-01T22:09:00.835Z" }, + { url = "https://files.pythonhosted.org/packages/03/15/e8cb403403a57ed316e80661db0e54d7aa2efcd85cb6156f33cc18746922/regex-2025.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e3f6e3c5a5a1adc3f7ea1b5aec89abfc2f4fbfba55dafb4343cd1d084f715b2", size = 797514, upload-time = "2025-09-01T22:09:02.538Z" }, + { url = "https://files.pythonhosted.org/packages/e4/26/2446f2b9585fed61faaa7e2bbce3aca7dd8df6554c32addee4c4caecf24a/regex-2025.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcb89c02a0d6c2bec9b0bb2d8c78782699afe8434493bfa6b4021cc51503f249", size = 862586, upload-time = "2025-09-01T22:09:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b8/82ffbe9c0992c31bbe6ae1c4b4e21269a5df2559102b90543c9b56724c3c/regex-2025.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0e2f95413eb0c651cd1516a670036315b91b71767af83bc8525350d4375ccba", size = 910815, upload-time = "2025-09-01T22:09:05.978Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d8/7303ea38911759c1ee30cc5bc623ee85d3196b733c51fd6703c34290a8d9/regex-2025.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a41dc039e1c97d3c2ed3e26523f748e58c4de3ea7a31f95e1cf9ff973fff5a", size = 802042, upload-time = "2025-09-01T22:09:07.865Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0e/6ad51a55ed4b5af512bb3299a05d33309bda1c1d1e1808fa869a0bed31bc/regex-2025.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f0b4258b161094f66857a26ee938d3fe7b8a5063861e44571215c44fbf0e5df", size = 786764, upload-time = "2025-09-01T22:09:09.362Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d5/394e3ffae6baa5a9217bbd14d96e0e5da47bb069d0dbb8278e2681a2b938/regex-2025.9.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bf70e18ac390e6977ea7e56f921768002cb0fa359c4199606c7219854ae332e0", size = 856557, upload-time = "2025-09-01T22:09:11.129Z" }, + { url = "https://files.pythonhosted.org/packages/cd/80/b288d3910c41194ad081b9fb4b371b76b0bbfdce93e7709fc98df27b37dc/regex-2025.9.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b84036511e1d2bb0a4ff1aec26951caa2dea8772b223c9e8a19ed8885b32dbac", size = 849108, upload-time = "2025-09-01T22:09:12.877Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cd/5ec76bf626d0d5abdc277b7a1734696f5f3d14fbb4a3e2540665bc305d85/regex-2025.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e05dcdfe224047f2a59e70408274c325d019aad96227ab959403ba7d58d2d7", size = 788201, upload-time = "2025-09-01T22:09:14.561Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/674672f3fdead107565a2499f3007788b878188acec6d42bc141c5366c2c/regex-2025.9.1-cp313-cp313-win32.whl", hash = "sha256:3b9a62107a7441b81ca98261808fed30ae36ba06c8b7ee435308806bd53c1ed8", size = 264508, upload-time = "2025-09-01T22:09:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/83/ad/931134539515eb64ce36c24457a98b83c1b2e2d45adf3254b94df3735a76/regex-2025.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:b38afecc10c177eb34cfae68d669d5161880849ba70c05cbfbe409f08cc939d7", size = 275469, upload-time = "2025-09-01T22:09:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/24/8c/96d34e61c0e4e9248836bf86d69cb224fd222f270fa9045b24e218b65604/regex-2025.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:ec329890ad5e7ed9fc292858554d28d58d56bf62cf964faf0aa57964b21155a0", size = 268586, upload-time = "2025-09-01T22:09:18.948Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/453cbea5323b049181ec6344a803777914074b9726c9c5dc76749966d12d/regex-2025.9.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:72fb7a016467d364546f22b5ae86c45680a4e0de6b2a6f67441d22172ff641f1", size = 486111, upload-time = "2025-09-01T22:09:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0e/92577f197bd2f7652c5e2857f399936c1876978474ecc5b068c6d8a79c86/regex-2025.9.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c9527fa74eba53f98ad86be2ba003b3ebe97e94b6eb2b916b31b5f055622ef03", size = 289520, upload-time = "2025-09-01T22:09:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/af/c6/b472398116cca7ea5a6c4d5ccd0fc543f7fd2492cb0c48d2852a11972f73/regex-2025.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c905d925d194c83a63f92422af7544ec188301451b292c8b487f0543726107ca", size = 287215, upload-time = "2025-09-01T22:09:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/cf/11/f12ecb0cf9ca792a32bb92f758589a84149017467a544f2f6bfb45c0356d/regex-2025.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74df7c74a63adcad314426b1f4ea6054a5ab25d05b0244f0c07ff9ce640fa597", size = 797855, upload-time = "2025-09-01T22:09:25.197Z" }, + { url = "https://files.pythonhosted.org/packages/46/88/bbb848f719a540fb5997e71310f16f0b33a92c5d4b4d72d4311487fff2a3/regex-2025.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4f6e935e98ea48c7a2e8be44494de337b57a204470e7f9c9c42f912c414cd6f5", size = 863363, upload-time = "2025-09-01T22:09:26.705Z" }, + { url = "https://files.pythonhosted.org/packages/54/a9/2321eb3e2838f575a78d48e03c1e83ea61bd08b74b7ebbdeca8abc50fc25/regex-2025.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4a62d033cd9ebefc7c5e466731a508dfabee827d80b13f455de68a50d3c2543d", size = 910202, upload-time = "2025-09-01T22:09:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/07/d1d70835d7d11b7e126181f316f7213c4572ecf5c5c97bdbb969fb1f38a2/regex-2025.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef971ebf2b93bdc88d8337238be4dfb851cc97ed6808eb04870ef67589415171", size = 801808, upload-time = "2025-09-01T22:09:30.733Z" }, + { url = "https://files.pythonhosted.org/packages/13/d1/29e4d1bed514ef2bf3a4ead3cb8bb88ca8af94130239a4e68aa765c35b1c/regex-2025.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d936a1db208bdca0eca1f2bb2c1ba1d8370b226785c1e6db76e32a228ffd0ad5", size = 786824, upload-time = "2025-09-01T22:09:32.61Z" }, + { url = "https://files.pythonhosted.org/packages/33/27/20d8ccb1bee460faaa851e6e7cc4cfe852a42b70caa1dca22721ba19f02f/regex-2025.9.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:7e786d9e4469698fc63815b8de08a89165a0aa851720eb99f5e0ea9d51dd2b6a", size = 857406, upload-time = "2025-09-01T22:09:34.117Z" }, + { url = "https://files.pythonhosted.org/packages/74/fe/60c6132262dc36430d51e0c46c49927d113d3a38c1aba6a26c7744c84cf3/regex-2025.9.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6b81d7dbc5466ad2c57ce3a0ddb717858fe1a29535c8866f8514d785fdb9fc5b", size = 848593, upload-time = "2025-09-01T22:09:35.598Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ae/2d4ff915622fabbef1af28387bf71e7f2f4944a348b8460d061e85e29bf0/regex-2025.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cd4890e184a6feb0ef195338a6ce68906a8903a0f2eb7e0ab727dbc0a3156273", size = 787951, upload-time = "2025-09-01T22:09:37.139Z" }, + { url = "https://files.pythonhosted.org/packages/85/37/dc127703a9e715a284cc2f7dbdd8a9776fd813c85c126eddbcbdd1ca5fec/regex-2025.9.1-cp314-cp314-win32.whl", hash = "sha256:34679a86230e46164c9e0396b56cab13c0505972343880b9e705083cc5b8ec86", size = 269833, upload-time = "2025-09-01T22:09:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/4bed4d3d0570e16771defd5f8f15f7ea2311edcbe91077436d6908956c4a/regex-2025.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:a1196e530a6bfa5f4bde029ac5b0295a6ecfaaffbfffede4bbaf4061d9455b70", size = 278742, upload-time = "2025-09-01T22:09:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3e/7d7ac6fd085023312421e0d69dfabdfb28e116e513fadbe9afe710c01893/regex-2025.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:f46d525934871ea772930e997d577d48c6983e50f206ff7b66d4ac5f8941e993", size = 271860, upload-time = "2025-09-01T22:09:42.413Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, + { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, + { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, + { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, + { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, + { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, + { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, + { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, + { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.14' and platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/db/f3950f5e5031b618aae9f423a39bf81a55c148aecd15a34527898e752cf4/ruamel.yaml-0.18.15.tar.gz", hash = "sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700", size = 146865, upload-time = "2025-08-19T11:15:10.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/e5/f2a0621f1781b76a38194acae72f01e37b1941470407345b6e8653ad7640/ruamel.yaml-0.18.15-py3-none-any.whl", hash = "sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701", size = 119702, upload-time = "2025-08-19T11:15:07.696Z" }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315, upload-time = "2024-10-20T10:10:56.22Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224, upload-time = "2024-10-20T10:12:45.162Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480, upload-time = "2024-10-20T10:12:46.758Z" }, + { url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068, upload-time = "2024-10-20T10:12:48.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012, upload-time = "2024-10-20T10:12:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352, upload-time = "2024-10-21T11:26:41.438Z" }, + { url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344, upload-time = "2024-10-21T11:26:43.62Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498, upload-time = "2024-12-11T19:58:15.592Z" }, + { url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205, upload-time = "2024-10-20T10:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185, upload-time = "2024-10-20T10:12:54.652Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433, upload-time = "2024-10-20T10:12:55.657Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362, upload-time = "2024-10-20T10:12:57.155Z" }, + { url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118, upload-time = "2024-10-20T10:12:58.501Z" }, + { url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497, upload-time = "2024-10-20T10:13:00.211Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042, upload-time = "2024-10-21T11:26:46.038Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831, upload-time = "2024-10-21T11:26:47.487Z" }, + { url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692, upload-time = "2024-12-11T19:58:17.252Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777, upload-time = "2024-10-20T10:13:01.395Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523, upload-time = "2024-10-20T10:13:02.768Z" }, + { url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011, upload-time = "2024-10-20T10:13:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488, upload-time = "2024-10-20T10:13:05.906Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066, upload-time = "2024-10-20T10:13:07.26Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785, upload-time = "2024-10-20T10:13:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017, upload-time = "2024-10-21T11:26:48.866Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270, upload-time = "2024-10-21T11:26:50.213Z" }, + { url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059, upload-time = "2024-12-11T19:58:18.846Z" }, + { url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583, upload-time = "2024-10-20T10:13:09.658Z" }, + { url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190, upload-time = "2024-10-20T10:13:10.66Z" }, +] + +[[package]] +name = "ruff" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" }, + { url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" }, + { url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" }, + { url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" }, + { url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" }, + { url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/77/fa7189fe44114658002566c6fe443d3ed0ec1fa782feb72af6ef7fbe98e7/sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29", size = 2136472, upload-time = "2025-08-11T15:52:21.789Z" }, + { url = "https://files.pythonhosted.org/packages/99/ea/92ac27f2fbc2e6c1766bb807084ca455265707e041ba027c09c17d697867/sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631", size = 2126535, upload-time = "2025-08-11T15:52:23.109Z" }, + { url = "https://files.pythonhosted.org/packages/94/12/536ede80163e295dc57fff69724caf68f91bb40578b6ac6583a293534849/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685", size = 3297521, upload-time = "2025-08-11T15:50:33.536Z" }, + { url = "https://files.pythonhosted.org/packages/03/b5/cacf432e6f1fc9d156eca0560ac61d4355d2181e751ba8c0cd9cb232c8c1/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca", size = 3297343, upload-time = "2025-08-11T15:57:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/d4c9b526f18457667de4c024ffbc3a0920c34237b9e9dd298e44c7c00ee5/sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d", size = 3232113, upload-time = "2025-08-11T15:50:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/aa/79/c0121b12b1b114e2c8a10ea297a8a6d5367bc59081b2be896815154b1163/sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3", size = 3258240, upload-time = "2025-08-11T15:57:52.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/a2f9be96fb382f3ba027ad42f00dbe30fdb6ba28cda5f11412eee346bec5/sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921", size = 2101248, upload-time = "2025-08-11T15:55:01.855Z" }, + { url = "https://files.pythonhosted.org/packages/ee/13/744a32ebe3b4a7a9c7ea4e57babae7aa22070d47acf330d8e5a1359607f1/sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8", size = 2126109, upload-time = "2025-08-11T15:55:04.092Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/20c78f1081446095450bdc6ee6cc10045fce67a8e003a5876b6eaafc5cc4/sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24", size = 2134891, upload-time = "2025-08-11T15:51:13.019Z" }, + { url = "https://files.pythonhosted.org/packages/45/0a/3d89034ae62b200b4396f0f95319f7d86e9945ee64d2343dcad857150fa2/sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83", size = 2123061, upload-time = "2025-08-11T15:51:14.319Z" }, + { url = "https://files.pythonhosted.org/packages/cb/10/2711f7ff1805919221ad5bee205971254845c069ee2e7036847103ca1e4c/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9", size = 3320384, upload-time = "2025-08-11T15:52:35.088Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0e/3d155e264d2ed2778484006ef04647bc63f55b3e2d12e6a4f787747b5900/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48", size = 3329648, upload-time = "2025-08-11T15:56:34.153Z" }, + { url = "https://files.pythonhosted.org/packages/5b/81/635100fb19725c931622c673900da5efb1595c96ff5b441e07e3dd61f2be/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687", size = 3258030, upload-time = "2025-08-11T15:52:36.933Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/a99302716d62b4965fded12520c1cbb189f99b17a6d8cf77611d21442e47/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe", size = 3294469, upload-time = "2025-08-11T15:56:35.553Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a2/3a11b06715149bf3310b55a98b5c1e84a42cfb949a7b800bc75cb4e33abc/sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d", size = 2098906, upload-time = "2025-08-11T15:55:00.645Z" }, + { url = "https://files.pythonhosted.org/packages/bc/09/405c915a974814b90aa591280623adc6ad6b322f61fd5cff80aeaef216c9/sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a", size = 2126260, upload-time = "2025-08-11T15:55:02.965Z" }, + { url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" }, + { url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, +] + +[package.optional-dependencies] +asyncio = [ + { name = "greenlet" }, +] + +[[package]] +name = "starlette" +version = "0.47.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/b9/cc3017f9a9c9b6e27c5106cc10cc7904653c3eec0729793aec10479dd669/starlette-0.47.3.tar.gz", hash = "sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9", size = 2584144, upload-time = "2025-08-24T13:36:42.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/901cfa59aaa5b30a99e16876f11abe38b59a1a2c51ffb3d7142bb6089069/starlette-0.47.3-py3-none-any.whl", hash = "sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51", size = 72991, upload-time = "2025-08-24T13:36:40.887Z" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "textual" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", extra = ["linkify", "plugins"] }, + { name = "platformdirs" }, + { name = "pygments" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/44/4b524b2f06e0fa6c4ede56a4e9af5edd5f3f83cf2eea5cb4fd0ce5bbe063/textual-6.1.0.tar.gz", hash = "sha256:cc89826ca2146c645563259320ca4ddc75d183c77afb7d58acdd46849df9144d", size = 1564786, upload-time = "2025-09-02T11:42:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/43/f91e041f239b54399310a99041faf33beae9a6e628671471d0fcd6276af4/textual-6.1.0-py3-none-any.whl", hash = "sha256:a3f5e6710404fcdc6385385db894699282dccf2ad50103cebc677403c1baadd5", size = 707840, upload-time = "2025-09-02T11:42:32.746Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typer" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/e8/2a73ccf9874ec4c7638f172efc8972ceab13a0e3480b389d6ed822f7a822/typer-0.17.4.tar.gz", hash = "sha256:b77dc07d849312fd2bb5e7f20a7af8985c7ec360c45b051ed5412f64d8dc1580", size = 103734, upload-time = "2025-09-05T18:14:40.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl", hash = "sha256:015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824", size = 46643, upload-time = "2025-09-05T18:14:39.166Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "uc-micro-py" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uv" +version = "0.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/4c/c270c6b8ed3e8c7fe38ea0b99df9eff09c332421b93d55a158371f75220e/uv-0.8.17.tar.gz", hash = "sha256:2afd4525a53c8ab3a11a5a15093c503d27da67e76257a649b05e4f0bc2ebb5ae", size = 3615060, upload-time = "2025-09-10T21:51:25.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/7d/bbaa45c88b2c91e02714a8a5c9e787c47e4898bddfdd268569163492ba45/uv-0.8.17-py3-none-linux_armv6l.whl", hash = "sha256:c51c9633ca93ef63c07df2443941e6264efd2819cc9faabfd9fe11899c6a0d6a", size = 20242144, upload-time = "2025-09-10T21:50:18.081Z" }, + { url = "https://files.pythonhosted.org/packages/65/34/609b72034df0c62bcfb0c0ad4b11e2b55e537c0f0817588b5337d3dcca71/uv-0.8.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c28fba6d7bb5c34ade2c8da5000faebe8425a287f42a043ca01ceb24ebc81590", size = 19363081, upload-time = "2025-09-10T21:50:22.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/9417df48f0c18a9d54c2444096e03f2f56a3534c5b869f50ac620729cbc8/uv-0.8.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b009f1ec9e28de00f76814ad66e35aaae82c98a0f24015de51943dcd1c2a1895", size = 17943513, upload-time = "2025-09-10T21:50:25.824Z" }, + { url = "https://files.pythonhosted.org/packages/63/1c/14fd54c852fd592a2b5da4b7960f3bf4a15c7e51eb20eaddabe8c8cca32d/uv-0.8.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:84d56ae50ca71aec032577adf9737974554a82a94e52cee57722745656c1d383", size = 19507222, upload-time = "2025-09-10T21:50:29.237Z" }, + { url = "https://files.pythonhosted.org/packages/be/47/f6a68cc310feca37c965bcbd57eb999e023d35eaeda9c9759867bf3ed232/uv-0.8.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:85c2140f8553b9a4387a7395dc30cd151ef94046785fe8b198f13f2c380fb39b", size = 19865652, upload-time = "2025-09-10T21:50:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/fdeb2d4a2635a6927c6d549b07177bcaf6ce15bdef58e8253e75c1b70f54/uv-0.8.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2076119783e4a6d3c9e25638956cb123f0eabf4d7d407d9661cdf7f84818dcb9", size = 20831760, upload-time = "2025-09-10T21:50:37.803Z" }, + { url = "https://files.pythonhosted.org/packages/d0/4c/bd58b8a76015aa9ac49d6b4e1211ae1ca98a0aade0c49e1a5f645fb5cd38/uv-0.8.17-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:707a55660d302924fdbcb509e63dfec8842e19d35b69bcc17af76c25db15ad6f", size = 22209056, upload-time = "2025-09-10T21:50:41.749Z" }, + { url = "https://files.pythonhosted.org/packages/7e/2e/28f59c00a2ed6532502fb1e27da9394e505fb7b41cc0274475104b43561b/uv-0.8.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1824b76911a14aaa9eee65ad9e180e6a4d2d7c86826232c2f28ae86aee56ed0e", size = 21871684, upload-time = "2025-09-10T21:50:45.331Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1d/a8a4fc08de1f767316467e7a1989bb125734b7ed9cd98ce8969386a70653/uv-0.8.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb9b515cc813fb1b08f1e7592f76e437e2fb44945e53cde4fee11dee3b16d0c3", size = 21145154, upload-time = "2025-09-10T21:50:50.388Z" }, + { url = "https://files.pythonhosted.org/packages/8f/35/cb47d2d07a383c07b0e5043c6fe5555f0fd79683c6d7f9760222987c8be9/uv-0.8.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d30d02fb65193309fc12a20f9e1a9fab67f469d3e487a254ca1145fd06788f", size = 21106619, upload-time = "2025-09-10T21:50:54.5Z" }, + { url = "https://files.pythonhosted.org/packages/6e/93/c310f0153b9dfe79bdd7f7eaef6380a8545c8939dbfc4e6bdee8f3ee7050/uv-0.8.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3941cecd9a6a46d3d4505753912c9cf3e8ae5eea30b9d0813f3656210f8c5d01", size = 19777591, upload-time = "2025-09-10T21:50:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/971d3c84c2f09cf8df4536c33644e6b97e10a259d8630a0c1696c1fa6e94/uv-0.8.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:cd0ad366cfe4cbe9212bd660b5b9f3a827ff35a7601cefdac2d153bfc8079eb7", size = 20845039, upload-time = "2025-09-10T21:51:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/4a/29/8ad9038e75cb91f54b81cc933dd14fcfa92fa6f8706117d43d4251a8a662/uv-0.8.17-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:505854bc75c497b95d2c65590291dc820999a4a7d9dfab4f44a9434a6cff7b5f", size = 19820370, upload-time = "2025-09-10T21:51:04.616Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c9/fc8482d1e7dfe187c6e03dcefbac0db41a5dd72aa7b017c0f80f91a04444/uv-0.8.17-py3-none-musllinux_1_1_i686.whl", hash = "sha256:dc479f661da449df37d68b36fdffa641e89fb53ad38c16a5c9f98f3211785b63", size = 20289951, upload-time = "2025-09-10T21:51:08.605Z" }, + { url = "https://files.pythonhosted.org/packages/2d/84/ad878ed045f02aa973be46636c802d494f8270caf5ea8bd04b7bbc68aa23/uv-0.8.17-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a1d11cd805be6d137ffef4a8227905f87f459031c645ac5031c30a3bcd08abd6", size = 21234644, upload-time = "2025-09-10T21:51:12.429Z" }, + { url = "https://files.pythonhosted.org/packages/f8/03/3fa2641513922988e641050b3adbc87de527f44c2cc8328510703616be6a/uv-0.8.17-py3-none-win32.whl", hash = "sha256:d13a616eb0b2b33c7aa09746cc85860101d595655b58653f0b499af19f33467c", size = 19216757, upload-time = "2025-09-10T21:51:16.021Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c4/0082f437bac162ab95e5a3a389a184c122d45eb5593960aab92fdf80374b/uv-0.8.17-py3-none-win_amd64.whl", hash = "sha256:cf85b84b81b41d57a9b6eeded8473ec06ace8ee959ad0bb57e102b5ad023bd34", size = 21125811, upload-time = "2025-09-10T21:51:19.397Z" }, + { url = "https://files.pythonhosted.org/packages/50/a2/29f57b118b3492c9d5ab1a99ba4906e7d7f8b658881d31bc2c4408d64d07/uv-0.8.17-py3-none-win_arm64.whl", hash = "sha256:64d649a8c4c3732b05dc712544963b004cf733d95fdc5d26f43c5493553ff0a7", size = 19564631, upload-time = "2025-09-10T21:51:22.599Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "validators" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399, upload-time = "2025-05-01T05:42:06.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712, upload-time = "2025-05-01T05:42:04.203Z" }, +] + +[[package]] +name = "weaviate-client" +version = "4.16.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib" }, + { name = "deprecation" }, + { name = "grpcio" }, + { name = "httpx" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "validators" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/e4/6a0b1501645f17a851067fc7bd0d5b53dc9777f2818be9c43debe06eda19/weaviate_client-4.16.9.tar.gz", hash = "sha256:d461071f1ff5ebddd0fc697959628a1d8caa12af1da071401ef25583c3084eba", size = 766390, upload-time = "2025-08-20T15:00:03.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/1a/fc66f5f33961351c759d56453d18176849da8f64186c941183bb574b808b/weaviate_client-4.16.9-py3-none-any.whl", hash = "sha256:8b4adabaec0d513edef94c8c1de61c89a86eba3b63a4dc1acdfc9580e80199f4", size = 579098, upload-time = "2025-08-20T15:00:01.882Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "whenever" +version = "0.8.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "python_full_version >= '3.13' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/b6/0e871022a7a5ec9c80c3e19028c806a7a079b3e0aaa524e1a8ccfd52e6bf/whenever-0.8.8.tar.gz", hash = "sha256:d0674d410fbbcf495f6cca0f1f575279e402887d20e4c1ca7d11309cd41b8125", size = 235496, upload-time = "2025-07-24T20:59:42.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/3d/240aa3d1dc6627e633470449f2ac8e8179e8d5b4e6c41ca5e805c0776f68/whenever-0.8.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0cbdcba2df308f09d26db924459d31ee5c5bcb09e72a16bfb9fd7cdd05812920", size = 390101, upload-time = "2025-07-24T20:59:22.969Z" }, + { url = "https://files.pythonhosted.org/packages/87/f0/b4bcdd4d8edeec098d8acbdedb35c066559a51752e383c6a3f944d4e3703/whenever-0.8.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:943f1e4054afc664b79b44929569598513587978395ef159340253ed0bf73e6e", size = 374988, upload-time = "2025-07-24T20:59:15.677Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a6/be07090cedde0fd27be569012609b933eb22c49e651e59cbba96951691da/whenever-0.8.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc6fe69fe491751c91d9d5a11bd18e3e15fa6d0d294e661d71e9df7e8cee3f9e", size = 397112, upload-time = "2025-07-24T20:58:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/48/08/1d9c49c8f35afc6342eded722d11503f6b0105b66e4861d6776a05111960/whenever-0.8.8-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1f6b813e15f845f9f8be7d6226cafc5cbd89746289fb302ad40213f28d1911ec", size = 436670, upload-time = "2025-07-24T20:58:27.947Z" }, + { url = "https://files.pythonhosted.org/packages/d5/78/6c616a30a518afecd7135928b6ca741ab274ab2021b631a21724cd056ae9/whenever-0.8.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aea29c76221f5d0ccdbbaf806c3b181c1aba9efade5726d501ce26ebed70b692", size = 430732, upload-time = "2025-07-24T20:58:39.876Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/1ea65baee041b268ddd8352e3582bd6c3fb87c1f77329b44ac61ab59e22c/whenever-0.8.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f331f03bdce04d9709053d49dd982c54e54f21005580273f3564327b42fb242", size = 450804, upload-time = "2025-07-24T20:58:46.399Z" }, + { url = "https://files.pythonhosted.org/packages/3b/fb/cddfc11b0c36787b746052e3e4741d2b61bcdf5636582d9f57ad5ca0b0cc/whenever-0.8.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f98dc66db6016660897e0fa56904e75de35daeacac5a012bf4424ef4d567c5", size = 412014, upload-time = "2025-07-24T20:59:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/ff/57/3d79a393c66e3999408f746159ba977b3ef5494b95503b3a1ac687251969/whenever-0.8.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:182d4ed4df2f09f173e09c606a866bc324f2ae4a67145f391cb669e915c4b0f4", size = 450866, upload-time = "2025-07-24T20:58:51.841Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a8/8cff5c1eb4771b999f96e5a784a30db52433f69be56193cf146059eb4174/whenever-0.8.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56d3227e3212375f4e3de4c97a006de5e7973f1273b7aedcf69f7c883762ccc3", size = 574881, upload-time = "2025-07-24T20:58:22.024Z" }, + { url = "https://files.pythonhosted.org/packages/a5/04/f423acad21fa11cf0240b417e04d9af3b74eb58e15820e500229874c69e5/whenever-0.8.8-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c21a8ef5ad6d03d473d782444b2b99a0e65d48ab11951ce11858896aebaf459b", size = 699936, upload-time = "2025-07-24T20:58:34.252Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d5/0c2c8be9e2376417203d950ca77c2df2132018863cde7c73154a114e72b2/whenever-0.8.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:325ae450a6f476837497a4391e7ddc7353f3f944061a5f607f48b9fc3f5d98c0", size = 625557, upload-time = "2025-07-24T20:58:57.795Z" }, + { url = "https://files.pythonhosted.org/packages/29/ce/d17170b7487da4b2c95960499d14838197645cb879c0862dcb35a4edf953/whenever-0.8.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9f4118fd28090c914e8be577ffded3508e53adbbff39c17df03611d16c68c0b2", size = 583281, upload-time = "2025-07-24T20:59:09.875Z" }, + { url = "https://files.pythonhosted.org/packages/50/b8/83ba57d8e14c7769f00096aa8de4d8022a034e402ce780abaf56a678e9d4/whenever-0.8.8-cp311-cp311-win32.whl", hash = "sha256:cf4147a361595da9fa981f35e95f5acd58316137ad97d140630407794b280c75", size = 328290, upload-time = "2025-07-24T20:59:28.821Z" }, + { url = "https://files.pythonhosted.org/packages/af/74/44253162e1b25fe2b70bde217ef0cd76f7811484a9bbed00df769fabffb6/whenever-0.8.8-cp311-cp311-win_amd64.whl", hash = "sha256:9faeee933c9fd22df352c55e5a19ae3959187971ee3d2aecfdded2fd6f4a86e8", size = 320868, upload-time = "2025-07-24T20:59:35.631Z" }, + { url = "https://files.pythonhosted.org/packages/9c/67/562a89b70ed28bef984a45c022c320ceea6722054b8bb2d54ad089c87978/whenever-0.8.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:43a0a56b2b2bb6f821161fa4e0c077e24909d02241132f8aad47a5ad604f4239", size = 391248, upload-time = "2025-07-24T20:59:24.12Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2d/d10a544d536b6fcf807f06355cfc36298c14b584999947932afe16083f53/whenever-0.8.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82988062e7f8695d1d567e9d259ebeb90f0d43c1b1e34d12019019887375709e", size = 375095, upload-time = "2025-07-24T20:59:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/2d/16/1844839a6db4de85d8da06beabab2a1207161f40d60bb13a0a5a89b2f997/whenever-0.8.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e929a3a2815bc5e2dd53504afec714a837c99b4b67dbb261e8594b20f395eaf", size = 396763, upload-time = "2025-07-24T20:58:16.971Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fc/7a4789b8f9c6335a9ecb027034a65caabe256a140389d3d04ee711070179/whenever-0.8.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c97861c051babbcf15b4e6ba021d52941096d4a0c46bf98db162f3720f02725", size = 437276, upload-time = "2025-07-24T20:58:29.35Z" }, + { url = "https://files.pythonhosted.org/packages/70/a3/ba827598a30ae11b4e9a5109a295f66cb3d90c7d19456bc9c886d8f84a75/whenever-0.8.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5731f7d0dc7226f88cc40facc7dfe3810fa921930e19b99cbf4086472e411b6", size = 432758, upload-time = "2025-07-24T20:58:41.132Z" }, + { url = "https://files.pythonhosted.org/packages/37/db/49a94b6ace1a3c3d8633fc45b7c87c604005564e5848bcafc548b321ccfb/whenever-0.8.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69f745b81e9f75b5fdd39596d39dfa05f8a9d7288de5b3782bb30d590a310e12", size = 451512, upload-time = "2025-07-24T20:58:47.429Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f4/7c4b828a7e2d8efe16ec989c11522bce8b5c8420a931a7acb97b5028f33c/whenever-0.8.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e19d4484413f5c08e5e1f5b8968efc119ce7a8bfe788aab136c5c93a33b93b", size = 412824, upload-time = "2025-07-24T20:59:04.697Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/0cebf03683013a0498c48b787be1b8af9b779cc35230f3645cf3c7497c5d/whenever-0.8.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:068232acf18432d86e75286b4b5bc6e3d9b4542a8f9b9ba0114e2aa9cdb4778c", size = 452049, upload-time = "2025-07-24T20:58:52.919Z" }, + { url = "https://files.pythonhosted.org/packages/02/fe/16608f4f30c1262a8d9e343e255908772172734331db19d113b6ff5fd0f3/whenever-0.8.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65889c31520e5241a619b17a82f30252a1b9e9f3dbaf813b1de2b45f218a2c87", size = 574646, upload-time = "2025-07-24T20:58:23.152Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/40904c9f9583b2c8a666c3c90ff58c92722b5fd62428470bc0824036d480/whenever-0.8.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:07dad7e78e3f73516630c3d74636dc966c19ae8b5099cbbf9fdb8b52678385fe", size = 700550, upload-time = "2025-07-24T20:58:35.355Z" }, + { url = "https://files.pythonhosted.org/packages/40/ee/041dde993beaba3902ccfbc858a41ddf2782bb274a93b41b635e073aee66/whenever-0.8.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:800832cf213fe48d58a2b0273304831a7bbfe7341712c2fdc631d9cba92231b0", size = 627101, upload-time = "2025-07-24T20:58:59.06Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2a/d85257e332953a77b2cfd27cd698cb1d226f165b3643e588fded9dd801da/whenever-0.8.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5267dd1977286c7e6231917112fb860b111e7c7c380f94e3725e0c13dd13fdd9", size = 584180, upload-time = "2025-07-24T20:59:11.059Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0e/bf591dc9334ba3f44a88996a60a5650408901e45877ca47808976624657a/whenever-0.8.8-cp312-cp312-win32.whl", hash = "sha256:8766b96c97570c5138100a20488985db0c7f49ad078644f8982a0e3d64080dd3", size = 329897, upload-time = "2025-07-24T20:59:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cd/6e41a11ad795947dc747208a7199aa6be9f3b8b07afba0b153ea6d715a35/whenever-0.8.8-cp312-cp312-win_amd64.whl", hash = "sha256:cf24b466c6043fe4f0344cdbf6c09b166e4fea03f5d57b6a439eb26f391b6839", size = 322283, upload-time = "2025-07-24T20:59:36.766Z" }, + { url = "https://files.pythonhosted.org/packages/1f/31/5bf53c6ae051cec2ae23477bee58ea7e6d61ef1b4a5f012e8fd65fa14eec/whenever-0.8.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:809d56d01440b37b3dae4e3856ebf89322d51333bcddfecacacc235fff3f45c1", size = 391249, upload-time = "2025-07-24T20:59:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/6a/95/0837ab424edb8568afecad9cf742bac1b4cd588bab8152ef26249c795c90/whenever-0.8.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3b0e34a7afe367f245bcf3b8a9e581b6c09437d623001acf23207b08709837e", size = 375098, upload-time = "2025-07-24T20:59:18.543Z" }, + { url = "https://files.pythonhosted.org/packages/81/62/e28b36a7f478729cee8571bf1667ba77091f2d8638230af705cdd34e8450/whenever-0.8.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d1b517eab4cd6edd13cbd7236b5bb3d1babf0606dd756141a6cc274580cee56", size = 396770, upload-time = "2025-07-24T20:58:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/77/e4/8706da917832a43999142afd7f2f68aabf6bd109fd69142237e61461f787/whenever-0.8.8-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24c0e9074b6f73d60eadd0d8149c0d29c5b98b054f881d0a1140367c43c9ec94", size = 437294, upload-time = "2025-07-24T20:58:30.399Z" }, + { url = "https://files.pythonhosted.org/packages/07/87/6d39cb9adf6ef982f3b8471f786fa1330bf73b021e2619204b8934b716b1/whenever-0.8.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b03e95f34d942e1e8c1c752d64e7166e7454112fbf6b4139d0eb9c017a17c6", size = 432762, upload-time = "2025-07-24T20:58:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/5f005ca40eb0d87804a3863f0c737cbd336e8d8bd234772809eb4dfa0ede/whenever-0.8.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c63453e0603bc4583661fbabe9b68ef41059b02f3bef9d572a9668e3dc74793", size = 451525, upload-time = "2025-07-24T20:58:48.577Z" }, + { url = "https://files.pythonhosted.org/packages/c7/61/b5e123dd98e90caeedd8ed3ede06b52ca0e18d8d16dfad8133c954702451/whenever-0.8.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1abba00ed463689d2d062eec36e5ea5f0d195d3fe6702121744592d5a234b09", size = 412819, upload-time = "2025-07-24T20:59:06.362Z" }, + { url = "https://files.pythonhosted.org/packages/93/a8/4897328754d8b7ffca335a473f5215380ad23651d19629e0c44290863ba3/whenever-0.8.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a05df53c1fe75df0a58c188971d63bf0ada627dcb74f2e0c2ff0fb3dd8d7d097", size = 452053, upload-time = "2025-07-24T20:58:54.04Z" }, + { url = "https://files.pythonhosted.org/packages/74/c1/fcd83e53e33d5b36e6b97f127a230dc9439cd5b3dcc5a6e2f546364b4c98/whenever-0.8.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2dde25b43a9375f7940504f8688259540d2d9960e5c973771d7b96030e3c95", size = 574642, upload-time = "2025-07-24T20:58:24.295Z" }, + { url = "https://files.pythonhosted.org/packages/d5/bc/22df727937940c0c4a3f0bf492c2d1ca5c03ab79307f0966a585e6159d34/whenever-0.8.8-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7c190f049532157a737a565a4797099684511011e2e1bd57e2488b0f5802a9e6", size = 700563, upload-time = "2025-07-24T20:58:36.411Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/143cabfd63ec0d05dbc9bfb6ba30f8d871ff19f5f34654602fb9ecbd572a/whenever-0.8.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9815709758e58ceb751cfeb8ad222275e23b6ad512b5b7a956566bda70a1c6cd", size = 627103, upload-time = "2025-07-24T20:59:00.204Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fd/f7359293eb3663f64da310db5a5e81be43943ffcdc27a3d62a92439f206f/whenever-0.8.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:605f0f06b6afbc8c86ca6c1c49a2b69cacb63d6af0fd683bdb1293a2635c3ce5", size = 584172, upload-time = "2025-07-24T20:59:12.186Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/6aaa8d1de1988e93e72135a7c84e02fd831833134383f2eca293def2562d/whenever-0.8.8-cp313-cp313-win32.whl", hash = "sha256:eb8fee01a0955aa9cbf4cc5b348a8562d7f1d277d9d6d0b9da601c0d45ad7f83", size = 329904, upload-time = "2025-07-24T20:59:31.173Z" }, + { url = "https://files.pythonhosted.org/packages/03/8f/9446a951cb423d76a046b90ba18403488d3282091453407e663af6bf4b2a/whenever-0.8.8-cp313-cp313-win_amd64.whl", hash = "sha256:fd71c8ac5cc761efe3b2dece32c5d22d44368155e37d948132b475bd804914d3", size = 322288, upload-time = "2025-07-24T20:59:37.901Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1b/6fbe6e7d4a477f7ca2fe9d4f4fcce0e243a145b45ee35adc55dc577bffa7/whenever-0.8.8-py3-none-any.whl", hash = "sha256:b63d58613af9e44bed80d4a61ba0427db069bdede28ad5365b40bbe375a12990", size = 53489, upload-time = "2025-07-24T20:59:41.163Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, + { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, + { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, + { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, + { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, + { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]