diff --git a/.env.example b/.env.example
index 7ad0363e..cb643ae2 100644
--- a/.env.example
+++ b/.env.example
@@ -24,7 +24,6 @@ BIGRAG_MASTER_KEY=
# Override only when the backing services are not running on local defaults.
# BIGRAG_DATABASE_URL=postgres://bigrag:bigrag@localhost:5432/bigrag?sslmode=disable
-# BIGRAG_QDRANT_URL=http://localhost:6333
# BIGRAG_REDIS_URL=redis://localhost:6379/0
# Optional Redis password for docker-compose production runs.
diff --git a/AGENTS.md b/AGENTS.md
index 3ac68271..16f1e4b0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,12 +2,12 @@
## Project Structure
-- `api/` — Python/FastAPI backend (Docling ingestion + Qdrant/Turbopuffer vector DB)
+- `api/` — Python/FastAPI backend (Docling ingestion + Turbopuffer vector search)
- `sdks/typescript/` — TypeScript SDK (`@bigrag/client`)
- `sdks/python/` — Python SDK (`bigrag`)
- `app/` — admin UI (Vite + TanStack Router + Tailwind v4 + Base UI, `@bigrag/app`)
- `website/` — Documentation site (Next.js + Fumadocs, content in `website/content/docs/`)
-- `e2e/` — pytest + vitest end-to-end suites against a fake-OpenAI + real Postgres/Redis/Qdrant stack
+- `e2e/` — pytest + vitest end-to-end suites against fake OpenAI/Turbopuffer services plus real Postgres/Redis
## Style Guide
@@ -33,8 +33,8 @@ When adding new code, prefer the smallest meaningful module instead of dropping
## Tech Stack
-- **Backend**: Python 3.12+, FastAPI, SQLAlchemy 2 (async) + asyncpg, Alembic, qdrant-client, docling, openai, cohere, cryptography (Fernet for at-rest encryption of provider secrets), dramatiq (Redis broker)
-- **Vector DB**: Qdrant default; turbopuffer alternative (selected per collection)
+- **Backend**: Python 3.12+, FastAPI, SQLAlchemy 2 (async) + asyncpg, Alembic, docling, openai, cohere, cryptography (Fernet for at-rest encryption of provider secrets), dramatiq (Redis broker)
+- **Vector DB**: Turbopuffer
- **Metadata DB**: PostgreSQL 17
- **Ingestion**: Docling (PDF, DOCX, PPTX, XLSX, HTML, Markdown, images)
- **Embedding**: OpenAI, Cohere, Voyage, and OpenAI-compatible providers
@@ -124,7 +124,7 @@ If a feature is removed, remove it from the docs too. Never leave stale referenc
```bash
./dev.sh # starts infra + backend + worker
-./dev.sh --infra # postgres + redis + qdrant only
+./dev.sh --infra # postgres + redis only
./dev.sh --website # docs site only
pnpm dev:app # admin UI on localhost:3000
```
@@ -133,7 +133,6 @@ pnpm dev:app # admin UI on localhost:3000
- Backend API: http://localhost:4000 (Swagger docs at /docs)
- Postgres: localhost:5432
- Redis: localhost:6379
-- Qdrant: localhost:6333
`dev.sh` only tears down the docker stack that it started — if you ran `docker compose up` separately before invoking `dev.sh`, it leaves that running on exit.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 980cfb0b..cbdda1be 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ Thank you for your interest in contributing to bigRAG. This guide will help you
- **Python 3.12+** with [uv](https://docs.astral.sh/uv/)
- **Node.js 20+** with [pnpm](https://pnpm.io/) (via corepack)
-- **Docker** and **Docker Compose** — for Postgres, Redis, Qdrant
+- **Docker** and **Docker Compose** — for Postgres and Redis
### Development Setup
@@ -31,7 +31,7 @@ Or manually:
```bash
# Start infrastructure
-docker compose up postgres redis qdrant -d
+docker compose up postgres redis -d
# Set up the Python backend
cd api
@@ -60,7 +60,7 @@ bigrag/
├── sdks/python/ # Python SDK (bigrag)
├── app/ # Admin UI (TanStack Router + React)
├── website/ # Docs site (Next.js + Fumadocs)
-├── docker-compose.yml # Full stack (Postgres, Redis, Qdrant, API)
+├── docker-compose.yml # Full stack (Postgres, Redis, API)
├── biome.jsonc # Biome linting config for TypeScript
├── pnpm-workspace.yaml # pnpm workspace config
├── dev.sh # One-command dev setup
diff --git a/README.md b/README.md
index df85e5a9..e269dbf3 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Open-source, self-hostable RAG platform. Upload documents, auto-chunk, embed, an
- **Document ingestion** — PDF, DOCX, PPTX, HTML, Markdown, images, and more via [Docling](https://github.com/DS4SD/docling)
- **Embedding providers** — OpenAI, OpenAI-compatible gateways, Cohere, and Voyage
- **Embedding presets** — save named provider/model configs once, reuse across collections
-- **Vector search** — semantic, keyword, and hybrid search modes via [Qdrant](https://qdrant.io) or [turbopuffer](https://turbopuffer.com)
+- **Vector search** — semantic, keyword, and hybrid search modes via [Turbopuffer](https://turbopuffer.com)
- **Reranking** — Cohere reranking for improved result relevance
- **Multi-collection queries** — search across collections in a single request
- **Generated chat** — stateless backend-grounded playground chat with streaming and citations
@@ -31,7 +31,7 @@ Open-source, self-hostable RAG platform. Upload documents, auto-chunk, embed, an
docker compose up -d
```
-This starts bigRAG API, worker, admin UI, Postgres, Redis, and Qdrant (or turbopuffer per collection). Open http://localhost:3000 for the admin UI or http://localhost:4000/docs for the interactive API docs.
+This starts bigRAG API, worker, admin UI, Postgres, and Redis. Configure Turbopuffer before ingesting or querying collections. Open http://localhost:3000 for the admin UI or http://localhost:4000/docs for the interactive API docs.
```bash
# Create a collection
@@ -52,7 +52,7 @@ curl -X POST http://localhost:4000/v1/collections/docs/query \
### Development
```bash
-./dev.sh # starts Postgres, Redis, Qdrant, the API with hot reload, and the worker
+./dev.sh # starts Postgres, Redis, the API with hot reload, and the worker
```
### Docker Images
@@ -90,7 +90,7 @@ graph TD
Worker -->|parse| Docling[Docling
PDF, DOCX, HTML, Images]
Worker -->|embed| Embedding[Embedding provider
OpenAI / compatible / Cohere / Voyage]
- Worker -->|store vectors| Vectors[(Qdrant
or turbopuffer)]
+ Worker -->|store vectors| Vectors[(Turbopuffer)]
Query -->|search| Vectors
Query -->|embed query| Embedding
@@ -288,8 +288,8 @@ Full-workspace keys expose 8 tools — `list_collections`, `get_collection`, `ge
## Configuration
-Most settings use the `BIGRAG_` prefix as environment variables, or configure via `bigrag.toml`.
-Backend logging defaults to `debug` / `text` for local development. Use `BIGRAG_LOG_LEVEL=info` and `BIGRAG_LOG_FORMAT=json` for production log collection.
+Bootstrap settings use the `BIGRAG_` prefix as environment variables, or configure via `bigrag.toml`.
+Backend logging defaults to `debug` / `text` for local development. Use `BIGRAG_LOG_LEVEL=info` and `BIGRAG_LOG_FORMAT=json` for production log collection. Configure Turbopuffer from the admin UI; it is stored in Postgres with the other instance settings.
| Variable | Description | Default |
|----------|-------------|---------|
@@ -303,13 +303,6 @@ Backend logging defaults to `debug` / `text` for local development. Use `BIGRAG_
| `BIGRAG_DB_POOL_MIN` | Min Postgres pool size | `5` |
| `BIGRAG_DB_POOL_MAX` | Max Postgres pool size | `50` |
| `BIGRAG_MIGRATION_TIMEOUT_SECONDS` | Startup migration check timeout (`0` disables the timeout) | `60` |
-| `BIGRAG_QDRANT_URL` | Qdrant URL | `http://localhost:6333` |
-| `BIGRAG_QDRANT_CONNECT_TIMEOUT_SECONDS` | Qdrant startup connection timeout (`0` disables the timeout) | `10` |
-| `BIGRAG_QDRANT_REQUIRED` | Fail API startup if Qdrant cannot be reached | `false` |
-| `BIGRAG_QDRANT_SEARCH_EF` | Optional Qdrant HNSW search recall/latency tuning | — |
-| `BIGRAG_TURBOPUFFER_API_KEY` | turbopuffer API key (only required for collections using turbopuffer) | — |
-| `BIGRAG_TURBOPUFFER_REGION` | turbopuffer region | `aws-us-east-1` |
-| `BIGRAG_TURBOPUFFER_NAMESPACE_PREFIX` | Prefix prepended to turbopuffer namespace names | `bigrag_` |
| `BIGRAG_REDIS_URL` | Redis URL | `redis://localhost:6379/0` |
| `BIGRAG_ENV` | `dev` or `prod` (prod enables startup safety checks) | `dev` |
| `BIGRAG_TRUSTED_PROXIES` | JSON array of trusted proxy CIDRs used to honor `X-Forwarded-For` for audit and access logs | `[]` |
diff --git a/api/alembic/versions/0001_initial_schema.py b/api/alembic/versions/0001_initial_schema.py
index 3658d651..fff94d0d 100644
--- a/api/alembic/versions/0001_initial_schema.py
+++ b/api/alembic/versions/0001_initial_schema.py
@@ -260,7 +260,6 @@ def upgrade() -> None:
sa.Column("embedding_api_key", bigrag.services.crypto.EncryptedString(), nullable=True),
sa.Column("embedding_base_url", sa.Text(), nullable=True),
sa.Column("embedding_preset_id", sa.Uuid(), nullable=True),
- sa.Column("vector_store_provider", sa.Text(), server_default="qdrant", nullable=False),
sa.Column("dimension", sa.Integer(), server_default=sa.text("1536"), nullable=False),
sa.Column("chunk_size", sa.Integer(), server_default=sa.text("512"), nullable=False),
sa.Column("chunk_overlap", sa.Integer(), server_default=sa.text("50"), nullable=False),
@@ -283,7 +282,6 @@ def upgrade() -> None:
server_default=sa.text("false"),
nullable=False,
),
- sa.Column("index_type", sa.Text(), server_default="HNSW", nullable=False),
sa.Column("tenant_field", sa.Text(), nullable=True),
sa.Column("metadata_schema", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column(
@@ -317,66 +315,6 @@ def upgrade() -> None:
unique=False,
)
op.create_index("idx_collections_name", "collections", ["name"], unique=False)
- op.create_table(
- "vector_migration_jobs",
- sa.Column("id", sa.Uuid(), nullable=False),
- sa.Column("collection_id", sa.Uuid(), nullable=True),
- sa.Column("collection_name", sa.Text(), nullable=False),
- sa.Column("source_provider", sa.Text(), nullable=False),
- sa.Column("target_provider", sa.Text(), nullable=False),
- sa.Column("status", sa.Text(), server_default="pending", nullable=False),
- sa.Column("phase", sa.Text(), server_default="queued", nullable=False),
- sa.Column("progress", sa.Double(), server_default=sa.text("0"), nullable=False),
- sa.Column("copied_points", sa.Integer(), server_default=sa.text("0"), nullable=False),
- sa.Column("total_points", sa.Integer(), nullable=True),
- sa.Column(
- "details",
- postgresql.JSONB(astext_type=sa.Text()),
- server_default=sa.text("'{}'::jsonb"),
- nullable=False,
- ),
- sa.Column("error_message", sa.Text(), nullable=True),
- sa.Column("created_by", sa.Uuid(), nullable=True),
- sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
- sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
- sa.Column(
- "created_at",
- sa.DateTime(timezone=True),
- server_default=sa.text("now()"),
- nullable=False,
- ),
- sa.Column(
- "updated_at",
- sa.DateTime(timezone=True),
- server_default=sa.text("now()"),
- nullable=False,
- ),
- sa.CheckConstraint(
- "status IN ('pending', 'running', 'canceling', 'succeeded', 'failed')",
- name="vector_migration_jobs_status_check",
- ),
- sa.ForeignKeyConstraint(["collection_id"], ["collections.id"], ondelete="SET NULL"),
- sa.ForeignKeyConstraint(["created_by"], ["users.id"], ondelete="SET NULL"),
- sa.PrimaryKeyConstraint("id"),
- )
- op.create_index(
- "idx_vector_migration_jobs_collection",
- "vector_migration_jobs",
- ["collection_name"],
- unique=False,
- )
- op.create_index(
- "idx_vector_migration_jobs_created_at_id",
- "vector_migration_jobs",
- [sa.literal_column("created_at DESC"), sa.literal_column("id DESC")],
- unique=False,
- )
- op.create_index(
- "idx_vector_migration_jobs_status",
- "vector_migration_jobs",
- ["status"],
- unique=False,
- )
op.create_table(
"connector_accounts",
sa.Column("id", sa.Uuid(), nullable=False),
@@ -1170,11 +1108,6 @@ def upgrade() -> None:
"webhook_deliveries",
"status IN ('pending', 'delivered', 'failed')",
)
- op.create_check_constraint(
- "collections_vector_store_provider_check",
- "collections",
- "vector_store_provider IN ('qdrant', 'turbopuffer')",
- )
op.create_check_constraint(
"embedding_presets_provider_check",
"embedding_presets",
@@ -1266,10 +1199,6 @@ def downgrade() -> None:
op.drop_table("connector_accounts")
op.drop_index("idx_collections_name", table_name="collections")
op.drop_index("idx_collections_created_at_id", table_name="collections")
- op.drop_index("idx_vector_migration_jobs_status", table_name="vector_migration_jobs")
- op.drop_index("idx_vector_migration_jobs_created_at_id", table_name="vector_migration_jobs")
- op.drop_index("idx_vector_migration_jobs_collection", table_name="vector_migration_jobs")
- op.drop_table("vector_migration_jobs")
op.drop_table("collections")
op.drop_index("idx_backup_jobs_status", table_name="backup_jobs")
op.drop_index("idx_backup_jobs_created_at_id", table_name="backup_jobs")
diff --git a/api/bigrag/app_factory/lifespan.py b/api/bigrag/app_factory/lifespan.py
index 12034bf1..0c31aa69 100644
--- a/api/bigrag/app_factory/lifespan.py
+++ b/api/bigrag/app_factory/lifespan.py
@@ -52,21 +52,16 @@ async def lifespan(app: FastAPI):
runtime = await runtime_settings.get_values(
[
"ingestion_workers",
- "qdrant_connect_timeout_seconds",
- "qdrant_required",
- "qdrant_search_ef",
- "qdrant_url",
"turbopuffer_api_key",
+ "turbopuffer_base_url",
"turbopuffer_namespace_prefix",
"turbopuffer_region",
]
)
vector_store.configure(
- qdrant_url=runtime["qdrant_url"],
- connect_timeout_seconds=runtime["qdrant_connect_timeout_seconds"],
- search_ef=runtime["qdrant_search_ef"],
turbopuffer_api_key=runtime["turbopuffer_api_key"],
+ turbopuffer_base_url=runtime["turbopuffer_base_url"],
turbopuffer_region=runtime["turbopuffer_region"],
turbopuffer_namespace_prefix=runtime["turbopuffer_namespace_prefix"],
)
@@ -76,12 +71,10 @@ async def lifespan(app: FastAPI):
except Exception as exc:
logger.warning(
"Vector store startup connection failed; API will start degraded",
- provider=vector_store.provider,
+ provider="turbopuffer",
error_type=exc.__class__.__name__,
error=str(exc),
)
- if runtime["qdrant_required"]:
- raise
app.state.vector_store = vector_store
storage = await init_storage_from_runtime(upload_dir=s.upload_dir)
diff --git a/api/bigrag/app_factory/routers.py b/api/bigrag/app_factory/routers.py
index d1dba1aa..dc2033fc 100644
--- a/api/bigrag/app_factory/routers.py
+++ b/api/bigrag/app_factory/routers.py
@@ -15,7 +15,6 @@ def include_all_routers(app: FastAPI) -> None:
from bigrag.routers.admin_realtime import router as admin_realtime_router
from bigrag.routers.admin_settings import router as admin_settings_router
from bigrag.routers.admin_users import router as admin_users_router
- from bigrag.routers.admin_vector_migrations import router as admin_vector_migrations_router
from bigrag.routers.admin_vector_storage import router as admin_vector_storage_router
from bigrag.routers.analytics import router as analytics_router
from bigrag.routers.auth import router as auth_router
@@ -51,7 +50,6 @@ def include_all_routers(app: FastAPI) -> None:
app.include_router(admin_settings_router)
app.include_router(admin_access_router)
app.include_router(admin_vector_storage_router)
- app.include_router(admin_vector_migrations_router)
app.include_router(admin_realtime_router)
app.include_router(mcp_servers_router)
app.include_router(admin_audit_router)
diff --git a/api/bigrag/config.py b/api/bigrag/config.py
index 9eacab94..b8c81c3f 100644
--- a/api/bigrag/config.py
+++ b/api/bigrag/config.py
@@ -27,15 +27,6 @@ class Settings(BaseSettings):
db_pool_max: int = 50
migration_timeout_seconds: int = 60
- qdrant_url: str = "http://localhost:6333"
- qdrant_connect_timeout_seconds: int = 10
- qdrant_required: bool = False
- qdrant_prefer_grpc: bool = False
- qdrant_grpc_port: int = 6334
- turbopuffer_api_key: str | None = None
- turbopuffer_region: str = "aws-us-east-1"
- turbopuffer_namespace_prefix: str = "bigrag_"
-
redis_url: str = "redis://localhost:6379/0"
master_key: str | None = None
diff --git a/api/bigrag/db/models/__init__.py b/api/bigrag/db/models/__init__.py
index b9a89dce..1b41b899 100644
--- a/api/bigrag/db/models/__init__.py
+++ b/api/bigrag/db/models/__init__.py
@@ -19,7 +19,6 @@
from bigrag.db.models.instance import InstanceSetting, MaintenanceLock
from bigrag.db.models.observability import AccessLog, AuditLog, BackupJob, QueryLog
from bigrag.db.models.preference import UserPreference
-from bigrag.db.models.vector_migration import VectorMigrationJob
from bigrag.db.models.webhook import Webhook, WebhookDelivery
__all__ = [
@@ -46,7 +45,6 @@
"User",
"UserSession",
"UserPreference",
- "VectorMigrationJob",
"Webhook",
"WebhookDelivery",
]
diff --git a/api/bigrag/db/models/collection.py b/api/bigrag/db/models/collection.py
index 4fda5d81..b6bac6a6 100644
--- a/api/bigrag/db/models/collection.py
+++ b/api/bigrag/db/models/collection.py
@@ -14,10 +14,6 @@
class Collection(Base):
__tablename__ = "collections"
__table_args__ = (
- sa.CheckConstraint(
- "vector_store_provider IN ('qdrant', 'turbopuffer')",
- name="collections_vector_store_provider_check",
- ),
sa.Index("idx_collections_name", "name"),
sa.Index("idx_collections_created_at_id", sa.desc("created_at"), sa.desc("id")),
)
@@ -37,11 +33,6 @@ class Collection(Base):
sa.ForeignKey("embedding_presets.id", ondelete="RESTRICT"),
nullable=True,
)
- vector_store_provider: Mapped[str] = mapped_column(
- sa.Text,
- nullable=False,
- server_default="qdrant",
- )
dimension: Mapped[int] = mapped_column(
sa.Integer, nullable=False, server_default=sa.text("1536")
)
@@ -75,7 +66,6 @@ class Collection(Base):
multimodal_enrichment_enabled: Mapped[bool] = mapped_column(
sa.Boolean, nullable=False, server_default=sa.false()
)
- index_type: Mapped[str] = mapped_column(sa.Text, nullable=False, server_default="HNSW")
tenant_field: Mapped[str | None] = mapped_column(sa.Text)
metadata_schema: Mapped[dict | None] = mapped_column(JSONB)
meta: Mapped[dict] = mapped_column(
diff --git a/api/bigrag/db/models/vector_migration.py b/api/bigrag/db/models/vector_migration.py
deleted file mode 100644
index d9f402c0..00000000
--- a/api/bigrag/db/models/vector_migration.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from __future__ import annotations
-
-from datetime import datetime
-from uuid import UUID
-
-import sqlalchemy as sa
-from sqlalchemy.dialects.postgresql import JSONB
-from sqlalchemy.orm import Mapped, mapped_column
-
-from bigrag.db.base import TS, Base, TSupd, UUIDpk
-
-
-class VectorMigrationJob(Base):
- __tablename__ = "vector_migration_jobs"
- __table_args__ = (
- sa.CheckConstraint(
- "status IN ('pending', 'running', 'canceling', 'succeeded', 'failed')",
- name="vector_migration_jobs_status_check",
- ),
- sa.Index("idx_vector_migration_jobs_collection", "collection_name"),
- sa.Index("idx_vector_migration_jobs_status", "status"),
- sa.Index(
- "idx_vector_migration_jobs_created_at_id",
- sa.desc("created_at"),
- sa.desc("id"),
- ),
- )
-
- id: Mapped[UUIDpk]
- collection_id: Mapped[UUID | None] = mapped_column(
- sa.ForeignKey("collections.id", ondelete="SET NULL")
- )
- collection_name: Mapped[str] = mapped_column(sa.Text, nullable=False)
- source_provider: Mapped[str] = mapped_column(sa.Text, nullable=False)
- target_provider: Mapped[str] = mapped_column(sa.Text, nullable=False)
- status: Mapped[str] = mapped_column(sa.Text, nullable=False, server_default="pending")
- phase: Mapped[str] = mapped_column(sa.Text, nullable=False, server_default="queued")
- progress: Mapped[float] = mapped_column(sa.Double, nullable=False, server_default=sa.text("0"))
- copied_points: Mapped[int] = mapped_column(
- sa.Integer, nullable=False, server_default=sa.text("0")
- )
- total_points: Mapped[int | None] = mapped_column(sa.Integer)
- details: Mapped[dict] = mapped_column(
- JSONB, nullable=False, server_default=sa.text("'{}'::jsonb")
- )
- error_message: Mapped[str | None] = mapped_column(sa.Text)
- created_by: Mapped[UUID | None] = mapped_column(sa.ForeignKey("users.id", ondelete="SET NULL"))
- started_at: Mapped[datetime | None] = mapped_column(sa.DateTime(timezone=True))
- completed_at: Mapped[datetime | None] = mapped_column(sa.DateTime(timezone=True))
- created_at: Mapped[TS]
- updated_at: Mapped[TSupd]
diff --git a/api/bigrag/logging.py b/api/bigrag/logging.py
index a0a0dad4..03e7d6a1 100644
--- a/api/bigrag/logging.py
+++ b/api/bigrag/logging.py
@@ -255,7 +255,6 @@ def configure_logging(log_level: str = "debug", log_format: str = "text") -> Non
"hpack",
"httpcore",
"httpx",
- "qdrant_client",
"uvicorn.access",
):
logging.getLogger(name).setLevel(logging.WARNING)
diff --git a/api/bigrag/main.py b/api/bigrag/main.py
index e4d31f52..b8adf430 100644
--- a/api/bigrag/main.py
+++ b/api/bigrag/main.py
@@ -47,7 +47,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
app = FastAPI(
title="bigRAG",
- description="Self-hostable RAG platform with Docling + Qdrant",
+ description="Self-hostable RAG platform with Docling + turbopuffer",
version=__version__,
lifespan=lifespan,
default_response_class=ORJSONResponse,
@@ -79,7 +79,6 @@ def cli() -> None:
parser.add_argument("--host", help="Server host")
parser.add_argument("--port", type=int, help="Server port")
parser.add_argument("--database-url", help="Postgres connection URL")
- parser.add_argument("--qdrant-url", help="Qdrant connection URL")
parser.add_argument("--redis-url", help="Redis connection URL")
args = parser.parse_args()
@@ -92,8 +91,6 @@ def cli() -> None:
overrides["port"] = args.port
if args.database_url is not None:
overrides["database_url"] = args.database_url
- if args.qdrant_url is not None:
- overrides["qdrant_url"] = args.qdrant_url
if args.redis_url is not None:
overrides["redis_url"] = args.redis_url
for key, value in overrides.items():
diff --git a/api/bigrag/middleware/maintenance.py b/api/bigrag/middleware/maintenance.py
index 4a5e134b..9310c02a 100644
--- a/api/bigrag/middleware/maintenance.py
+++ b/api/bigrag/middleware/maintenance.py
@@ -7,7 +7,7 @@
from bigrag.services.maintenance import active_lock
SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
-CONTROL_PATH_PREFIXES = ("/v1/admin/backups", "/v1/admin/vector-storage/migrations")
+CONTROL_PATH_PREFIXES = ("/v1/admin/backups",)
def _is_control_path(path: str) -> bool:
diff --git a/api/bigrag/models/collection.py b/api/bigrag/models/collection.py
index 4754965d..cee4226f 100644
--- a/api/bigrag/models/collection.py
+++ b/api/bigrag/models/collection.py
@@ -4,14 +4,11 @@
from pydantic import BaseModel, Field, model_validator
-from bigrag.services.vector_store.base import VectorStoreProvider
-
class CreateCollectionRequest(BaseModel):
name: str = Field(min_length=1, max_length=128, pattern=r"^[a-zA-Z][a-zA-Z0-9_]*$")
description: str = ""
embedding_preset_id: str | None = None
- vector_store_provider: VectorStoreProvider = "qdrant"
embedding_provider: str | None = None
embedding_model: str | None = None
embedding_api_key: str | None = None
@@ -24,16 +21,11 @@ class CreateCollectionRequest(BaseModel):
pattern=r"^(paragraph|recursive)$",
description="Chunking algorithm: paragraph (default) or recursive.",
)
- index_type: str = Field(
- default="HNSW",
- pattern=r"^HNSW$",
- description="Vector index preference. Qdrant uses HNSW for dense-vector search.",
- )
tenant_field: str | None = Field(
default=None,
max_length=64,
description=(
- "Optional metadata field name to index for tenant-aware Qdrant "
+ "Optional metadata field name to index for tenant-aware "
"payload filtering in multi-tenant deployments."
),
)
@@ -92,12 +84,10 @@ class CollectionResponse(BaseModel):
description: str
embedding_provider: str
embedding_model: str
- vector_store_provider: VectorStoreProvider
dimension: int
chunk_size: int
chunk_overlap: int
chunk_strategy: str = "paragraph"
- index_type: str = "HNSW"
tenant_field: str | None = None
has_metadata_schema: bool = False
document_count: int
diff --git a/api/bigrag/models/vector_migration.py b/api/bigrag/models/vector_migration.py
deleted file mode 100644
index 71954ee9..00000000
--- a/api/bigrag/models/vector_migration.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from __future__ import annotations
-
-from datetime import datetime
-
-from pydantic import BaseModel, Field
-
-from bigrag.services.vector_store.base import VectorStoreProvider
-
-
-class VectorMigrationCreateRequest(BaseModel):
- collection: str = Field(min_length=1, max_length=128)
- target_provider: VectorStoreProvider
-
-
-class VectorMigrationJobResponse(BaseModel):
- id: str
- collection_id: str | None
- collection_name: str
- source_provider: VectorStoreProvider
- target_provider: VectorStoreProvider
- status: str
- phase: str
- progress: float
- copied_points: int
- total_points: int | None
- details: dict
- error_message: str | None = None
- created_by: str | None = None
- started_at: datetime | None = None
- completed_at: datetime | None = None
- created_at: datetime
- updated_at: datetime
-
-
-class VectorMigrationJobListResponse(BaseModel):
- jobs: list[VectorMigrationJobResponse]
- total: int | None = None
- next_cursor: str | None = None
diff --git a/api/bigrag/routers/admin_realtime.py b/api/bigrag/routers/admin_realtime.py
index 61e4184d..1b7aa76d 100644
--- a/api/bigrag/routers/admin_realtime.py
+++ b/api/bigrag/routers/admin_realtime.py
@@ -11,7 +11,6 @@
from bigrag.routers.admin_access import access_overview, list_access_logs
from bigrag.routers.admin_audit import list_audit_log
from bigrag.routers.admin_backups import list_backup_jobs
-from bigrag.routers.admin_vector_migrations import list_vector_migration_jobs
from bigrag.routers.collections import get_collection_stats
from bigrag.routers.connectors import connector_sources, connector_sync_jobs
from bigrag.routers.documents import get_document, list_documents
@@ -37,7 +36,6 @@
ACTIVE_SYNC_JOB_STATUSES = {"pending", "running"}
ACTIVE_BACKUP_JOB_STATUSES = {"pending", "running"}
-ACTIVE_VECTOR_MIGRATION_STATUSES = {"pending", "running", "canceling"}
def _parse_document_ids(document_ids: list[str]) -> list[str]:
@@ -84,12 +82,6 @@ def _backup_jobs_interval(payload: Any | None) -> float:
return 2.0 if active else 15.0
-def _vector_migration_jobs_interval(payload: Any | None) -> float:
- jobs = getattr(payload, "jobs", []) if payload is not None else []
- active = any(getattr(job, "status", None) in ACTIVE_VECTOR_MIGRATION_STATUSES for job in jobs)
- return 2.0 if active else 15.0
-
-
@router.get("/collections/{collection_name}/documents", response_class=StreamingResponse)
async def collection_documents_stream(
collection_name: str,
@@ -283,31 +275,6 @@ async def load():
return _interval_response(topic, load, _backup_jobs_interval)
-@router.get("/vector-migrations", response_class=StreamingResponse)
-async def vector_migration_jobs_stream(
- collection: str | None = Query(default=None, max_length=128),
- limit: int = Query(default=20, ge=1, le=100),
- offset: int = Query(default=0, ge=0),
- user: dict = Depends(require_admin_session),
-):
- topic = f"vector-migrations:{collection or 'all'}:{limit}:{offset}"
-
- async def load():
- return await _with_session(
- lambda session: list_vector_migration_jobs(
- collection=collection,
- limit=limit,
- offset=offset,
- cursor=None,
- include_total=False,
- _=user,
- session=session,
- )
- )
-
- return _interval_response(topic, load, _vector_migration_jobs_interval)
-
-
@router.get("/access/overview", response_class=StreamingResponse)
async def access_overview_stream(
window_days: int = Query(default=7, ge=1, le=90),
diff --git a/api/bigrag/routers/admin_vector_migrations.py b/api/bigrag/routers/admin_vector_migrations.py
deleted file mode 100644
index aa623fec..00000000
--- a/api/bigrag/routers/admin_vector_migrations.py
+++ /dev/null
@@ -1,177 +0,0 @@
-from __future__ import annotations
-
-from uuid import UUID
-
-import sqlalchemy as sa
-from fastapi import APIRouter, Depends, HTTPException, Query, Request
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from bigrag.db.models import VectorMigrationJob
-from bigrag.db.session import get_session
-from bigrag.middleware.auth import require_admin_session
-from bigrag.models import StatusResponse
-from bigrag.models.vector_migration import (
- VectorMigrationCreateRequest,
- VectorMigrationJobListResponse,
- VectorMigrationJobResponse,
-)
-from bigrag.routers import uuid_or_404
-from bigrag.services import audit
-from bigrag.services.error_sanitize import sanitize_message_text
-from bigrag.services.jobs.actors import enqueue_vector_migration_job
-from bigrag.services.pagination import apply_cursor, build_response_cursor, decode_cursor_or_400
-from bigrag.services.vector_migration import (
- VectorMigrationConflictError,
- VectorMigrationError,
- create_vector_migration_job,
- delete_vector_migration_job,
-)
-
-router = APIRouter(
- prefix="/v1/admin/vector-storage/migrations",
- tags=["admin:vector-storage"],
-)
-
-
-def vector_migration_job_response(job: VectorMigrationJob) -> VectorMigrationJobResponse:
- return VectorMigrationJobResponse(
- id=str(job.id),
- collection_id=str(job.collection_id) if job.collection_id else None,
- collection_name=job.collection_name,
- source_provider=job.source_provider,
- target_provider=job.target_provider,
- status=job.status,
- phase=job.phase,
- progress=job.progress,
- copied_points=job.copied_points,
- total_points=job.total_points,
- details=job.details or {},
- error_message=job.error_message,
- created_by=str(job.created_by) if job.created_by else None,
- started_at=job.started_at,
- completed_at=job.completed_at,
- created_at=job.created_at,
- updated_at=job.updated_at,
- )
-
-
-@router.get("", response_model=VectorMigrationJobListResponse)
-async def list_vector_migration_jobs(
- collection: str | None = Query(default=None, max_length=128),
- limit: int = Query(default=20, ge=1, le=100),
- offset: int = Query(default=0, ge=0),
- cursor: str | None = Query(default=None),
- include_total: bool = Query(default=False),
- _: dict = Depends(require_admin_session),
- session: AsyncSession = Depends(get_session),
-) -> VectorMigrationJobListResponse:
- cursor_tuple = decode_cursor_or_400(cursor)
- stmt = sa.select(VectorMigrationJob).order_by(
- VectorMigrationJob.created_at.desc(),
- VectorMigrationJob.id.desc(),
- )
- count_stmt = sa.select(sa.func.count()).select_from(VectorMigrationJob)
- if collection:
- stmt = stmt.where(VectorMigrationJob.collection_name == collection)
- count_stmt = count_stmt.where(VectorMigrationJob.collection_name == collection)
-
- if cursor_tuple is not None:
- stmt = apply_cursor(
- stmt,
- VectorMigrationJob.created_at,
- VectorMigrationJob.id,
- cursor_tuple,
- ).limit(limit + 1)
- else:
- stmt = stmt.limit(limit + 1).offset(offset)
-
- rows = (await session.scalars(stmt)).all()
- page, next_cursor = build_response_cursor(list(rows), "created_at", "id", limit)
-
- total: int | None = None
- if include_total:
- total = (await session.scalar(count_stmt)) or 0
-
- return VectorMigrationJobListResponse(
- jobs=[vector_migration_job_response(job) for job in page],
- total=total,
- next_cursor=next_cursor,
- )
-
-
-@router.get("/{migration_id}", response_model=VectorMigrationJobResponse)
-async def get_vector_migration_job(
- migration_id: str,
- _: dict = Depends(require_admin_session),
- session: AsyncSession = Depends(get_session),
-) -> VectorMigrationJobResponse:
- try:
- target_id = UUID(migration_id)
- except ValueError as exc:
- raise HTTPException(status_code=400, detail="Invalid migration_id") from exc
- job = await session.get(VectorMigrationJob, target_id)
- if job is None:
- raise HTTPException(status_code=404, detail="Vector migration job not found")
- return vector_migration_job_response(job)
-
-
-@router.post("", response_model=VectorMigrationJobResponse, status_code=201)
-async def start_vector_migration_job(
- body: VectorMigrationCreateRequest,
- request: Request,
- admin: dict = Depends(require_admin_session),
-) -> VectorMigrationJobResponse:
- user_id = UUID(admin["id"]) if admin.get("id") else None
- try:
- job = await create_vector_migration_job(
- collection_name=body.collection,
- target_provider=body.target_provider,
- created_by=user_id,
- )
- except VectorMigrationConflictError as exc:
- raise HTTPException(
- status_code=409,
- detail=sanitize_message_text(str(exc)) or "Vector migration cannot be started.",
- ) from exc
- except VectorMigrationError as exc:
- raise HTTPException(
- status_code=400,
- detail=sanitize_message_text(str(exc)) or "Vector migration cannot be started.",
- ) from exc
- audit.record(
- request,
- user=admin,
- action="vector_migration.requested",
- resource_type="vector_migration_job",
- resource_id=str(job.id),
- metadata={
- "collection": job.collection_name,
- "source_provider": job.source_provider,
- "target_provider": job.target_provider,
- },
- )
- enqueue_vector_migration_job(str(job.id))
- return vector_migration_job_response(job)
-
-
-@router.delete("/{migration_id}", response_model=StatusResponse)
-async def delete_vector_migration_job_route(
- migration_id: str,
- request: Request,
- admin: dict = Depends(require_admin_session),
-) -> StatusResponse:
- target_id = uuid_or_404(migration_id, "Vector migration job")
- result = await delete_vector_migration_job(target_id)
- if result is None:
- raise HTTPException(status_code=404, detail="Vector migration job not found")
- audit.record(
- request,
- user=admin,
- action="vector_migration.delete",
- resource_type="vector_migration_job",
- resource_id=migration_id,
- metadata={"result": result},
- )
- if result == "stop_requested":
- return StatusResponse(status="ok", message="Vector migration stop requested")
- return StatusResponse(status="ok", message="Vector migration deleted")
diff --git a/api/bigrag/routers/admin_vector_storage.py b/api/bigrag/routers/admin_vector_storage.py
index 0fffd917..a4e4114f 100644
--- a/api/bigrag/routers/admin_vector_storage.py
+++ b/api/bigrag/routers/admin_vector_storage.py
@@ -7,6 +7,7 @@
from bigrag.db.models import Collection, Document
from bigrag.db.session import get_session
from bigrag.middleware.auth import require_admin_session
+from bigrag.services.health import categorize_dependency_error
from bigrag.services.vector_store import vector_store
router = APIRouter(prefix="/v1/admin/vector-storage", tags=["admin:vector-storage"])
@@ -17,12 +18,15 @@ async def vector_storage_overview(
_: dict = Depends(require_admin_session),
session: AsyncSession = Depends(get_session),
) -> dict[str, object]:
- provider_health = await vector_store.provider_health()
+ health: dict[str, object] = {"status": "ok", "error": None}
+ try:
+ await vector_store.health_check()
+ except Exception as exc:
+ health = {"status": "error", "error": categorize_dependency_error(exc)}
rows = (
await session.execute(
sa.select(
Collection.name,
- Collection.vector_store_provider,
sa.func.count(Document.id).label("documents"),
sa.func.coalesce(sa.func.sum(Document.chunk_count), 0).label("chunks"),
sa.func.coalesce(sa.func.sum(Document.file_size), 0).label("bytes"),
@@ -35,12 +39,11 @@ async def vector_storage_overview(
collections = [
{
"name": name,
- "provider": provider,
"documents": int(documents or 0),
"chunks": int(chunks or 0),
"bytes": int(bytes_ or 0),
}
- for name, provider, documents, chunks, bytes_ in rows
+ for name, documents, chunks, bytes_ in rows
]
totals = {
"collections": len(collections),
@@ -49,9 +52,8 @@ async def vector_storage_overview(
"bytes": sum(item["bytes"] for item in collections),
}
return {
- "fallback_provider": vector_store.provider,
- "configured_providers": list(vector_store.configured_providers),
- "provider_health": provider_health,
+ "provider": "turbopuffer",
+ "health": health,
"collections": collections,
"totals": totals,
}
diff --git a/api/bigrag/routers/collections.py b/api/bigrag/routers/collections.py
index 087ea0d4..381a0663 100644
--- a/api/bigrag/routers/collections.py
+++ b/api/bigrag/routers/collections.py
@@ -49,12 +49,10 @@ def _collection_response(c: Collection) -> CollectionResponse:
description=c.description,
embedding_provider=c.embedding_provider,
embedding_model=c.embedding_model,
- vector_store_provider=c.vector_store_provider,
dimension=c.dimension,
chunk_size=c.chunk_size,
chunk_overlap=c.chunk_overlap,
chunk_strategy=c.chunk_strategy,
- index_type=c.index_type,
tenant_field=c.tenant_field,
has_metadata_schema=bool(c.metadata_schema),
document_count=c.document_count,
@@ -125,7 +123,6 @@ async def create_collection(
logger.info(
"create collection",
name=body.name,
- vector_store_provider=body.vector_store_provider,
provider=body.embedding_provider,
model=body.embedding_model,
)
@@ -232,14 +229,12 @@ async def create_collection(
collection = Collection(
name=body.name,
description=body.description,
- vector_store_provider=body.vector_store_provider,
embedding_provider=provider,
embedding_model=model,
dimension=dimension,
chunk_size=body.chunk_size,
chunk_overlap=body.chunk_overlap,
chunk_strategy=body.chunk_strategy,
- index_type=body.index_type,
tenant_field=body.tenant_field,
meta=body.metadata,
metadata_schema=body.metadata_schema,
@@ -260,11 +255,11 @@ async def create_collection(
await session.commit()
except IntegrityError as e:
await session.rollback()
- await vector_store.delete_collection(body.name, provider=body.vector_store_provider)
+ await vector_store.delete_collection(body.name)
raise HTTPException(status_code=409, detail="Collection already exists") from e
except Exception:
await session.rollback()
- await vector_store.delete_collection(body.name, provider=body.vector_store_provider)
+ await vector_store.delete_collection(body.name)
raise
await session.refresh(collection)
await collection_cache.invalidate(body.name)
@@ -285,7 +280,6 @@ async def create_collection(
metadata={
"name": body.name,
"provider": provider,
- "vector_store_provider": body.vector_store_provider,
"model": model,
"dimension": dimension,
},
diff --git a/api/bigrag/routers/collections_embedding.py b/api/bigrag/routers/collections_embedding.py
index 396a5451..76bcc6de 100644
--- a/api/bigrag/routers/collections_embedding.py
+++ b/api/bigrag/routers/collections_embedding.py
@@ -14,6 +14,7 @@
from bigrag.services.ingestion_job import create_ingestion_job
from bigrag.services.queue import ingestion_queue
from bigrag.services.retrieval import invalidate_collection_query_cache
+from bigrag.services.vector_store import vector_store
logger = get_logger("bigrag.routers.collections_embedding")
@@ -47,7 +48,6 @@ async def reembed_collection(
"chunk_size": collection.chunk_size,
"chunk_overlap": collection.chunk_overlap,
"chunk_strategy": collection.chunk_strategy or "paragraph",
- "vector_store_provider": collection.vector_store_provider,
"tenant_field": collection.tenant_field,
}
jobs = [
@@ -61,6 +61,8 @@ async def reembed_collection(
]
doc_ids = [doc_id for doc_id, _ in docs]
+ for doc_id in doc_ids:
+ await vector_store.delete_by_document(name, str(doc_id))
await session.execute(
sa.update(Document)
.where(Document.id.in_(doc_ids))
diff --git a/api/bigrag/routers/documents.py b/api/bigrag/routers/documents.py
index 3c14e52f..19ac88c3 100644
--- a/api/bigrag/routers/documents.py
+++ b/api/bigrag/routers/documents.py
@@ -286,7 +286,6 @@ async def delete_document(
await vector_store.delete_by_document(
collection_name,
document_id,
- provider=collection.get("vector_store_provider"),
)
storage = get_storage()
await storage.delete(file_path)
@@ -333,7 +332,6 @@ async def reprocess_document(
await vector_store.delete_by_document(
collection_name,
document_id,
- provider=collection.get("vector_store_provider"),
)
doc.status = "pending"
@@ -451,7 +449,6 @@ async def get_document_chunks(
document_id,
limit=limit,
offset=offset,
- provider=collection.get("vector_store_provider"),
)
return {"chunks": chunks, "total": total}
diff --git a/api/bigrag/routers/documents_batch.py b/api/bigrag/routers/documents_batch.py
index 5c52d16d..8a18bac9 100644
--- a/api/bigrag/routers/documents_batch.py
+++ b/api/bigrag/routers/documents_batch.py
@@ -241,7 +241,6 @@ async def _delete_one(doc_id: str, doc: Document) -> bool:
await vector_store.delete_by_document(
collection_name,
doc_id,
- provider=collection.get("vector_store_provider"),
)
storage = get_storage()
await storage.delete(doc.file_path)
diff --git a/api/bigrag/routers/documents_global.py b/api/bigrag/routers/documents_global.py
index 881f8c5d..8c72c566 100644
--- a/api/bigrag/routers/documents_global.py
+++ b/api/bigrag/routers/documents_global.py
@@ -60,6 +60,5 @@ async def get_document_chunks_global(
document_id,
limit=limit,
offset=offset,
- provider=collection.get("vector_store_provider"),
)
return {"chunks": chunks, "total": total}
diff --git a/api/bigrag/routers/evaluation.py b/api/bigrag/routers/evaluation.py
index 9eba0e5e..38b3384e 100644
--- a/api/bigrag/routers/evaluation.py
+++ b/api/bigrag/routers/evaluation.py
@@ -122,7 +122,6 @@ async def run_evaluation(
search_mode=body.search_mode,
filters=case_filters,
reranking_config=get_reranking_config(collection),
- vector_store_provider=collection.get("vector_store_provider"),
)
hit_ids = [r.get("document_id") or r.get("id") for r in outcome.results]
expected = set(case.relevant_ids)
diff --git a/api/bigrag/routers/query.py b/api/bigrag/routers/query.py
index 9ab04c50..811073e7 100644
--- a/api/bigrag/routers/query.py
+++ b/api/bigrag/routers/query.py
@@ -95,7 +95,6 @@ async def query_collection(
search_mode=search_mode,
reranking_config=get_reranking_config(collection),
rerank_override=body.rerank,
- vector_store_provider=collection.get("vector_store_provider"),
)
logger.info(
@@ -214,7 +213,6 @@ async def multi_collection_query(
embedding_models = {}
reranking_configs = {}
- vector_store_providers = {}
resolved_collections = await asyncio.gather(
*[get_collection_or_404(col_name) for col_name in body.collections]
)
@@ -225,7 +223,6 @@ async def multi_collection_query(
except (ImportError, ValueError) as e:
raise HTTPException(status_code=400, detail=f"Collection '{col_name}': {e}") from e
reranking_configs[col_name] = get_reranking_config(collection)
- vector_store_providers[col_name] = collection.get("vector_store_provider") or "qdrant"
include_multimodal_by_collection = {
col_name: bool(body.multimodal and collection.get("multimodal_enabled"))
for col_name, collection in zip(body.collections, resolved_collections, strict=True)
@@ -241,7 +238,6 @@ async def multi_collection_query(
search_mode=body.search_mode,
reranking_configs=reranking_configs,
rerank_override=body.rerank,
- vector_store_providers=vector_store_providers,
)
logger.info("multi-query complete", collections=body.collections, results=len(results))
@@ -314,7 +310,6 @@ async def run_one(item: BatchQueryItem) -> tuple[BatchQueryItem, list[dict], int
search_mode=item.search_mode,
reranking_config=get_reranking_config(collection),
rerank_override=item.rerank,
- vector_store_provider=collection.get("vector_store_provider"),
)
include_multimodal = bool(item.multimodal and collection.get("multimodal_enabled"))
diff --git a/api/bigrag/routers/vectors.py b/api/bigrag/routers/vectors.py
index b2c27e2c..f673299e 100644
--- a/api/bigrag/routers/vectors.py
+++ b/api/bigrag/routers/vectors.py
@@ -90,7 +90,6 @@ async def upsert_vectors(
embeddings=embeddings,
texts=texts,
metadata=metadata,
- provider=collection.get("vector_store_provider"),
)
await invalidate_collection_query_cache(collection_name)
logger.info("vector upsert complete", collection=collection_name, upserted=count)
@@ -123,12 +122,11 @@ async def delete_vectors(
status_code=413,
detail=f"Too many vector IDs. Max: {limits['max_vector_delete_count']}",
)
- collection = await get_collection_or_404(collection_name)
+ await get_collection_or_404(collection_name)
logger.info("vector delete", collection=collection_name, ids=len(body.ids))
await vector_store.delete_by_ids(
collection_name,
body.ids,
- provider=collection.get("vector_store_provider"),
)
await invalidate_collection_query_cache(collection_name)
access_log.set_context(request, metadata={"deleted": len(body.ids)})
diff --git a/api/bigrag/services/backup/exporters.py b/api/bigrag/services/backup/exporters.py
index 4ff39bb2..bcbdbbf1 100644
--- a/api/bigrag/services/backup/exporters.py
+++ b/api/bigrag/services/backup/exporters.py
@@ -77,7 +77,6 @@ async def _export_vector_store(temp_dir: Path) -> dict[str, int]:
async for point in vector_store.iter_collection_points(
collection.name,
with_vectors=False,
- provider=collection.vector_store_provider,
):
f.write(orjson.dumps(_point_payload(point)) + b"\n")
count += 1
@@ -88,7 +87,6 @@ async def _export_vector_store(temp_dir: Path) -> dict[str, int]:
collections_meta.append(
{
"collection": collection.name,
- "provider": collection.vector_store_provider,
"vector_store_collection": collection.name,
"exists": exists,
"points": count,
diff --git a/api/bigrag/services/backup/jobs.py b/api/bigrag/services/backup/jobs.py
index e2332b03..085630ff 100644
--- a/api/bigrag/services/backup/jobs.py
+++ b/api/bigrag/services/backup/jobs.py
@@ -12,7 +12,7 @@
import sqlalchemy as sa
from bigrag.db.engine import session_factory
-from bigrag.db.models import AuditLog, BackupJob, ConnectorSyncJob, VectorMigrationJob
+from bigrag.db.models import AuditLog, BackupJob, ConnectorSyncJob
from bigrag.logging import get_logger
from bigrag.services.maintenance import acquire_backup_lock, active_lock, release_backup_lock
from bigrag.services.queue import ingestion_queue
@@ -25,8 +25,6 @@
logger = get_logger("bigrag.backup")
-ACTIVE_VECTOR_MIGRATION_STATUSES = ("pending", "running", "canceling")
-
async def create_backup_job(*, label: str, created_by: uuid.UUID | None) -> BackupJob:
lock = await active_lock()
@@ -43,17 +41,6 @@ async def create_backup_job(*, label: str, created_by: uuid.UUID | None) -> Back
)
if active is not None:
raise BackupConfigError("A backup is already pending or running")
- active_migration = await session.scalar(
- sa.select(VectorMigrationJob)
- .where(VectorMigrationJob.status.in_(ACTIVE_VECTOR_MIGRATION_STATUSES))
- .order_by(VectorMigrationJob.created_at.desc())
- .limit(1)
- .with_for_update()
- )
- if active_migration is not None:
- raise BackupConfigError(
- "A vector migration is already pending, running, or canceling"
- )
job = BackupJob(label=label.strip(), created_by=created_by)
session.add(job)
await session.refresh(job)
diff --git a/api/bigrag/services/chat/questions/api.py b/api/bigrag/services/chat/questions/api.py
index 03a4bb9a..1fe2cbbf 100644
--- a/api/bigrag/services/chat/questions/api.py
+++ b/api/bigrag/services/chat/questions/api.py
@@ -192,7 +192,6 @@ async def _sample_chunks(
str(document.id),
limit=CHUNK_LIMIT,
offset=offset,
- provider=collection.get("vector_store_provider"),
)
for chunk in chunks:
item = dict(chunk)
diff --git a/api/bigrag/services/chat/turn/prepare.py b/api/bigrag/services/chat/turn/prepare.py
index f8484d2b..617a17fe 100644
--- a/api/bigrag/services/chat/turn/prepare.py
+++ b/api/bigrag/services/chat/turn/prepare.py
@@ -90,7 +90,6 @@ async def _prepare_chat_turn(
search_mode=search_mode,
reranking_config=get_reranking_config(collection),
rerank_override=rerank,
- vector_store_provider=collection.get("vector_store_provider"),
)
sources = await _sources_from_results(session, outcome.results)
timings = ChatTimings(
diff --git a/api/bigrag/services/collection_cache.py b/api/bigrag/services/collection_cache.py
index de44635b..6d13f7d7 100644
--- a/api/bigrag/services/collection_cache.py
+++ b/api/bigrag/services/collection_cache.py
@@ -34,7 +34,6 @@ def _serialize(c: Collection, preset: EmbeddingPreset | None = None) -> dict:
"embedding_preset_id": str(c.embedding_preset_id) if c.embedding_preset_id else None,
"embedding_preset_api_key": (preset.api_key if preset else None) if crypto_ready else None,
"embedding_preset_base_url": preset.base_url if preset else None,
- "vector_store_provider": c.vector_store_provider,
"dimension": c.dimension,
"chunk_size": c.chunk_size,
"chunk_overlap": c.chunk_overlap,
@@ -48,7 +47,6 @@ def _serialize(c: Collection, preset: EmbeddingPreset | None = None) -> dict:
"reranking_api_key": c.reranking_api_key if crypto_ready else None,
"multimodal_enabled": c.multimodal_enabled,
"multimodal_enrichment_enabled": c.multimodal_enrichment_enabled,
- "index_type": c.index_type,
"tenant_field": c.tenant_field,
"metadata_schema": c.metadata_schema,
"metadata": c.meta or {},
diff --git a/api/bigrag/services/collection_provision.py b/api/bigrag/services/collection_provision.py
index 4dca1db4..8c23088e 100644
--- a/api/bigrag/services/collection_provision.py
+++ b/api/bigrag/services/collection_provision.py
@@ -36,21 +36,19 @@ async def verify_embedding_credentials(
) from exc
-def vector_store_unavailable_detail(provider: str) -> str:
- if provider == "turbopuffer":
- return (
- "turbopuffer is not configured. Save a turbopuffer API key in Vector Storage "
- "before creating a turbopuffer collection."
- )
- return f"{provider} vector store is not configured."
+def vector_store_unavailable_detail() -> str:
+ return (
+ "Turbopuffer is not configured. Save a turbopuffer API key in Settings "
+ "before creating a collection."
+ )
-def ensure_vector_store_provider_available(provider: str) -> None:
- if provider in vector_store.configured_providers:
+def ensure_vector_store_available() -> None:
+ if vector_store.is_configured():
return
raise HTTPException(
status_code=400,
- detail=vector_store_unavailable_detail(provider),
+ detail=vector_store_unavailable_detail(),
)
@@ -58,48 +56,38 @@ async def create_vector_store_collection(
body: CreateCollectionRequest,
dimension: int,
) -> None:
- ensure_vector_store_provider_available(body.vector_store_provider)
+ ensure_vector_store_available()
try:
await vector_store.create_collection(
body.name,
dimension,
- index_type=body.index_type,
tenant_field=body.tenant_field,
- provider=body.vector_store_provider,
)
except RuntimeError as e:
message = str(e)
if "API key is not configured" in message or "client is not connected" in message:
raise HTTPException(
status_code=400,
- detail=vector_store_unavailable_detail(body.vector_store_provider),
+ detail=vector_store_unavailable_detail(),
) from e
logger.warning(
"vector collection create failed",
collection=body.name,
- vector_store_provider=body.vector_store_provider,
error_type=e.__class__.__name__,
error=message,
)
raise HTTPException(
status_code=502,
- detail=(
- f"Unable to create {body.vector_store_provider} vector collection. "
- "Check Vector Storage settings."
- ),
+ detail="Unable to create vector collection. Check Turbopuffer settings.",
) from e
except httpx.HTTPError as e:
logger.warning(
"vector collection create failed",
collection=body.name,
- vector_store_provider=body.vector_store_provider,
error_type=e.__class__.__name__,
error=str(e),
)
raise HTTPException(
status_code=502,
- detail=(
- f"Unable to create {body.vector_store_provider} vector collection. "
- "Check Vector Storage settings."
- ),
+ detail="Unable to create vector collection. Check Turbopuffer settings.",
) from e
diff --git a/api/bigrag/services/collections.py b/api/bigrag/services/collections.py
index cf7d72d5..a6a7533a 100644
--- a/api/bigrag/services/collections.py
+++ b/api/bigrag/services/collections.py
@@ -25,14 +25,13 @@ async def delete_collection(session: AsyncSession, name: str) -> str:
logger.info("delete collection jobs cancelled", collection=name, flushed=flushed)
deleted_id = str(collection.id)
- vector_store_provider = collection.vector_store_provider
await session.delete(collection)
await session.commit()
await collection_cache.invalidate(name)
await invalidate_collection_query_cache(name)
logger.info("delete collection database records removed", collection=name)
- await vector_store.delete_collection(name, provider=vector_store_provider)
+ await vector_store.delete_collection(name)
logger.info("delete collection vectors dropped", collection=name)
deleted = await get_storage().delete_prefix(f"{name}/")
@@ -51,7 +50,6 @@ async def truncate_collection(session: AsyncSession, name: str) -> str:
logger.info("truncate collection jobs cancelled", collection=name, flushed=flushed)
collection_id = str(collection.id)
- vector_store_provider = collection.vector_store_provider
await session.execute(sa.delete(Document).where(Document.collection_id == collection.id))
await session.execute(
sa.update(Collection).where(Collection.id == collection.id).values(document_count=0)
@@ -61,7 +59,7 @@ async def truncate_collection(session: AsyncSession, name: str) -> str:
await invalidate_collection_query_cache(name)
logger.info("truncate collection documents removed", collection=name)
- await vector_store.delete_collection(name, provider=vector_store_provider)
+ await vector_store.delete_collection(name)
logger.info("truncate collection vectors cleared", collection=name)
deleted = await get_storage().delete_prefix(f"{name}/")
diff --git a/api/bigrag/services/connectors/documents.py b/api/bigrag/services/connectors/documents.py
index 19844259..72c5e9f0 100644
--- a/api/bigrag/services/connectors/documents.py
+++ b/api/bigrag/services/connectors/documents.py
@@ -104,7 +104,6 @@ async def _put_downloaded(storage_key: str) -> None:
await vector_store.delete_by_document(
source.collection_name,
str(doc.id),
- provider=collection.vector_store_provider,
)
old_path = doc.file_path
storage_key = f"{source.collection_name}/{doc.id}{downloaded.file_ext}"
@@ -156,7 +155,6 @@ async def delete_synced_document(
await vector_store.delete_by_document(
source.collection_name,
str(doc.id),
- provider=collection.vector_store_provider,
)
storage = get_storage()
await storage.delete(doc.file_path)
diff --git a/api/bigrag/services/connectors/manifest.py b/api/bigrag/services/connectors/manifest.py
index e4358686..6b869b59 100644
--- a/api/bigrag/services/connectors/manifest.py
+++ b/api/bigrag/services/connectors/manifest.py
@@ -26,7 +26,6 @@ def collection_dict_for_sync(collection: Collection) -> dict[str, Any]:
"chunk_size": collection.chunk_size,
"chunk_overlap": collection.chunk_overlap,
"chunk_strategy": collection.chunk_strategy or "paragraph",
- "vector_store_provider": collection.vector_store_provider,
"tenant_field": collection.tenant_field,
"metadata_schema": collection.metadata_schema,
}
diff --git a/api/bigrag/services/health.py b/api/bigrag/services/health.py
index cacd6952..89aa6232 100644
--- a/api/bigrag/services/health.py
+++ b/api/bigrag/services/health.py
@@ -211,11 +211,6 @@ async def _check_redis():
healthy = False
else:
checks[name] = True
- checks["vector_store_provider"] = "per_collection"
- checks["qdrant"] = (
- checks["vector_store"] if "qdrant" in getattr(vs, "configured_providers", ()) else None
- )
-
embedding_result = await check_embedding_provider()
checks.update(embedding_result)
if not embedding_result.get("embedding"):
diff --git a/api/bigrag/services/ingestion_job.py b/api/bigrag/services/ingestion_job.py
index 3bad2281..00e928d3 100644
--- a/api/bigrag/services/ingestion_job.py
+++ b/api/bigrag/services/ingestion_job.py
@@ -17,7 +17,6 @@ class IngestionJob:
chunk_size: int
chunk_overlap: int
chunk_strategy: str = "paragraph"
- vector_store_provider: str = "qdrant"
tenant_field: str | None = None
embedding_base_url: str | None = None
multimodal_enabled: bool = False
@@ -45,7 +44,6 @@ def serialize(self) -> bytes:
"chunk_size": self.chunk_size,
"chunk_overlap": self.chunk_overlap,
"chunk_strategy": self.chunk_strategy,
- "vector_store_provider": self.vector_store_provider,
"tenant_field": self.tenant_field,
"attempt": self.attempt,
"max_attempts": self.max_attempts,
@@ -79,7 +77,6 @@ def create_ingestion_job(
chunk_size=collection["chunk_size"],
chunk_overlap=collection["chunk_overlap"],
chunk_strategy=collection.get("chunk_strategy") or "paragraph",
- vector_store_provider=collection.get("vector_store_provider") or "qdrant",
tenant_field=collection.get("tenant_field"),
multimodal_enabled=bool(collection.get("multimodal_enabled")),
multimodal_enrichment_enabled=bool(collection.get("multimodal_enrichment_enabled")),
diff --git a/api/bigrag/services/jobs/actors.py b/api/bigrag/services/jobs/actors.py
index c4dfd69b..f24204ad 100644
--- a/api/bigrag/services/jobs/actors.py
+++ b/api/bigrag/services/jobs/actors.py
@@ -71,10 +71,6 @@ def enqueue_backup_job(job_id: str) -> None:
run_backup.send(job_id)
-def enqueue_vector_migration_job(job_id: str) -> None:
- run_vector_migration.send(job_id)
-
-
def seed_periodic_jobs(enabled_queues: set[str] | None = None) -> None:
if enabled_queues is None or MAINTENANCE_QUEUE in enabled_queues:
_schedule_sync(
@@ -227,18 +223,6 @@ async def _run_backup(job_id: str) -> None:
await run_backup_job(job_id)
-@dramatiq.actor(queue_name=MAINTENANCE_QUEUE, max_retries=0, broker=broker)
-def run_vector_migration(job_id: str) -> None:
- _run(_run_vector_migration, job_id)
-
-
-async def _run_vector_migration(job_id: str) -> None:
- await ensure_worker_runtime()
- from bigrag.services.vector_migration import run_vector_migration_job
-
- await run_vector_migration_job(job_id)
-
-
@dramatiq.actor(queue_name=MAINTENANCE_QUEUE, max_retries=0, broker=broker)
def run_cleanup() -> None:
try:
diff --git a/api/bigrag/services/jobs/runtime.py b/api/bigrag/services/jobs/runtime.py
index 6fab5c0a..e569385d 100644
--- a/api/bigrag/services/jobs/runtime.py
+++ b/api/bigrag/services/jobs/runtime.py
@@ -3,6 +3,7 @@
import asyncio
import os
from datetime import UTC, datetime
+from typing import Any
from bigrag import __version__
from bigrag import config as config_module
@@ -31,8 +32,16 @@
_initialized = False
_storage = None
_heartbeat_task: asyncio.Task | None = None
+_vector_store_settings: tuple[object, ...] | None = None
_HEARTBEAT_SECONDS = 30
_HEARTBEAT_TTL_SECONDS = 120
+_RUNTIME_SETTING_KEYS = (
+ "ingestion_workers",
+ "turbopuffer_api_key",
+ "turbopuffer_base_url",
+ "turbopuffer_namespace_prefix",
+ "turbopuffer_region",
+)
_DEFAULT_QUEUES = {
INGESTION_QUEUE,
CONNECTORS_QUEUE,
@@ -46,6 +55,7 @@ async def ensure_worker_runtime() -> None:
global _initialized, _storage
async with _lock:
if _initialized:
+ await _sync_runtime_settings()
await start_worker_heartbeat()
return
settings = config_module.settings
@@ -61,30 +71,9 @@ async def ensure_worker_runtime() -> None:
await run_migrations()
configure_logging(log_level=settings.log_level, log_format=settings.log_format)
logger.info("worker migrations complete")
- runtime = await runtime_settings.get_values(
- [
- "ingestion_workers",
- "qdrant_connect_timeout_seconds",
- "qdrant_required",
- "qdrant_search_ef",
- "qdrant_url",
- "turbopuffer_api_key",
- "turbopuffer_namespace_prefix",
- "turbopuffer_region",
- ]
- )
+ runtime = await runtime_settings.get_values(list(_RUNTIME_SETTING_KEYS))
logger.info("worker runtime settings loaded")
- vector_store.configure(
- qdrant_url=runtime["qdrant_url"],
- connect_timeout_seconds=runtime["qdrant_connect_timeout_seconds"],
- search_ef=runtime["qdrant_search_ef"],
- turbopuffer_api_key=runtime["turbopuffer_api_key"],
- turbopuffer_region=runtime["turbopuffer_region"],
- turbopuffer_namespace_prefix=runtime["turbopuffer_namespace_prefix"],
- )
- vector_store.connect()
- await vector_store.health_check()
- logger.info("worker vector store ready")
+ await _sync_vector_store(runtime)
_storage = await init_storage_from_runtime(upload_dir=settings.upload_dir)
await redis_cache.connect(settings.redis_url)
await event_bus.connect(settings.redis_url)
@@ -99,6 +88,48 @@ async def ensure_worker_runtime() -> None:
logger.info("worker ready")
+async def _sync_runtime_settings() -> None:
+ runtime = await runtime_settings.get_values(list(_RUNTIME_SETTING_KEYS))
+ ingestion_queue._num_workers = runtime["ingestion_workers"]
+ await _sync_vector_store(runtime)
+
+
+async def _sync_vector_store(runtime: dict[str, Any]) -> None:
+ global _vector_store_settings
+ settings = (
+ runtime["turbopuffer_api_key"],
+ runtime["turbopuffer_base_url"],
+ runtime["turbopuffer_namespace_prefix"],
+ runtime["turbopuffer_region"],
+ )
+ if settings == _vector_store_settings:
+ return
+ if _vector_store_settings is not None:
+ await vector_store.close()
+ vector_store.configure(
+ turbopuffer_api_key=runtime["turbopuffer_api_key"],
+ turbopuffer_base_url=runtime["turbopuffer_base_url"],
+ turbopuffer_region=runtime["turbopuffer_region"],
+ turbopuffer_namespace_prefix=runtime["turbopuffer_namespace_prefix"],
+ )
+ if runtime["turbopuffer_api_key"]:
+ try:
+ vector_store.connect()
+ await vector_store.health_check()
+ logger.info("worker vector store ready")
+ except Exception as exc:
+ logger.warning(
+ "worker vector store connection failed; worker will continue degraded",
+ provider="turbopuffer",
+ error_type=exc.__class__.__name__,
+ error=str(exc),
+ )
+ else:
+ logger.warning("worker vector store not configured", provider="turbopuffer")
+ _vector_store_settings = settings
+ ingestion_queue.bind_vector_store(vector_store)
+
+
async def record_worker_heartbeat() -> None:
redis = ingestion_queue.redis
if redis is not None:
diff --git a/api/bigrag/services/maintenance.py b/api/bigrag/services/maintenance.py
index e5a57a58..08e6fb55 100644
--- a/api/bigrag/services/maintenance.py
+++ b/api/bigrag/services/maintenance.py
@@ -11,7 +11,6 @@
MAINTENANCE_LOCK_NAME = "maintenance"
BACKUP_LOCK_NAME = MAINTENANCE_LOCK_NAME
-VECTOR_MIGRATION_LOCK_NAME = MAINTENANCE_LOCK_NAME
class MaintenanceActiveError(RuntimeError):
diff --git a/api/bigrag/services/queue_embedding/insert.py b/api/bigrag/services/queue_embedding/insert.py
index 82242a67..8479caab 100644
--- a/api/bigrag/services/queue_embedding/insert.py
+++ b/api/bigrag/services/queue_embedding/insert.py
@@ -107,7 +107,6 @@ async def chunk_and_embed(
job.collection_name,
job.embedding_dimension,
tenant_field=getattr(job, "tenant_field", None),
- provider=job.vector_store_provider,
)
await ensure_job_current(job)
@@ -246,7 +245,6 @@ async def _embed_one_bounded(bn, bs, be, bc):
texts=batch_texts,
embeddings=embeddings,
metadata=metadata,
- provider=job.vector_store_provider,
)
try:
await ensure_job_current(job)
@@ -254,7 +252,6 @@ async def _embed_one_bounded(bn, bs, be, bc):
await vector_store.delete_by_document(
job.collection_name,
doc,
- provider=job.vector_store_provider,
)
raise
insert_elapsed = time.monotonic() - t1
@@ -266,7 +263,6 @@ async def _embed_one_bounded(bn, bs, be, bc):
await vector_store.delete_by_ids(
job.collection_name,
[f"{doc}_{i}" for i in range(batch_start, batch_end)],
- provider=job.vector_store_provider,
)
except Exception as cleanup_exc:
logger.warning(
diff --git a/api/bigrag/services/retrieval/modes.py b/api/bigrag/services/retrieval/modes.py
index a0e5d3c1..8104db3f 100644
--- a/api/bigrag/services/retrieval/modes.py
+++ b/api/bigrag/services/retrieval/modes.py
@@ -8,7 +8,7 @@
from bigrag.services.embedding import EmbeddingModel
from bigrag.services.retrieval.cache import embed_query_with_cache
from bigrag.services.retrieval.fusion import keyword_patterns, keyword_score, reciprocal_rank_fusion
-from bigrag.services.vector_store import VectorStoreFeatureError, VectorStoreProvider, vector_store
+from bigrag.services.vector_store import vector_store
async def keyword_search(
@@ -17,7 +17,6 @@ async def keyword_search(
query_terms: list[str],
top_k: int,
filter_expr: FilterExpression | None,
- vector_store_provider: VectorStoreProvider | None,
timings: dict[str, float],
) -> list[dict]:
t0 = time.monotonic()
@@ -27,9 +26,8 @@ async def keyword_search(
query_terms=query_terms,
top_k=top_k,
filters=filter_expr,
- provider=vector_store_provider,
)
- except VectorStoreFeatureError as exc:
+ except RuntimeError as exc:
raise ValidationError(str(exc)) from exc
timings["search_ms"] = (time.monotonic() - t0) * 1000
@@ -53,7 +51,6 @@ async def hybrid_search(
top_k: int,
max_top_k: int,
filter_expr: FilterExpression | None,
- vector_store_provider: VectorStoreProvider | None,
timings: dict[str, float],
) -> tuple[list[dict], list[float] | None]:
t0 = time.monotonic()
@@ -67,18 +64,16 @@ async def hybrid_search(
query_embedding=query_embedding,
top_k=fusion_pool,
filters=filter_expr,
- provider=vector_store_provider,
)
keyword_task = vector_store.text_search(
collection=collection_name,
query_terms=query_terms,
top_k=fusion_pool,
filters=filter_expr,
- provider=vector_store_provider,
)
try:
semantic_results, keyword_raw = await asyncio.gather(semantic_task, keyword_task)
- except VectorStoreFeatureError as exc:
+ except RuntimeError as exc:
raise ValidationError(str(exc)) from exc
timings["search_ms"] = (time.monotonic() - t0) * 1000
@@ -102,7 +97,6 @@ async def semantic_search(
embedding_model: EmbeddingModel,
top_k: int,
filter_expr: FilterExpression | None,
- vector_store_provider: VectorStoreProvider | None,
timings: dict[str, float],
) -> tuple[list[dict], list[float]]:
t0 = time.monotonic()
@@ -115,7 +109,6 @@ async def semantic_search(
query_embedding=query_embedding,
top_k=top_k,
filters=filter_expr,
- provider=vector_store_provider,
)
timings["search_ms"] = (time.monotonic() - t0) * 1000
return results, query_embedding
diff --git a/api/bigrag/services/retrieval/orchestrate.py b/api/bigrag/services/retrieval/orchestrate.py
index 6ed442b1..55006a8d 100644
--- a/api/bigrag/services/retrieval/orchestrate.py
+++ b/api/bigrag/services/retrieval/orchestrate.py
@@ -19,7 +19,6 @@
from bigrag.services.retrieval.modes import hybrid_search, keyword_search, semantic_search
from bigrag.services.retrieval.rerank import rerank_results
from bigrag.services.runtime_settings import get_values
-from bigrag.services.vector_store import VectorStoreProvider, vector_store
logger = get_logger("bigrag.retrieval")
@@ -40,10 +39,6 @@ def _on_done(t: asyncio.Task) -> None:
return task
-def _supports_text_search(provider: VectorStoreProvider | None) -> bool:
- return vector_store.supports_text_search_for(provider)
-
-
async def retrieve(
collection_name: str,
query: str,
@@ -54,7 +49,6 @@ async def retrieve(
search_mode: str = "semantic",
reranking_config: dict | None = None,
rerank_override: bool | None = None,
- vector_store_provider: VectorStoreProvider | None = None,
) -> RetrievalOutcome:
if top_k > MAX_TOP_K:
raise ValidationError(f"top_k {top_k} exceeds maximum {MAX_TOP_K}")
@@ -75,9 +69,6 @@ async def retrieve(
filter_expr = build_filter(filters) if filters else None
except ValueError as exc:
raise ValidationError(str(exc)) from exc
- if search_mode in {"keyword", "hybrid"} and not _supports_text_search(vector_store_provider):
- provider_label = vector_store_provider or vector_store.provider
- raise ValidationError(f"{provider_label} does not support {search_mode} search in v1")
query_terms = tokenize_query(query)
result_cache_key: str | None = None
@@ -126,7 +117,6 @@ async def retrieve(
query_terms=query_terms,
top_k=top_k,
filter_expr=filter_expr,
- vector_store_provider=vector_store_provider,
timings=timings,
)
elif search_mode == "hybrid":
@@ -138,7 +128,6 @@ async def retrieve(
top_k=top_k,
max_top_k=MAX_TOP_K,
filter_expr=filter_expr,
- vector_store_provider=vector_store_provider,
timings=timings,
)
else:
@@ -148,7 +137,6 @@ async def retrieve(
embedding_model=embedding_model,
top_k=top_k,
filter_expr=filter_expr,
- vector_store_provider=vector_store_provider,
timings=timings,
)
@@ -225,7 +213,6 @@ async def retrieve_multi(
search_mode: str = "semantic",
reranking_configs: dict[str, dict] | None = None,
rerank_override: bool | None = None,
- vector_store_providers: dict[str, VectorStoreProvider] | None = None,
) -> list[dict]:
if top_k > MAX_TOP_K:
raise ValidationError(f"top_k {top_k} exceeds maximum {MAX_TOP_K}")
@@ -242,7 +229,6 @@ async def search_one(col_name: str) -> list[dict]:
search_mode=search_mode,
reranking_config=col_reranking,
rerank_override=rerank_override,
- vector_store_provider=(vector_store_providers or {}).get(col_name),
)
for r in outcome.results:
r["collection"] = col_name
diff --git a/api/bigrag/services/runtime_setting_specs/vector_store.py b/api/bigrag/services/runtime_setting_specs/vector_store.py
index 1080ec7f..c7f51aa4 100644
--- a/api/bigrag/services/runtime_setting_specs/vector_store.py
+++ b/api/bigrag/services/runtime_setting_specs/vector_store.py
@@ -3,32 +3,6 @@
from bigrag.services.runtime_setting_specs._spec import SettingSpec
VECTOR_STORE_SPECS: tuple[SettingSpec, ...] = (
- SettingSpec(
- key="qdrant_url",
- group="vector_store",
- label="Qdrant URL",
- kind="string",
- default="http://localhost:6333",
- description="Qdrant connection URL.",
- ),
- SettingSpec(
- key="qdrant_connect_timeout_seconds",
- group="vector_store",
- label="Qdrant connect timeout",
- kind="int",
- default=10,
- description="Qdrant startup connection timeout in seconds.",
- min=0,
- max=300,
- ),
- SettingSpec(
- key="qdrant_required",
- group="vector_store",
- label="Require vector store",
- kind="bool",
- default=False,
- description="Fail startup if configured vector-store clients cannot be reached.",
- ),
SettingSpec(
key="turbopuffer_api_key",
group="vector_store",
@@ -38,6 +12,14 @@
description="turbopuffer API key.",
secret=True,
),
+ SettingSpec(
+ key="turbopuffer_base_url",
+ group="vector_store",
+ label="turbopuffer base URL",
+ kind="string",
+ default=None,
+ description="Optional turbopuffer API base URL.",
+ ),
SettingSpec(
key="turbopuffer_region",
group="vector_store",
@@ -54,14 +36,4 @@
default="bigrag_",
description="Prefix prepended to turbopuffer namespace names.",
),
- SettingSpec(
- key="qdrant_search_ef",
- group="vector_store",
- label="Qdrant search ef",
- kind="int",
- default=None,
- description="Optional Qdrant HNSW search ef override.",
- min=1,
- max=10000,
- ),
)
diff --git a/api/bigrag/services/runtime_settings_apply.py b/api/bigrag/services/runtime_settings_apply.py
index 711d5549..f9784f4c 100644
--- a/api/bigrag/services/runtime_settings_apply.py
+++ b/api/bigrag/services/runtime_settings_apply.py
@@ -42,10 +42,8 @@
}
VECTOR_CONFIG_KEYS = {
- "qdrant_connect_timeout_seconds",
- "qdrant_search_ef",
- "qdrant_url",
"turbopuffer_api_key",
+ "turbopuffer_base_url",
"turbopuffer_namespace_prefix",
"turbopuffer_region",
}
@@ -155,8 +153,9 @@ async def _prepare_vector_backend(values: dict[str, Any]) -> VectorStore:
store = VectorStore()
_configure_vector_store(store, values)
try:
- store.connect()
- await store.health_check()
+ if values.get("turbopuffer_api_key"):
+ store.connect()
+ await store.health_check()
return store
except Exception:
await store.close()
@@ -165,10 +164,8 @@ async def _prepare_vector_backend(values: dict[str, Any]) -> VectorStore:
def _configure_vector_store(store: VectorStore, values: dict[str, Any]) -> None:
store.configure(
- qdrant_url=values["qdrant_url"],
- connect_timeout_seconds=values["qdrant_connect_timeout_seconds"],
- search_ef=values["qdrant_search_ef"],
turbopuffer_api_key=values["turbopuffer_api_key"],
+ turbopuffer_base_url=values["turbopuffer_base_url"],
turbopuffer_region=values["turbopuffer_region"],
turbopuffer_namespace_prefix=values["turbopuffer_namespace_prefix"],
)
diff --git a/api/bigrag/services/vector_migration/__init__.py b/api/bigrag/services/vector_migration/__init__.py
deleted file mode 100644
index 28c5b5a8..00000000
--- a/api/bigrag/services/vector_migration/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import annotations
-
-from bigrag.services.vector_migration.jobs import (
- VectorMigrationConflictError,
- VectorMigrationError,
- create_vector_migration_job,
- delete_vector_migration_job,
- run_vector_migration_job,
-)
-
-__all__ = [
- "VectorMigrationConflictError",
- "VectorMigrationError",
- "create_vector_migration_job",
- "delete_vector_migration_job",
- "run_vector_migration_job",
-]
diff --git a/api/bigrag/services/vector_migration/jobs.py b/api/bigrag/services/vector_migration/jobs.py
deleted file mode 100644
index b4cf092c..00000000
--- a/api/bigrag/services/vector_migration/jobs.py
+++ /dev/null
@@ -1,522 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import uuid
-from datetime import UTC, datetime
-from typing import Any
-
-import sqlalchemy as sa
-
-from bigrag.db.engine import session_factory
-from bigrag.db.models import AuditLog, BackupJob, Collection, ConnectorSyncJob, VectorMigrationJob
-from bigrag.logging import get_logger
-from bigrag.services import collection_cache
-from bigrag.services.error_sanitize import sanitize_message_text
-from bigrag.services.maintenance import (
- acquire_maintenance_lock,
- active_lock,
- release_maintenance_lock,
-)
-from bigrag.services.queue import ingestion_queue
-from bigrag.services.retrieval import invalidate_collection_query_cache
-from bigrag.services.runtime_settings import get_value
-from bigrag.services.vector_store import vector_store
-from bigrag.services.vector_store._util import validate_provider
-from bigrag.services.vector_store.base import _FIXED_PAYLOAD_FIELDS
-
-logger = get_logger("bigrag.vector_migration")
-
-ACTIVE_STATUSES = ("pending", "running", "canceling")
-ACTIVE_BACKUP_STATUSES = ("pending", "running")
-
-
-class VectorMigrationError(RuntimeError):
- pass
-
-
-class VectorMigrationConflictError(VectorMigrationError):
- pass
-
-
-class VectorMigrationCanceledError(VectorMigrationError):
- pass
-
-
-async def create_vector_migration_job(
- *,
- collection_name: str,
- target_provider: str,
- created_by: uuid.UUID | None,
-) -> VectorMigrationJob:
- target = validate_provider(target_provider)
- lock = await active_lock()
- if lock is not None:
- raise VectorMigrationConflictError(f"Instance maintenance active: {lock.reason}")
- if target not in vector_store.configured_providers:
- raise VectorMigrationError(f"{target} vector store is not configured")
- async with session_factory()() as session:
- async with session.begin():
- active_backup = await session.scalar(
- sa.select(BackupJob)
- .where(BackupJob.status.in_(ACTIVE_BACKUP_STATUSES))
- .order_by(BackupJob.created_at.desc())
- .limit(1)
- .with_for_update()
- )
- if active_backup is not None:
- raise VectorMigrationConflictError("A backup is already pending or running")
- active = await session.scalar(
- sa.select(VectorMigrationJob)
- .where(VectorMigrationJob.status.in_(ACTIVE_STATUSES))
- .order_by(VectorMigrationJob.created_at.desc())
- .limit(1)
- .with_for_update()
- )
- if active is not None:
- raise VectorMigrationConflictError(
- "A vector migration is already pending, running, or canceling"
- )
- collection = await session.scalar(
- sa.select(Collection).where(Collection.name == collection_name).with_for_update()
- )
- if collection is None:
- raise VectorMigrationError("Collection not found")
- source = validate_provider(collection.vector_store_provider)
- if source == target:
- raise VectorMigrationConflictError("Collection already uses that vector provider")
- job = VectorMigrationJob(
- collection_id=collection.id,
- collection_name=collection.name,
- source_provider=source,
- target_provider=target,
- created_by=created_by,
- )
- session.add(job)
- await session.refresh(job)
- return job
-
-
-async def delete_vector_migration_job(job_id: uuid.UUID) -> str | None:
- async with session_factory()() as session:
- async with session.begin():
- job = await session.scalar(
- sa.select(VectorMigrationJob)
- .where(VectorMigrationJob.id == job_id)
- .with_for_update()
- )
- if job is None:
- return None
- if job.status == "pending" or job.status in {"succeeded", "failed"}:
- await session.delete(job)
- return "deleted"
- details = dict(job.details or {})
- details["delete_requested"] = True
- job.details = details
- job.status = "canceling"
- job.phase = "canceling"
- job.updated_at = datetime.now(UTC)
- return "stop_requested"
-
-
-async def run_vector_migration_job(job_id: str) -> None:
- owner_id = uuid.UUID(job_id)
- locked = False
- try:
- job = await _get_job(owner_id)
- if job is None:
- return
- if job.status != "pending":
- return
- locked = await acquire_maintenance_lock(
- owner_id,
- reason=f"vector migration for {job.collection_name}",
- metadata={
- "collection": job.collection_name,
- "source_provider": job.source_provider,
- "target_provider": job.target_provider,
- },
- )
- if not locked:
- await _fail_job(owner_id, "Another maintenance lock is active")
- return
- if not await _mark_running(owner_id):
- return
- await _raise_if_delete_requested(owner_id)
- await _wait_for_connector_sync_drain(owner_id)
- await _wait_for_ingestion_drain(owner_id)
- await _run_locked_migration(owner_id)
- except VectorMigrationCanceledError:
- await _delete_job(owner_id, "vector_migration.deleted", {"reason": "canceled"})
- except Exception as exc:
- logger.exception("vector migration failed", job_id=job_id, error=str(exc))
- await _fail_job(owner_id, sanitize_message_text(str(exc)) or "Vector migration failed")
- finally:
- if locked:
- await release_maintenance_lock(owner_id)
-
-
-async def _run_locked_migration(job_id: uuid.UUID) -> None:
- job, collection = await _load_job_and_collection(job_id)
- if job is None:
- return
- if collection is None:
- await _fail_job(job_id, "Collection not found")
- return
- source = validate_provider(job.source_provider)
- target = validate_provider(job.target_provider)
- cutover_done = False
- copied = 0
- try:
- if collection.vector_store_provider != source:
- raise VectorMigrationError(
- "Collection vector provider changed before migration started"
- )
- await _raise_if_delete_requested(job_id)
- await _update_job(job_id, phase="provisioning", progress=0.2)
- await vector_store.delete_collection(collection.name, provider=target)
- await _raise_if_delete_requested(job_id)
- await vector_store.create_collection(
- collection.name,
- collection.dimension,
- index_type=collection.index_type,
- tenant_field=collection.tenant_field,
- provider=target,
- )
- await _raise_if_delete_requested(job_id)
- copied = await _copy_points(job_id, collection.name, source, target)
- await _raise_if_delete_requested(job_id)
- await _update_job(
- job_id,
- phase="verifying",
- progress=0.86,
- copied_points=copied,
- total_points=copied,
- )
- target_count = 0 if copied == 0 else await _count_points(collection.name, target)
- if target_count != copied:
- raise VectorMigrationError(
- f"Target point count mismatch: copied {copied}, target has {target_count}"
- )
- await _raise_if_delete_requested(job_id)
- await _cutover_collection(job_id, collection.id, collection.name, source, target)
- cutover_done = True
- await _update_job(
- job_id,
- phase="cleanup",
- progress=0.94,
- copied_points=copied,
- total_points=copied,
- )
- await vector_store.delete_collection(collection.name, provider=source)
- await _complete_job(job_id, copied)
- except VectorMigrationCanceledError:
- if not cutover_done:
- try:
- await vector_store.delete_collection(collection.name, provider=target)
- except Exception as cleanup_exc:
- logger.warning(
- "canceled vector migration cleanup failed",
- collection=collection.name,
- target_provider=target,
- error=str(cleanup_exc),
- )
- await _delete_job(job_id, "vector_migration.deleted", {"reason": "canceled"})
- except Exception as exc:
- message = sanitize_message_text(str(exc)) or "Vector migration failed"
- if not cutover_done:
- try:
- await vector_store.delete_collection(collection.name, provider=target)
- except Exception as cleanup_exc:
- logger.warning(
- "partial vector migration cleanup failed",
- collection=collection.name,
- target_provider=target,
- error=str(cleanup_exc),
- )
- await _fail_job(
- job_id,
- message,
- phase="cleanup_failed" if cutover_done else "failed",
- copied_points=copied,
- total_points=copied or None,
- )
-
-
-async def _copy_points(
- job_id: uuid.UUID,
- collection: str,
- source: str,
- target: str,
-) -> int:
- batch_size = max(1, min(int(await get_value("ingestion_batch_size")), 1000))
- copied = 0
- batch: list[dict[str, Any]] = []
- await _raise_if_delete_requested(job_id)
- await _update_job(job_id, phase="copying", progress=0.32)
- async for point in vector_store.iter_collection_points(
- collection,
- with_vectors=True,
- provider=source,
- ):
- await _raise_if_delete_requested(job_id)
- batch.append(_normalise_point(point))
- if len(batch) >= batch_size:
- copied += await _insert_batch(collection, target, batch)
- batch.clear()
- await _update_job(
- job_id,
- copied_points=copied,
- progress=min(0.84, 0.34 + copied / (copied + batch_size) * 0.48),
- )
- if batch:
- await _raise_if_delete_requested(job_id)
- copied += await _insert_batch(collection, target, batch)
- await _update_job(job_id, copied_points=copied, progress=0.84)
- return copied
-
-
-def _normalise_point(point: dict[str, Any]) -> dict[str, Any]:
- payload = dict(point.get("payload") or {})
- vector = point.get("vector")
- if vector is None:
- raise VectorMigrationError("Source point is missing its vector")
- public_id = str(payload.get("id") or point.get("id") or "")
- if not public_id:
- raise VectorMigrationError("Source point is missing its id")
- return {
- "id": public_id,
- "document_id": str(payload.get("document_id") or ""),
- "chunk_index": int(payload.get("chunk_index") or 0),
- "text": str(payload.get("text") or ""),
- "vector": vector,
- "metadata": {
- k: v for k, v in payload.items() if k not in _FIXED_PAYLOAD_FIELDS and v is not None
- },
- }
-
-
-async def _insert_batch(
- collection: str,
- target: str,
- batch: list[dict[str, Any]],
-) -> int:
- return await vector_store.insert(
- collection=collection,
- ids=[item["id"] for item in batch],
- document_ids=[item["document_id"] for item in batch],
- chunk_indices=[item["chunk_index"] for item in batch],
- texts=[item["text"] for item in batch],
- embeddings=[item["vector"] for item in batch],
- metadata=[item["metadata"] for item in batch],
- provider=target,
- )
-
-
-async def _count_points(collection: str, provider: str) -> int:
- count = 0
- async for _point in vector_store.iter_collection_points(
- collection,
- with_vectors=False,
- provider=provider,
- ):
- count += 1
- return count
-
-
-async def _cutover_collection(
- job_id: uuid.UUID,
- collection_id: uuid.UUID,
- collection_name: str,
- source: str,
- target: str,
-) -> None:
- await _update_job(job_id, phase="cutover", progress=0.9)
- async with session_factory()() as session:
- async with session.begin():
- collection = await session.scalar(
- sa.select(Collection).where(Collection.id == collection_id).with_for_update()
- )
- if collection is None:
- raise VectorMigrationError("Collection not found during cutover")
- if collection.vector_store_provider != source:
- raise VectorMigrationError("Collection vector provider changed during migration")
- collection.vector_store_provider = target
- await collection_cache.invalidate(collection_name)
- await invalidate_collection_query_cache(collection_name)
-
-
-async def _wait_for_ingestion_drain(job_id: uuid.UUID, max_wait_seconds: int = 1800) -> None:
- deadline = asyncio.get_event_loop().time() + max_wait_seconds
- while True:
- await _raise_if_delete_requested(job_id)
- stats = await ingestion_queue.stats
- processing = int(stats.get("processing") or 0)
- if processing <= 0:
- return
- if asyncio.get_event_loop().time() >= deadline:
- raise VectorMigrationError(
- f"Timed out waiting for ingestion drain after {max_wait_seconds}s"
- )
- await _update_job(job_id, phase="draining", progress=0.12)
- await asyncio.sleep(1)
-
-
-async def _wait_for_connector_sync_drain(job_id: uuid.UUID, max_wait_seconds: int = 1800) -> None:
- deadline = asyncio.get_event_loop().time() + max_wait_seconds
- while True:
- await _raise_if_delete_requested(job_id)
- async with session_factory()() as session:
- running = await session.scalar(
- sa.select(sa.func.count())
- .select_from(ConnectorSyncJob)
- .where(ConnectorSyncJob.status == "running")
- )
- if int(running or 0) <= 0:
- return
- if asyncio.get_event_loop().time() >= deadline:
- raise VectorMigrationError(
- f"Timed out waiting for connector sync drain after {max_wait_seconds}s"
- )
- await _update_job(job_id, phase="draining", progress=0.08)
- await asyncio.sleep(1)
-
-
-async def _load_job_and_collection(
- job_id: uuid.UUID,
-) -> tuple[VectorMigrationJob | None, Collection | None]:
- async with session_factory()() as session:
- job = await session.get(VectorMigrationJob, job_id)
- if job is None:
- return None, None
- collection = await session.scalar(
- sa.select(Collection).where(Collection.name == job.collection_name)
- )
- return job, collection
-
-
-async def _get_job(job_id: uuid.UUID) -> VectorMigrationJob | None:
- async with session_factory()() as session:
- return await session.get(VectorMigrationJob, job_id)
-
-
-async def _mark_running(job_id: uuid.UUID) -> bool:
- updated = await _update_job(
- job_id,
- status="running",
- phase="draining",
- progress=0.04,
- started_at=datetime.now(UTC),
- )
- if updated <= 0:
- return False
- await _insert_audit(job_id, "vector_migration.start", {})
- return True
-
-
-async def _complete_job(job_id: uuid.UUID, copied_points: int) -> None:
- await _update_job(
- job_id,
- status="succeeded",
- phase="complete",
- progress=1.0,
- copied_points=copied_points,
- total_points=copied_points,
- completed_at=datetime.now(UTC),
- )
- await _insert_audit(job_id, "vector_migration.succeeded", {"copied_points": copied_points})
- if await _delete_requested(job_id):
- await _delete_job(
- job_id,
- "vector_migration.deleted",
- {"reason": "completed_after_delete_request"},
- )
-
-
-async def _fail_job(
- job_id: uuid.UUID,
- message: str,
- *,
- phase: str = "failed",
- copied_points: int | None = None,
- total_points: int | None = None,
-) -> None:
- values: dict[str, Any] = {
- "status": "failed",
- "phase": phase,
- "error_message": sanitize_message_text(message),
- "completed_at": datetime.now(UTC),
- }
- if copied_points is not None:
- values["copied_points"] = copied_points
- if total_points is not None:
- values["total_points"] = total_points
- await _update_job(job_id, **values)
- await _insert_audit(job_id, "vector_migration.failed", {"error": values["error_message"]})
- if await _delete_requested(job_id):
- await _delete_job(
- job_id,
- "vector_migration.deleted",
- {"reason": "failed_after_delete_request"},
- )
-
-
-async def _update_job(job_id: uuid.UUID, **values: Any) -> int:
- async with session_factory()() as session:
- values["updated_at"] = sa.func.now()
- result = await session.execute(
- sa.update(VectorMigrationJob).where(VectorMigrationJob.id == job_id).values(**values)
- )
- await session.commit()
- return result.rowcount or 0
-
-
-async def _insert_audit(job_id: uuid.UUID, action: str, metadata: dict[str, Any]) -> None:
- async with session_factory()() as session:
- job = await session.get(VectorMigrationJob, job_id)
- session.add(
- AuditLog(
- actor_id=job.created_by if job else None,
- actor_email=None,
- api_key_id=None,
- action=action,
- resource_type="vector_migration_job",
- resource_id=str(job_id),
- meta=metadata,
- ip=None,
- user_agent=None,
- )
- )
- await session.commit()
-
-
-async def _delete_requested(job_id: uuid.UUID) -> bool:
- job = await _get_job(job_id)
- if job is None:
- return False
- return job.status == "canceling" or bool((job.details or {}).get("delete_requested"))
-
-
-async def _raise_if_delete_requested(job_id: uuid.UUID) -> None:
- if await _delete_requested(job_id):
- raise VectorMigrationCanceledError("Vector migration deletion requested")
-
-
-async def _delete_job(job_id: uuid.UUID, action: str, metadata: dict[str, Any]) -> None:
- async with session_factory()() as session:
- job = await session.get(VectorMigrationJob, job_id)
- session.add(
- AuditLog(
- actor_id=job.created_by if job else None,
- actor_email=None,
- api_key_id=None,
- action=action,
- resource_type="vector_migration_job",
- resource_id=str(job_id),
- meta=metadata,
- ip=None,
- user_agent=None,
- )
- )
- if job is not None:
- await session.delete(job)
- await session.commit()
diff --git a/api/bigrag/services/vector_store/__init__.py b/api/bigrag/services/vector_store/__init__.py
index dd050983..a171a352 100644
--- a/api/bigrag/services/vector_store/__init__.py
+++ b/api/bigrag/services/vector_store/__init__.py
@@ -1,16 +1,10 @@
from __future__ import annotations
-from bigrag.services.vector_store.base import (
- VectorStoreBackend,
- VectorStoreFeatureError,
- VectorStoreProvider,
-)
+from bigrag.services.vector_store.base import VectorStoreBackend
from bigrag.services.vector_store.facade import VectorStore, vector_store
__all__ = [
"VectorStore",
"VectorStoreBackend",
- "VectorStoreFeatureError",
- "VectorStoreProvider",
"vector_store",
]
diff --git a/api/bigrag/services/vector_store/_util.py b/api/bigrag/services/vector_store/_util.py
deleted file mode 100644
index 4b847419..00000000
--- a/api/bigrag/services/vector_store/_util.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from __future__ import annotations
-
-from typing import cast
-
-from bigrag.logging import get_logger
-from bigrag.services.vector_store.base import VectorStoreBackend, VectorStoreProvider
-
-logger = get_logger("bigrag.vector_store")
-
-PROVIDERS: tuple[VectorStoreProvider, ...] = ("qdrant", "turbopuffer")
-
-
-def validate_provider(value: str) -> VectorStoreProvider:
- if value not in PROVIDERS:
- raise ValueError(f"Unsupported vector store provider: {value}")
- return cast(VectorStoreProvider, value)
-
-
-async def close_backends(
- backends: dict[VectorStoreProvider, VectorStoreBackend],
- *,
- log_errors: bool = False,
-) -> None:
- seen: set[int] = set()
- for backend in backends.values():
- ident = id(backend)
- if ident in seen:
- continue
- seen.add(ident)
- try:
- await backend.close()
- except Exception as exc:
- if log_errors:
- logger.warning("old vector store close failed", error=str(exc))
- else:
- raise
diff --git a/api/bigrag/services/vector_store/base.py b/api/bigrag/services/vector_store/base.py
index 6992ef0f..ffbda9af 100644
--- a/api/bigrag/services/vector_store/base.py
+++ b/api/bigrag/services/vector_store/base.py
@@ -2,12 +2,10 @@
import uuid
from collections.abc import AsyncIterator
-from typing import Literal, Protocol
+from typing import Protocol
from bigrag.services._retrieval_filters import FilterExpression
-VectorStoreProvider = Literal["qdrant", "turbopuffer"]
-
_POINT_NAMESPACE = uuid.UUID("1b04f7ca-0c3b-5d76-a5bb-6e4b4a40f61d")
_FIXED_PAYLOAD_FIELDS = {"id", "text", "document_id", "chunk_index", "embedding"}
DEFAULT_SEARCH_PAYLOAD_FIELDS: list[str] = [
@@ -23,14 +21,7 @@
]
-class VectorStoreFeatureError(RuntimeError):
- pass
-
-
class VectorStoreBackend(Protocol):
- provider: VectorStoreProvider
- supports_text_search: bool
-
def connect(self) -> None: ...
async def close(self) -> None: ...
@@ -41,7 +32,6 @@ async def create_collection(
self,
name: str,
dimension: int,
- index_type: str = "HNSW",
tenant_field: str | None = None,
) -> None: ...
diff --git a/api/bigrag/services/vector_store/facade.py b/api/bigrag/services/vector_store/facade.py
index 956a9139..055e5fa3 100644
--- a/api/bigrag/services/vector_store/facade.py
+++ b/api/bigrag/services/vector_store/facade.py
@@ -5,99 +5,50 @@
from contextlib import asynccontextmanager
from typing import Any
-from bigrag.config import settings as _app_settings
-from bigrag.logging import get_logger
from bigrag.services._retrieval_filters import FilterExpression
-from bigrag.services.error_sanitize import sanitize_message_text
-from bigrag.services.vector_store._util import PROVIDERS, close_backends, validate_provider
-from bigrag.services.vector_store.base import VectorStoreBackend, VectorStoreProvider
-from bigrag.services.vector_store.qdrant import QdrantVectorStore
+from bigrag.services.vector_store.base import VectorStoreBackend
from bigrag.services.vector_store.turbopuffer import TurbopufferVectorStore
-logger = get_logger("bigrag.vector_store")
-
class VectorStore:
def __init__(self) -> None:
- self.provider: str = "collection"
- self.backends: dict[VectorStoreProvider, VectorStoreBackend] = {}
- self._configured_providers: set[VectorStoreProvider] = {"qdrant"}
- self._fallback_provider: VectorStoreProvider = "qdrant"
- self.backend = QdrantVectorStore()
+ self._backend_instance: VectorStoreBackend = TurbopufferVectorStore()
self.client: Any | None = None
self._condition = asyncio.Condition()
self._active = 0
self._swapping = False
+ def is_configured(self) -> bool:
+ return bool(getattr(self.backend, "api_key", None))
+
@property
def backend(self) -> VectorStoreBackend:
- return self.backends[self._fallback_provider]
+ return self._backend_instance
@backend.setter
def backend(self, value: VectorStoreBackend) -> None:
- provider = validate_provider(getattr(value, "provider", self._fallback_provider))
- self.backends[provider] = value
- self._fallback_provider = provider
- self._configured_providers.add(provider)
+ self._backend_instance = value
self._sync_client()
def configure(
self,
url: str | None = None,
*,
- provider: VectorStoreProvider | None = None,
- connect_timeout_seconds: int | float | None = 10,
- search_ef: int | None = None,
- qdrant_url: str | None = None,
- qdrant_prefer_grpc: bool | None = None,
- qdrant_grpc_port: int | None = None,
turbopuffer_api_key: str | None = None,
turbopuffer_region: str = "aws-us-east-1",
turbopuffer_namespace_prefix: str = "bigrag_",
+ turbopuffer_base_url: str | None = None,
+ **_: Any,
) -> None:
- if provider is not None:
- validate_provider(provider)
- prefer_grpc = (
- qdrant_prefer_grpc
- if qdrant_prefer_grpc is not None
- else _app_settings.qdrant_prefer_grpc
+ self.backend = TurbopufferVectorStore(
+ api_key=turbopuffer_api_key,
+ region=turbopuffer_region or "aws-us-east-1",
+ namespace_prefix=turbopuffer_namespace_prefix,
+ base_url=turbopuffer_base_url,
)
- grpc_port = (
- qdrant_grpc_port if qdrant_grpc_port is not None else _app_settings.qdrant_grpc_port
- )
- self.backends = {
- "qdrant": QdrantVectorStore(
- qdrant_url or url or "http://localhost:6333",
- connect_timeout_seconds=connect_timeout_seconds,
- search_ef=search_ef,
- prefer_grpc=prefer_grpc,
- grpc_port=grpc_port,
- ),
- "turbopuffer": TurbopufferVectorStore(
- api_key=turbopuffer_api_key,
- region=turbopuffer_region,
- namespace_prefix=turbopuffer_namespace_prefix,
- ),
- }
- self._configured_providers = {"qdrant"}
- if turbopuffer_api_key or provider == "turbopuffer":
- self._configured_providers.add("turbopuffer")
- self._fallback_provider = provider or "qdrant"
- self.provider = provider or "collection"
- self._sync_client()
-
- def supports_text_search_for(self, provider: VectorStoreProvider | None = None) -> bool:
- return self.backends[
- validate_provider(provider or self._fallback_provider)
- ].supports_text_search
-
- @property
- def configured_providers(self) -> tuple[VectorStoreProvider, ...]:
- return tuple(provider for provider in PROVIDERS if provider in self._configured_providers)
def connect(self) -> None:
- for provider in self.configured_providers:
- self.backends[provider].connect()
+ self.backend.connect()
self._sync_client()
async def close(self) -> None:
@@ -106,7 +57,7 @@ async def close(self) -> None:
try:
while self._active:
await self._condition.wait()
- await close_backends(self.backends)
+ await self.backend.close()
self._sync_client()
finally:
self._swapping = False
@@ -117,23 +68,23 @@ async def replace_with(self, other: VectorStore) -> None:
self._swapping = True
while self._active:
await self._condition.wait()
- old_backends = dict(self.backends)
- self.provider = other.provider
- self.backends = dict(other.backends)
- self._configured_providers = set(other._configured_providers)
- self._fallback_provider = other._fallback_provider
+ old_backend = self.backend
+ self.backend = other.backend
self._sync_client()
- await close_backends(old_backends, log_errors=True)
+ try:
+ await old_backend.close()
+ except Exception:
+ pass
self._swapping = False
self._condition.notify_all()
@asynccontextmanager
- async def _backend(self, provider: VectorStoreProvider) -> AsyncIterator[VectorStoreBackend]:
+ async def _backend(self) -> AsyncIterator[VectorStoreBackend]:
async with self._condition:
while self._swapping:
await self._condition.wait()
self._active += 1
- backend = self.backends[provider]
+ backend = self.backend
try:
yield backend
finally:
@@ -143,102 +94,33 @@ async def _backend(self, provider: VectorStoreProvider) -> AsyncIterator[VectorS
self._condition.notify_all()
async def health_check(self) -> None:
- errors: list[str] = []
- for provider in self.configured_providers:
- try:
- async with self._backend(provider) as backend:
- await backend.health_check()
- except Exception as exc:
- logger.warning(
- "vector store provider unhealthy",
- provider=provider,
- error_type=type(exc).__name__,
- )
- errors.append(f"{provider}: {type(exc).__name__}")
- if errors:
- raise RuntimeError("; ".join(errors))
-
- async def provider_health(self) -> dict[str, dict[str, object]]:
- results: dict[str, dict[str, object]] = {}
- for provider in PROVIDERS:
- configured = provider in self._configured_providers
- if not configured:
- results[provider] = {"configured": False, "status": "not_configured", "error": None}
- continue
- try:
- async with self._backend(provider) as backend:
- await backend.health_check()
- results[provider] = {"configured": True, "status": "ok", "error": None}
- except Exception as exc:
- logger.warning(
- "vector store provider_health error",
- provider=provider,
- error_type=type(exc).__name__,
- )
- results[provider] = {
- "configured": True,
- "status": "error",
- "error": sanitize_message_text(type(exc).__name__),
- }
- return results
+ async with self._backend() as backend:
+ await backend.health_check()
- def _client(self, provider: VectorStoreProvider | None = None) -> Any:
- backend = self.backends[validate_provider(provider or self._fallback_provider)]
- if isinstance(backend, QdrantVectorStore):
- return backend._client()
- client = getattr(backend, "client", None)
+ def _client(self) -> Any:
+ client = getattr(self.backend, "client", None)
if client is None:
- backend.connect()
- client = getattr(backend, "client", None)
+ self.backend.connect()
+ client = getattr(self.backend, "client", None)
return client
- async def _provider_for(
- self,
- collection: str,
- provider: VectorStoreProvider | None,
- ) -> VectorStoreProvider:
- if provider is not None:
- return validate_provider(provider)
- try:
- from bigrag.services.collection_cache import get_or_404
-
- value = (await get_or_404(collection)).get("vector_store_provider")
- if value:
- return validate_provider(str(value))
- except Exception:
- return self._fallback_provider
- return self._fallback_provider
-
def _sync_client(self) -> None:
- fallback = self.backends.get(self._fallback_provider)
- self.client = getattr(fallback, "client", None)
- if self.client is not None:
- return
- for provider in self.configured_providers:
- client = getattr(self.backends[provider], "client", None)
- if client is not None:
- self.client = client
- return
+ self.client = getattr(self.backend, "client", None)
async def create_collection(
self,
name: str,
dimension: int,
- index_type: str = "HNSW",
tenant_field: str | None = None,
- provider: VectorStoreProvider | None = None,
) -> None:
- selected_provider = await self._provider_for(name, provider)
- async with self._backend(selected_provider) as backend:
- await backend.create_collection(name, dimension, index_type, tenant_field)
+ async with self._backend() as backend:
+ await backend.create_collection(name, dimension, tenant_field)
async def delete_collection(
self,
name: str,
- provider: VectorStoreProvider | None = None,
) -> None:
- selected_provider = await self._provider_for(name, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
await backend.delete_collection(name)
async def insert(
@@ -250,10 +132,8 @@ async def insert(
texts: list[str],
embeddings: list[list[float]],
metadata: list[dict] | None = None,
- provider: VectorStoreProvider | None = None,
) -> int:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
return await backend.insert(
collection,
ids,
@@ -270,11 +150,9 @@ async def search(
query_embedding: list[float],
top_k: int = 10,
filters: FilterExpression | None = None,
- provider: VectorStoreProvider | None = None,
payload_fields: list[str] | None = None,
) -> list[dict]:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
return await backend.search(
collection,
query_embedding,
@@ -289,30 +167,24 @@ async def get_chunks(
document_id: str,
limit: int = 10000,
offset: int = 0,
- provider: VectorStoreProvider | None = None,
) -> tuple[list[dict], int]:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
return await backend.get_chunks(collection, document_id, limit, offset)
async def delete_by_document(
self,
collection: str,
document_id: str,
- provider: VectorStoreProvider | None = None,
) -> None:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
await backend.delete_by_document(collection, document_id)
async def delete_by_ids(
self,
collection: str,
ids: list[str],
- provider: VectorStoreProvider | None = None,
) -> None:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
await backend.delete_by_ids(collection, ids)
async def text_search(
@@ -321,10 +193,8 @@ async def text_search(
query_terms: list[str],
top_k: int = 10,
filters: FilterExpression | None = None,
- provider: VectorStoreProvider | None = None,
) -> list[dict]:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
return await backend.text_search(collection, query_terms, top_k, filters)
async def upsert(
@@ -334,10 +204,8 @@ async def upsert(
embeddings: list[list[float]],
texts: list[str],
metadata: list[dict] | None = None,
- provider: VectorStoreProvider | None = None,
) -> int:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
return await backend.upsert(collection, ids, embeddings, texts, metadata)
async def export_collection_points(
@@ -345,10 +213,8 @@ async def export_collection_points(
collection: str,
*,
with_vectors: bool = True,
- provider: VectorStoreProvider | None = None,
) -> list[dict]:
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
return await backend.export_collection_points(collection, with_vectors=with_vectors)
async def iter_collection_points(
@@ -356,10 +222,9 @@ async def iter_collection_points(
collection: str,
*,
with_vectors: bool = True,
- provider: VectorStoreProvider | None = None,
+ **_: Any,
):
- selected_provider = await self._provider_for(collection, provider)
- async with self._backend(selected_provider) as backend:
+ async with self._backend() as backend:
async for point in backend.iter_collection_points(
collection,
with_vectors=with_vectors,
diff --git a/api/bigrag/services/vector_store/qdrant.py b/api/bigrag/services/vector_store/qdrant.py
deleted file mode 100644
index 3c72ba31..00000000
--- a/api/bigrag/services/vector_store/qdrant.py
+++ /dev/null
@@ -1,499 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-from collections.abc import Awaitable, Callable
-from typing import Any
-
-import httpx
-from qdrant_client import AsyncQdrantClient, models
-
-from bigrag.logging import get_logger
-from bigrag.services._retrieval_filters import FilterExpression
-from bigrag.services.vector_store.base import (
- VectorStoreProvider,
- _backend_name,
- _build_payload,
- _chunk_rows_from_payloads,
- _point_id,
- _row_from_payload,
-)
-from bigrag.services.vector_store.qdrant_filter import combine_filters, to_qdrant_filter
-
-logger = get_logger("bigrag.vector_store")
-
-_TRANSIENT_ERRORS = (
- ConnectionError,
- TimeoutError,
- OSError,
- httpx.HTTPError,
-)
-
-
-class QdrantVectorStore:
- provider: VectorStoreProvider = "qdrant"
- supports_text_search = True
-
- def __init__(
- self,
- url: str = "http://localhost:6333",
- *,
- connect_timeout_seconds: int | float | None = 10,
- search_ef: int | None = None,
- prefix: str = "bigrag_",
- prefer_grpc: bool = False,
- grpc_port: int = 6334,
- ) -> None:
- self.url = url
- self.client: AsyncQdrantClient | None = None
- self._max_retries: int = 2
- self._connect_timeout_seconds = (
- None
- if connect_timeout_seconds is None or connect_timeout_seconds <= 0
- else float(connect_timeout_seconds)
- )
- self._search_ef = search_ef if search_ef and search_ef > 0 else None
- self.prefix = prefix
- self._prefer_grpc = bool(prefer_grpc)
- self._grpc_port = int(grpc_port) if grpc_port else 6334
-
- def connect(self) -> None:
- self.client = AsyncQdrantClient(
- url=self.url,
- timeout=self._connect_timeout_seconds,
- prefer_grpc=self._prefer_grpc,
- grpc_port=self._grpc_port,
- )
- logger.info(
- "connected to qdrant",
- url=self.url,
- prefer_grpc=self._prefer_grpc,
- grpc_port=self._grpc_port,
- )
-
- async def reconnect(self) -> None:
- logger.warning("reconnecting to qdrant", url=self.url)
- await self.close()
- self.connect()
- logger.info("reconnected to qdrant", url=self.url)
-
- async def _run_with_retry(
- self,
- fn: Callable[..., Awaitable[Any]],
- *args: Any,
- **kwargs: Any,
- ) -> Any:
- last_error = None
- for attempt in range(self._max_retries + 1):
- try:
- return await fn(*args, **kwargs)
- except _TRANSIENT_ERRORS as e:
- last_error = e
- if attempt < self._max_retries:
- logger.warning(
- "qdrant transient error",
- attempt=attempt + 1,
- max_attempts=self._max_retries + 1,
- error=repr(e),
- )
- await self.reconnect()
- else:
- raise
- except Exception as e:
- err_str = str(e).lower()
- if any(kw in err_str for kw in ("connect", "timeout", "unavailable", "reset")):
- last_error = e
- if attempt < self._max_retries:
- logger.warning(
- "qdrant likely transient error",
- attempt=attempt + 1,
- max_attempts=self._max_retries + 1,
- error=repr(e),
- )
- await self.reconnect()
- else:
- raise
- else:
- raise
- raise last_error
-
- async def close(self) -> None:
- if self.client:
- await self.client.close()
- self.client = None
- logger.info("qdrant connection closed")
-
- async def health_check(self) -> None:
- client = self._client()
- await self._run_with_retry(client.get_collections)
-
- def _client(self) -> AsyncQdrantClient:
- if self.client is None:
- self.connect()
- if self.client is None:
- raise RuntimeError("Qdrant client is not connected")
- return self.client
-
- def _col(self, name: str) -> str:
- return _backend_name(self.prefix, name)
-
- async def _create_payload_index(
- self,
- collection_name: str,
- field_name: str,
- schema: Any,
- ) -> None:
- client = self._client()
- try:
- await self._run_with_retry(
- client.create_payload_index,
- collection_name=collection_name,
- field_name=field_name,
- field_schema=schema,
- wait=True,
- )
- except Exception as exc:
- if "already exists" in str(exc).lower() or "exists" in str(exc).lower():
- return
- logger.warning(
- "vector_store: payload index creation failed",
- collection=collection_name,
- field=field_name,
- error=str(exc),
- )
-
- async def _ensure_payload_indexes(
- self,
- collection_name: str,
- tenant_field: str | None = None,
- ) -> None:
- text_schema = models.TextIndexParams(
- type=models.TextIndexType.TEXT,
- tokenizer=models.TokenizerType.WORD,
- min_token_len=2,
- lowercase=True,
- )
- indexes: list[tuple[str, Any]] = [
- ("id", "keyword"),
- ("document_id", "keyword"),
- ("chunk_index", "integer"),
- ("char_start", "integer"),
- ("char_end", "integer"),
- ("page_no", "integer"),
- ("text", text_schema),
- ]
- if tenant_field:
- indexes.append((tenant_field, "keyword"))
-
- await asyncio.gather(
- *[
- self._create_payload_index(collection_name, field_name, schema)
- for field_name, schema in indexes
- ]
- )
-
- async def create_collection(
- self,
- name: str,
- dimension: int,
- index_type: str = "HNSW",
- tenant_field: str | None = None,
- ) -> None:
- col = self._col(name)
- client = self._client()
-
- if not await self._run_with_retry(client.collection_exists, col):
- await self._run_with_retry(
- client.create_collection,
- collection_name=col,
- vectors_config=models.VectorParams(
- size=dimension,
- distance=models.Distance.COSINE,
- ),
- )
- logger.info(
- "created qdrant collection",
- collection=col,
- dimension=dimension,
- index=index_type,
- )
-
- await self._ensure_payload_indexes(col, tenant_field=tenant_field)
-
- async def delete_collection(self, name: str) -> None:
- col = self._col(name)
- client = self._client()
- if await self._run_with_retry(client.collection_exists, col):
- await self._run_with_retry(client.delete_collection, col)
- logger.info("dropped qdrant collection", collection=col)
-
- async def insert(
- self,
- collection: str,
- ids: list[str],
- document_ids: list[str],
- chunk_indices: list[int],
- texts: list[str],
- embeddings: list[list[float]],
- metadata: list[dict] | None = None,
- ) -> int:
- col = self._col(collection)
- points = []
- for i in range(len(ids)):
- points.append(
- models.PointStruct(
- id=_point_id(col, ids[i]),
- vector=embeddings[i],
- payload=_build_payload(
- id_=ids[i],
- document_id=document_ids[i],
- chunk_index=chunk_indices[i],
- text=texts[i],
- metadata=metadata[i] if metadata else None,
- ),
- )
- )
-
- client = self._client()
- await self._run_with_retry(client.upsert, collection_name=col, points=points, wait=True)
- logger.info("inserted vectors", collection=col, count=len(points))
- return len(points)
-
- def _search_params(self) -> models.SearchParams | None:
- if self._search_ef is None:
- return None
- return models.SearchParams(hnsw_ef=self._search_ef)
-
- @staticmethod
- def _row_from_qdrant(point: Any) -> dict:
- payload = dict(getattr(point, "payload", None) or {})
- point_id = str(getattr(point, "id", ""))
- return _row_from_payload(point_id, getattr(point, "score", 0.0), payload)
-
- async def search(
- self,
- collection: str,
- query_embedding: list[float],
- top_k: int = 10,
- filters: FilterExpression | None = None,
- payload_fields: list[str] | None = None,
- ) -> list[dict]:
- col = self._col(collection)
-
- with_payload: Any = (
- models.PayloadSelectorInclude(include=list(payload_fields)) if payload_fields else True
- )
-
- client = self._client()
- results = await self._run_with_retry(
- client.query_points,
- collection_name=col,
- query=query_embedding,
- limit=top_k,
- query_filter=to_qdrant_filter(filters),
- search_params=self._search_params(),
- with_payload=with_payload,
- with_vectors=False,
- )
-
- hits = [self._row_from_qdrant(point) for point in results.points]
- logger.info("vector search", collection=col, top_k=top_k, hits=len(hits), filters=filters)
- return hits
-
- async def get_chunks(
- self,
- collection: str,
- document_id: str,
- limit: int = 10000,
- offset: int = 0,
- ) -> tuple[list[dict], int]:
- col = self._col(collection)
- client = self._client()
- if not await self._run_with_retry(client.collection_exists, col):
- return [], 0
-
- doc_filter = models.Filter(
- must=[
- models.FieldCondition(
- key="document_id",
- match=models.MatchValue(value=document_id),
- )
- ]
- )
-
- try:
- count_resp = await self._run_with_retry(
- client.count,
- collection_name=col,
- count_filter=doc_filter,
- exact=True,
- )
- total = int(getattr(count_resp, "count", 0))
- except Exception:
- total = 0
-
- needed = offset + max(limit, 0)
- if needed <= 0:
- return [], total
-
- results = []
- next_offset = None
- page_size = min(max(needed, 256), 10000)
- while True:
- batch, next_offset = await self._run_with_retry(
- client.scroll,
- collection_name=col,
- scroll_filter=doc_filter,
- with_payload=True,
- with_vectors=False,
- limit=page_size,
- offset=next_offset,
- )
- results.extend(batch)
- if next_offset is None:
- break
- if total and len(results) >= total:
- break
- payloads = [r.payload or {} for r in results]
- rows, computed_total = _chunk_rows_from_payloads(payloads, limit, offset)
- return rows, total or computed_total
-
- async def delete_by_document(self, collection: str, document_id: str) -> None:
- col = self._col(collection)
- client = self._client()
- if not await self._run_with_retry(client.collection_exists, col):
- return
- await self._run_with_retry(
- client.delete,
- collection_name=col,
- points_selector=models.Filter(
- must=[
- models.FieldCondition(
- key="document_id",
- match=models.MatchValue(value=document_id),
- )
- ]
- ),
- wait=True,
- )
- logger.info("delete vectors by document", collection=col, document_id=document_id)
-
- async def delete_by_ids(self, collection: str, ids: list[str]) -> None:
- col = self._col(collection)
- client = self._client()
- point_ids = [_point_id(col, id_) for id_ in ids]
- await self._run_with_retry(
- client.delete,
- collection_name=col,
- points_selector=point_ids,
- wait=True,
- )
- logger.info("delete vectors by ids", collection=col, count=len(ids))
-
- async def text_search(
- self,
- collection: str,
- query_terms: list[str],
- top_k: int = 10,
- filters: FilterExpression | None = None,
- ) -> list[dict]:
- col = self._col(collection)
- terms = [term for term in query_terms if term]
- if not terms:
- return []
-
- text_filter = models.Filter(
- should=[
- models.FieldCondition(key="text", match=models.MatchText(text=term))
- for term in terms
- ]
- )
- combined_filter = combine_filters(to_qdrant_filter(filters), text_filter)
-
- try:
- client = self._client()
- results, _next_offset = await self._run_with_retry(
- client.scroll,
- collection_name=col,
- scroll_filter=combined_filter,
- with_payload=True,
- with_vectors=False,
- limit=top_k * 10,
- )
- except _TRANSIENT_ERRORS:
- raise
- except Exception as exc:
- logger.warning("text search query failed", collection=col, error=repr(exc))
- return []
-
- logger.info("text search", collection=col, terms=len(terms), hits=len(results))
- return [self._row_from_qdrant(point) for point in results]
-
- async def upsert(
- self,
- collection: str,
- ids: list[str],
- embeddings: list[list[float]],
- texts: list[str],
- metadata: list[dict] | None = None,
- ) -> int:
- col = self._col(collection)
- points = []
- for i in range(len(ids)):
- points.append(
- models.PointStruct(
- id=_point_id(col, ids[i]),
- vector=embeddings[i],
- payload=_build_payload(
- id_=ids[i],
- document_id="",
- chunk_index=0,
- text=texts[i],
- metadata=metadata[i] if metadata else None,
- ),
- )
- )
-
- client = self._client()
- await self._run_with_retry(client.upsert, collection_name=col, points=points, wait=True)
- logger.info("upserted vectors", collection=col, count=len(points))
- return len(points)
-
- async def export_collection_points(
- self,
- collection: str,
- *,
- with_vectors: bool = True,
- ) -> list[dict]:
- return [
- point
- async for point in self.iter_collection_points(collection, with_vectors=with_vectors)
- ]
-
- async def iter_collection_points(
- self,
- collection: str,
- *,
- with_vectors: bool = True,
- ):
- col = self._col(collection)
- client = self._client()
- if not await self._run_with_retry(client.collection_exists, col):
- return
- offset = None
- while True:
- points, offset = await self._run_with_retry(
- client.scroll,
- collection_name=col,
- limit=256,
- offset=offset,
- with_payload=True,
- with_vectors=with_vectors,
- )
- for point in points:
- yield {
- "id": str(getattr(point, "id", "")),
- "payload": getattr(point, "payload", {}) or {},
- "vector": getattr(point, "vector", None) if with_vectors else None,
- }
- if offset is None:
- break
diff --git a/api/bigrag/services/vector_store/qdrant_filter.py b/api/bigrag/services/vector_store/qdrant_filter.py
deleted file mode 100644
index ae1c44c0..00000000
--- a/api/bigrag/services/vector_store/qdrant_filter.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from __future__ import annotations
-
-from qdrant_client import models
-
-from bigrag.services._retrieval_filters import FilterExpression
-
-
-def to_qdrant_filter(filters: FilterExpression | None) -> models.Filter | None:
- if filters is None:
- return None
- must: list[models.Condition] = []
- must_not: list[models.Condition] = []
- for condition in filters.conditions:
- if condition.operator == "eq":
- must.append(
- models.FieldCondition(
- key=condition.field,
- match=models.MatchValue(value=condition.value),
- )
- )
- elif condition.operator == "ne":
- must_not.append(
- models.FieldCondition(
- key=condition.field,
- match=models.MatchValue(value=condition.value),
- )
- )
- elif condition.operator == "in":
- must.append(
- models.FieldCondition(
- key=condition.field,
- match=models.MatchAny(any=condition.value),
- )
- )
- else:
- must.append(
- models.FieldCondition(
- key=condition.field,
- range=models.Range(
- gt=condition.value if condition.operator == "gt" else None,
- gte=condition.value if condition.operator == "gte" else None,
- lt=condition.value if condition.operator == "lt" else None,
- lte=condition.value if condition.operator == "lte" else None,
- ),
- )
- )
- if not must and not must_not:
- return None
- return models.Filter(must=must or None, must_not=must_not or None)
-
-
-def combine_filters(
- *filters: models.Filter | None,
-) -> models.Filter | None:
- active = [f for f in filters if f is not None]
- if not active:
- return None
- if len(active) == 1:
- return active[0]
- return models.Filter(must=active)
diff --git a/api/bigrag/services/vector_store/turbopuffer.py b/api/bigrag/services/vector_store/turbopuffer.py
index 45d15492..0cfefd63 100644
--- a/api/bigrag/services/vector_store/turbopuffer.py
+++ b/api/bigrag/services/vector_store/turbopuffer.py
@@ -2,13 +2,11 @@
from typing import Any
-import httpx
+from turbopuffer import AsyncTurbopuffer, NotFoundError
from bigrag.logging import get_logger
from bigrag.services._retrieval_filters import FilterCondition, FilterExpression
from bigrag.services.vector_store.base import (
- VectorStoreFeatureError,
- VectorStoreProvider,
_backend_name,
_build_payload,
_chunk_rows_from_payloads,
@@ -22,7 +20,7 @@
_EXPORT_PAGE_SIZE = 10000
-def _to_turbopuffer_filter(filters: FilterExpression | None) -> list | None:
+def _to_turbopuffer_filter(filters: FilterExpression | None) -> tuple | None:
if filters is None:
return None
clauses = [_to_turbopuffer_condition(condition) for condition in filters.conditions]
@@ -30,10 +28,10 @@ def _to_turbopuffer_filter(filters: FilterExpression | None) -> list | None:
return None
if len(clauses) == 1:
return clauses[0]
- return ["And", clauses]
+ return ("And", tuple(clauses))
-def _to_turbopuffer_condition(condition: FilterCondition) -> list:
+def _to_turbopuffer_condition(condition: FilterCondition) -> tuple:
field = _PUBLIC_ID_FIELD if condition.field == "id" else condition.field
op = {
"eq": "Eq",
@@ -44,7 +42,7 @@ def _to_turbopuffer_condition(condition: FilterCondition) -> list:
"lte": "Lte",
"in": "In",
}[condition.operator]
- return [field, op, condition.value]
+ return (field, op, condition.value)
def _schema(dimension: int) -> dict:
@@ -53,7 +51,7 @@ def _schema(dimension: int) -> dict:
_PUBLIC_ID_FIELD: {"type": "string"},
"document_id": {"type": "string"},
"chunk_index": {"type": "int"},
- "text": {"type": "string", "filterable": False},
+ "text": {"type": "string", "full_text_search": True},
}
@@ -67,40 +65,58 @@ def _row_payload(row: dict) -> dict:
return payload
-class TurbopufferVectorStore:
- provider: VectorStoreProvider = "turbopuffer"
- supports_text_search = False
+def _response_row(row: Any) -> dict:
+ if isinstance(row, dict):
+ return row
+ if hasattr(row, "to_dict"):
+ return row.to_dict()
+ if hasattr(row, "model_dump"):
+ return row.model_dump(mode="json", by_alias=True)
+ return dict(row)
+
+class TurbopufferVectorStore:
def __init__(
self,
*,
- api_key: str | None,
- region: str,
+ api_key: str | None = None,
+ region: str = "aws-us-east-1",
namespace_prefix: str = "bigrag_",
+ base_url: str | None = None,
) -> None:
self.api_key = api_key
self.region = region
self.prefix = namespace_prefix or "bigrag_"
- self.client: httpx.AsyncClient | None = None
+ self.base_url = base_url.rstrip("/") if base_url else None
+ self.client: AsyncTurbopuffer | None = None
def connect(self) -> None:
if not self.api_key:
raise RuntimeError("turbopuffer API key is not configured")
- base_url = f"https://{self.region}.turbopuffer.com"
- self.client = httpx.AsyncClient(
- base_url=base_url,
- headers={"Authorization": f"Bearer {self.api_key}"},
+ kwargs: dict[str, Any] = {
+ "api_key": self.api_key,
+ }
+ if self.base_url:
+ kwargs["base_url"] = self.base_url
+ else:
+ kwargs["region"] = self.region
+ self.client = AsyncTurbopuffer(
+ **kwargs,
+ max_retries=2,
timeout=30,
)
- logger.info("connected to turbopuffer", region=self.region)
+ logger.info("connected to turbopuffer", region=self.region, base_url=self.base_url)
- def _client(self) -> httpx.AsyncClient:
+ def _client(self) -> AsyncTurbopuffer:
if self.client is None:
self.connect()
if self.client is None:
raise RuntimeError("turbopuffer client is not connected")
return self.client
+ def _namespace_client(self, name: str):
+ return self._client().namespace(self._namespace(name))
+
def _namespace(self, name: str) -> str:
return _backend_name(self.prefix, name)
@@ -109,29 +125,26 @@ def _point_id(self, collection: str, value: str) -> str:
async def close(self) -> None:
if self.client is not None:
- await self.client.aclose()
+ await self.client.close()
self.client = None
async def health_check(self) -> None:
- client = self._client()
- response = await client.get("/v1/namespaces")
- response.raise_for_status()
+ async for _ in self._client().namespaces(prefix=self.prefix, page_size=1):
+ break
async def create_collection(
self,
name: str,
dimension: int,
- index_type: str = "HNSW",
tenant_field: str | None = None,
) -> None:
await self.health_check()
async def delete_collection(self, name: str) -> None:
- client = self._client()
- response = await client.delete(f"/v2/namespaces/{self._namespace(name)}")
- if response.status_code == 404:
+ try:
+ await self._namespace_client(name).delete_all()
+ except NotFoundError:
return
- response.raise_for_status()
async def insert(
self,
@@ -172,10 +185,10 @@ async def insert(
return len(rows)
async def _write(self, collection: str, payload: dict) -> dict:
- client = self._client()
- response = await client.post(f"/v2/namespaces/{self._namespace(collection)}", json=payload)
- response.raise_for_status()
- return response.json() if response.content else {}
+ response = await self._namespace_client(collection).write(**payload)
+ if hasattr(response, "to_dict"):
+ return response.to_dict()
+ return {}
async def search(
self,
@@ -197,14 +210,8 @@ async def search(
turbo_filter = _to_turbopuffer_filter(filters)
if turbo_filter:
payload["filters"] = turbo_filter
- client = self._client()
- response = await client.post(
- f"/v2/namespaces/{self._namespace(collection)}/query",
- json=payload,
- )
- response.raise_for_status()
rows = []
- for row in response.json().get("rows", []):
+ for row in await self._query_rows(collection, payload):
point_id = str(row.get("id", ""))
distance = float(row.get("$dist", 0.0))
rows.append(_row_from_payload(point_id, max(0.0, 1.0 - distance), _row_payload(row)))
@@ -240,13 +247,8 @@ async def get_chunks(
return _chunk_rows_from_payloads([_row_payload(row) for row in rows], limit, offset)
async def _query_rows(self, collection: str, payload: dict) -> list[dict]:
- client = self._client()
- response = await client.post(
- f"/v2/namespaces/{self._namespace(collection)}/query",
- json=payload,
- )
- response.raise_for_status()
- return response.json().get("rows", [])
+ response = await self._namespace_client(collection).query(**payload)
+ return [_response_row(row) for row in response.rows]
async def delete_by_document(self, collection: str, document_id: str) -> None:
await self._write(collection, {"delete_by_filter": ["document_id", "Eq", document_id]})
@@ -261,7 +263,26 @@ async def text_search(
top_k: int = 10,
filters: FilterExpression | None = None,
) -> list[dict]:
- raise VectorStoreFeatureError("turbopuffer does not support keyword or hybrid search in v1")
+ query = " ".join(term for term in query_terms if term).strip()
+ if not query:
+ return []
+ payload: dict[str, Any] = {
+ "rank_by": ["text", "BM25", query],
+ "top_k": top_k,
+ "exclude_attributes": ["vector"],
+ }
+ turbo_filter = _to_turbopuffer_filter(filters)
+ if turbo_filter:
+ payload["filters"] = turbo_filter
+ rows = await self._query_rows(collection, payload)
+ results = []
+ for row in rows:
+ point_id = str(row.get("id", ""))
+ score = row.get("$score")
+ if score is None:
+ score = max(0.0, 1.0 - float(row.get("$dist", 0.0)))
+ results.append(_row_from_payload(point_id, float(score), _row_payload(row)))
+ return results
async def upsert(
self,
diff --git a/api/pyproject.toml b/api/pyproject.toml
index 98a0454c..f07daee3 100644
--- a/api/pyproject.toml
+++ b/api/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "bigrag"
version = "2026.4.30"
-description = "Self-hostable RAG platform with Docling ingestion and pluggable vector storage"
+description = "Self-hostable RAG platform with Docling ingestion and Turbopuffer vector storage"
license = "MIT"
requires-python = ">=3.12"
dependencies = [
@@ -11,7 +11,6 @@ dependencies = [
"asyncpg>=0.30.0,<1",
"sqlalchemy[asyncio]>=2.0.36,<3",
"alembic>=1.14.0,<2",
- "qdrant-client>=1.17.0,<2",
"docling>=2.25.0,<3",
"pypdfium2>=5.7.0,<6",
"huggingface-hub>=0.36.0,<2",
@@ -30,6 +29,7 @@ dependencies = [
"mcp>=1.12.0,<2",
"tiktoken>=0.8.0,<1",
"dramatiq[redis]>=2.1.0,<3",
+ "turbopuffer>=2.1.0,<3",
]
[dependency-groups]
diff --git a/api/uv.lock b/api/uv.lock
index ed566c8f..f0d0d161 100644
--- a/api/uv.lock
+++ b/api/uv.lock
@@ -31,6 +31,113 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" },
]
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.6.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" },
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.13.5"
+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/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" },
+ { url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" },
+ { url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" },
+ { url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" },
+ { url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" },
+ { url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" },
+ { url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" },
+ { url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" },
+ { url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" },
+ { url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" },
+ { url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" },
+ { url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" },
+ { url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" },
+ { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" },
+ { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" },
+ { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" },
+ { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" },
+ { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" },
+ { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" },
+ { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" },
+ { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" },
+ { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" },
+ { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" },
+ { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" },
+ { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" },
+ { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" },
+ { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" },
+ { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" },
+ { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" },
+ { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" },
+ { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" },
+ { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" },
+ { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" },
+ { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" },
+]
+
+[[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 = "alembic"
version = "1.18.4"
@@ -210,12 +317,12 @@ dependencies = [
{ name = "pydantic-settings" },
{ name = "pypdfium2" },
{ name = "python-multipart" },
- { name = "qdrant-client" },
{ name = "redis", extra = ["hiredis"] },
{ name = "sqlalchemy", extra = ["asyncio"] },
{ name = "starlette" },
{ name = "structlog" },
{ name = "tiktoken" },
+ { name = "turbopuffer" },
{ name = "uvicorn", extra = ["standard"] },
]
@@ -244,12 +351,12 @@ requires-dist = [
{ name = "pydantic-settings", specifier = ">=2.7.0,<3" },
{ name = "pypdfium2", specifier = ">=5.7.0,<6" },
{ name = "python-multipart", specifier = ">=0.0.18,<1" },
- { name = "qdrant-client", specifier = ">=1.17.0,<2" },
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<8" },
{ name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0.36,<3" },
{ name = "starlette", specifier = ">=1.0.0,<2" },
{ name = "structlog", specifier = ">=24.4.0,<26" },
{ name = "tiktoken", specifier = ">=0.8.0,<1" },
+ { name = "turbopuffer", specifier = ">=2.1.0,<3" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.34.0,<1" },
]
@@ -875,6 +982,95 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" },
]
+[[package]]
+name = "frozenlist"
+version = "1.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" },
+ { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" },
+ { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" },
+ { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" },
+ { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" },
+ { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" },
+ { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" },
+ { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" },
+ { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" },
+ { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" },
+ { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" },
+ { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" },
+ { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" },
+ { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" },
+ { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" },
+ { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" },
+ { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" },
+ { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" },
+ { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" },
+ { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
+]
+
[[package]]
name = "fsspec"
version = "2026.4.0"
@@ -931,47 +1127,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" },
]
-[[package]]
-name = "grpcio"
-version = "1.80.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" },
- { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" },
- { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" },
- { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" },
- { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" },
- { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" },
- { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" },
- { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" },
- { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" },
- { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" },
- { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" },
- { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" },
- { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" },
- { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" },
- { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" },
- { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" },
- { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" },
- { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" },
- { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" },
- { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" },
- { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" },
- { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" },
- { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" },
- { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" },
- { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" },
- { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" },
- { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" },
- { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" },
- { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" },
- { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" },
-]
-
[[package]]
name = "h11"
version = "0.16.0"
@@ -981,19 +1136,6 @@ 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 = "hf-xet"
version = "1.5.0"
@@ -1086,15 +1228,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/35/d6/191e6741addc97bcf5e755661f8c82f0fd0aa35f07ece56e858da689b57e/hiredis-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ab1f646ff531d70bfd25f01e60708dfa3d105eb458b7dedd9fe9a443039fd809", size = 23811, upload-time = "2026-03-16T15:20:34.292Z" },
]
-[[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"
@@ -1152,11 +1285,6 @@ 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 = "httpx-sse"
version = "0.4.3"
@@ -1186,15 +1314,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/11/0b64cc9024329b76d7547c19a67604a61d21d3ba678a69d1b220c29d5112/huggingface_hub-1.15.0-py3-none-any.whl", hash = "sha256:a4a59af04cbc41a3fe3fec429b171ef994ef8c971eda10136746f408dd4e3744", size = 663602, upload-time = "2026-05-15T11:42:50.487Z" },
]
-[[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.15"
@@ -1377,6 +1496,7 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" },
{ url = "https://files.pythonhosted.org/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" },
{ url = "https://files.pythonhosted.org/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" },
+ { url = "https://files.pythonhosted.org/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" },
{ url = "https://files.pythonhosted.org/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" },
{ url = "https://files.pythonhosted.org/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" },
{ url = "https://files.pythonhosted.org/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" },
@@ -1394,6 +1514,7 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" },
{ url = "https://files.pythonhosted.org/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" },
{ url = "https://files.pythonhosted.org/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" },
{ url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" },
{ url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" },
{ url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" },
@@ -1411,6 +1532,7 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" },
{ url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" },
{ url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" },
{ url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" },
{ url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" },
{ url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" },
@@ -1428,6 +1550,7 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" },
{ url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" },
{ url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" },
]
[[package]]
@@ -1588,6 +1711,105 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
]
+[[package]]
+name = "multidict"
+version = "6.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" },
+ { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" },
+ { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" },
+ { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" },
+ { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" },
+ { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" },
+ { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" },
+ { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" },
+ { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" },
+ { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" },
+ { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" },
+ { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" },
+ { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" },
+ { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" },
+ { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" },
+ { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" },
+ { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" },
+ { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" },
+ { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" },
+ { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" },
+ { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" },
+ { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" },
+ { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" },
+ { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" },
+ { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" },
+ { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" },
+ { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" },
+ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
+]
+
[[package]]
name = "multiprocess"
version = "0.70.19"
@@ -2095,30 +2317,97 @@ wheels = [
]
[[package]]
-name = "portalocker"
-version = "3.2.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pywin32", marker = "sys_platform == 'win32'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" },
-]
-
-[[package]]
-name = "protobuf"
-version = "7.34.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/6b/6b/a0e95cad1ad7cc3f2c6821fcab91671bd5b78bd42afb357bb4765f29bc41/protobuf-7.34.1.tar.gz", hash = "sha256:9ce42245e704cc5027be797c1db1eb93184d44d1cdd71811fb2d9b25ad541280", size = 454708, upload-time = "2026-03-20T17:34:47.036Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/11/3325d41e6ee15bf1125654301211247b042563bcc898784351252549a8ad/protobuf-7.34.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8b2cc79c4d8f62b293ad9b11ec3aebce9af481fa73e64556969f7345ebf9fc7", size = 429247, upload-time = "2026-03-20T17:34:37.024Z" },
- { url = "https://files.pythonhosted.org/packages/eb/9d/aa69df2724ff63efa6f72307b483ce0827f4347cc6d6df24b59e26659fef/protobuf-7.34.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:5185e0e948d07abe94bb76ec9b8416b604cfe5da6f871d67aad30cbf24c3110b", size = 325753, upload-time = "2026-03-20T17:34:38.751Z" },
- { url = "https://files.pythonhosted.org/packages/92/e8/d174c91fd48e50101943f042b09af9029064810b734e4160bbe282fa1caa/protobuf-7.34.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:403b093a6e28a960372b44e5eb081775c9b056e816a8029c61231743d63f881a", size = 340198, upload-time = "2026-03-20T17:34:39.871Z" },
- { url = "https://files.pythonhosted.org/packages/53/1b/3b431694a4dc6d37b9f653f0c64b0a0d9ec074ee810710c0c3da21d67ba7/protobuf-7.34.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ff40ce8cd688f7265326b38d5a1bed9bfdf5e6723d49961432f83e21d5713e4", size = 324267, upload-time = "2026-03-20T17:34:41.1Z" },
- { url = "https://files.pythonhosted.org/packages/85/29/64de04a0ac142fb685fd09999bc3d337943fb386f3a0ec57f92fd8203f97/protobuf-7.34.1-cp310-abi3-win32.whl", hash = "sha256:34b84ce27680df7cca9f231043ada0daa55d0c44a2ddfaa58ec1d0d89d8bf60a", size = 426628, upload-time = "2026-03-20T17:34:42.536Z" },
- { url = "https://files.pythonhosted.org/packages/4d/87/cb5e585192a22b8bd457df5a2c16a75ea0db9674c3a0a39fc9347d84e075/protobuf-7.34.1-cp310-abi3-win_amd64.whl", hash = "sha256:e97b55646e6ce5cbb0954a8c28cd39a5869b59090dfaa7df4598a7fba869468c", size = 437901, upload-time = "2026-03-20T17:34:44.112Z" },
- { url = "https://files.pythonhosted.org/packages/88/95/608f665226bca68b736b79e457fded9a2a38c4f4379a4a7614303d9db3bc/protobuf-7.34.1-py3-none-any.whl", hash = "sha256:bb3812cd53aefea2b028ef42bd780f5b96407247f20c6ef7c679807e9d188f11", size = 170715, upload-time = "2026-03-20T17:34:45.384Z" },
+name = "propcache"
+version = "0.5.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" },
+ { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" },
+ { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" },
+ { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" },
+ { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" },
+ { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" },
+ { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" },
+ { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" },
+ { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" },
+ { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" },
+ { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" },
+ { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" },
+ { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" },
+ { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" },
+ { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" },
+ { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" },
+ { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" },
+ { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" },
+ { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" },
+ { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" },
+ { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" },
+ { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" },
+ { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" },
+ { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" },
+ { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" },
+ { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" },
+ { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" },
+ { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" },
+ { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" },
]
[[package]]
@@ -2149,6 +2438,124 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
]
+[[package]]
+name = "pybase64"
+version = "1.4.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167, upload-time = "2025-12-06T13:23:16.821Z" },
+ { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673, upload-time = "2025-12-06T13:23:17.835Z" },
+ { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210, upload-time = "2025-12-06T13:23:18.813Z" },
+ { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599, upload-time = "2025-12-06T13:23:20.195Z" },
+ { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922, upload-time = "2025-12-06T13:23:21.487Z" },
+ { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712, upload-time = "2025-12-06T13:23:22.77Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300, upload-time = "2025-12-06T13:23:24.543Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278, upload-time = "2025-12-06T13:23:25.608Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817, upload-time = "2025-12-06T13:23:26.633Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611, upload-time = "2025-12-06T13:23:27.684Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404, upload-time = "2025-12-06T13:23:28.714Z" },
+ { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817, upload-time = "2025-12-06T13:23:30.079Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854, upload-time = "2025-12-06T13:23:31.17Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333, upload-time = "2025-12-06T13:23:32.422Z" },
+ { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492, upload-time = "2025-12-06T13:23:33.515Z" },
+ { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974, upload-time = "2025-12-06T13:23:36.27Z" },
+ { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" },
+ { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901, upload-time = "2025-12-06T13:23:40.951Z" },
+ { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807, upload-time = "2025-12-06T13:23:42.006Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" },
+ { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" },
+ { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158, upload-time = "2025-12-06T13:23:46.872Z" },
+ { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672, upload-time = "2025-12-06T13:23:47.88Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244, upload-time = "2025-12-06T13:23:49.026Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620, upload-time = "2025-12-06T13:23:50.081Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930, upload-time = "2025-12-06T13:23:51.68Z" },
+ { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425, upload-time = "2025-12-06T13:23:52.732Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327, upload-time = "2025-12-06T13:23:53.856Z" },
+ { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294, upload-time = "2025-12-06T13:23:54.937Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858, upload-time = "2025-12-06T13:23:56.031Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629, upload-time = "2025-12-06T13:23:57.12Z" },
+ { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448, upload-time = "2025-12-06T13:23:58.298Z" },
+ { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841, upload-time = "2025-12-06T13:23:59.886Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910, upload-time = "2025-12-06T13:24:00.994Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335, upload-time = "2025-12-06T13:24:02.046Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486, upload-time = "2025-12-06T13:24:03.141Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978, upload-time = "2025-12-06T13:24:04.221Z" },
+ { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" },
+ { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497, upload-time = "2025-12-06T13:24:08.873Z" },
+ { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076, upload-time = "2025-12-06T13:24:09.975Z" },
+ { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317, upload-time = "2025-12-06T13:24:11.129Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534, upload-time = "2025-12-06T13:24:12.433Z" },
+ { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399, upload-time = "2025-12-06T13:24:13.928Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487, upload-time = "2025-12-06T13:24:15.177Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959, upload-time = "2025-12-06T13:24:16.854Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874, upload-time = "2025-12-06T13:24:18.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572, upload-time = "2025-12-06T13:24:19.417Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636, upload-time = "2025-12-06T13:24:20.497Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193, upload-time = "2025-12-06T13:24:21.559Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655, upload-time = "2025-12-06T13:24:22.673Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471, upload-time = "2025-12-06T13:24:23.8Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119, upload-time = "2025-12-06T13:24:24.994Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791, upload-time = "2025-12-06T13:24:26.046Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701, upload-time = "2025-12-06T13:24:27.466Z" },
+ { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" },
+ { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835, upload-time = "2025-12-06T13:24:31.767Z" },
+ { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673, upload-time = "2025-12-06T13:24:32.82Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" },
+ { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257, upload-time = "2025-12-06T13:24:38.049Z" },
+ { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685, upload-time = "2025-12-06T13:24:40.327Z" },
+ { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460, upload-time = "2025-12-06T13:24:41.735Z" },
+ { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688, upload-time = "2025-12-06T13:24:42.908Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040, upload-time = "2025-12-06T13:24:44.37Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478, upload-time = "2025-12-06T13:24:45.815Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463, upload-time = "2025-12-06T13:24:46.938Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360, upload-time = "2025-12-06T13:24:48.039Z" },
+ { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999, upload-time = "2025-12-06T13:24:49.547Z" },
+ { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736, upload-time = "2025-12-06T13:24:50.641Z" },
+ { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298, upload-time = "2025-12-06T13:24:51.791Z" },
+ { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049, upload-time = "2025-12-06T13:24:53.253Z" },
+ { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952, upload-time = "2025-12-06T13:24:54.342Z" },
+ { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484, upload-time = "2025-12-06T13:24:55.774Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542, upload-time = "2025-12-06T13:24:57Z" },
+ { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045, upload-time = "2025-12-06T13:24:58.179Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" },
+ { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601, upload-time = "2025-12-06T13:25:04.222Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078, upload-time = "2025-12-06T13:25:05.362Z" },
+ { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474, upload-time = "2025-12-06T13:25:06.434Z" },
+ { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706, upload-time = "2025-12-06T13:25:07.636Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589, upload-time = "2025-12-06T13:25:08.809Z" },
+ { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670, upload-time = "2025-12-06T13:25:10.04Z" },
+ { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194, upload-time = "2025-12-06T13:25:11.48Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984, upload-time = "2025-12-06T13:25:12.645Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750, upload-time = "2025-12-06T13:25:13.848Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816, upload-time = "2025-12-06T13:25:15.356Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348, upload-time = "2025-12-06T13:25:16.559Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842, upload-time = "2025-12-06T13:25:18.055Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651, upload-time = "2025-12-06T13:25:19.191Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295, upload-time = "2025-12-06T13:25:20.822Z" },
+ { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960, upload-time = "2025-12-06T13:25:22.099Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863, upload-time = "2025-12-06T13:25:23.442Z" },
+ { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" },
+ { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" },
+ { url = "https://files.pythonhosted.org/packages/17/45/92322aec1b6979e789b5710f73c59f2172bc37c8ce835305434796824b7b/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:2baaa092f3475f3a9c87ac5198023918ea8b6c125f4c930752ab2cbe3cd1d520", size = 38746, upload-time = "2025-12-06T13:26:25.869Z" },
+ { url = "https://files.pythonhosted.org/packages/11/94/f1a07402870388fdfc2ecec0c718111189732f7d0f2d7fe1386e19e8fad0/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:cde13c0764b1af07a631729f26df019070dad759981d6975527b7e8ecb465b6c", size = 32573, upload-time = "2025-12-06T13:26:27.792Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/8f/43c3bb11ca9bacf81cb0b7a71500bb65b2eda6d5fe07433c09b543de97f3/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5c29a582b0ea3936d02bd6fe9bf674ab6059e6e45ab71c78404ab2c913224414", size = 43461, upload-time = "2025-12-06T13:26:28.906Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/4c/2a5258329200be57497d3972b5308558c6de42e3749c6cc2aa1cbe34b25a/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6b664758c804fa919b4f1257aa8cf68e95db76fc331de5f70bfc3a34655afe1", size = 36058, upload-time = "2025-12-06T13:26:30.092Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" },
+]
+
[[package]]
name = "pyclipper"
version = "1.4.0"
@@ -2466,24 +2873,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
-[[package]]
-name = "qdrant-client"
-version = "1.18.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "grpcio" },
- { name = "httpx", extra = ["http2"] },
- { name = "numpy" },
- { name = "portalocker" },
- { name = "protobuf" },
- { name = "pydantic" },
- { name = "urllib3" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/65/45/5b1bdd15a3c7730eefb9c113600829e20d689b82b5a23f9e07d107094004/qdrant_client-1.18.0.tar.gz", hash = "sha256:52e8ece1a7d40519801bf0b70713bfa0f6b7ae28c7275bbe0b0286fbed7f6db4", size = 352580, upload-time = "2026-05-11T14:12:38.702Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d6/10/c437bd2ac41ef30d3019063e6ce537dc111e9214473b337ee88f7fa6359a/qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd", size = 398126, upload-time = "2026-05-11T14:12:36.998Z" },
-]
-
[[package]]
name = "rapidocr"
version = "3.8.1"
@@ -3379,6 +3768,26 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/68/fa86e5a39608000f645535b2c124920126327ab731f8c4fafd5b07ff8d4b/triton-3.7.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce061073102714b725f3660ec6939d94a1da7984b3aa99c921417cae273672f5", size = 201546766, upload-time = "2026-05-07T18:46:42.088Z" },
]
+[[package]]
+name = "turbopuffer"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiohttp" },
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "orjson" },
+ { name = "pybase64" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/af/6c/00fb1c2bc74e2c61e7185649f1439aadb43f28362907d4f2a7eea4030559/turbopuffer-2.1.0.tar.gz", hash = "sha256:c9b9447cc30ec54838b390ae07dd584d77d54c7fbf4df4482559279a767eb044", size = 345993, upload-time = "2026-05-17T21:00:56.658Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2b/fb/ca540afa9134e69b2280d4d52947874d271c10397006f4c06f07542b9d97/turbopuffer-2.1.0-py3-none-any.whl", hash = "sha256:63edcf7ef5d4a2ad0283cd0fbbf374732ff073107c26ae63ec2dd882f5abb422", size = 130463, upload-time = "2026-05-17T21:00:54.958Z" },
+]
+
[[package]]
name = "typer"
version = "0.21.2"
@@ -3640,3 +4049,85 @@ sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e
wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" },
]
+
+[[package]]
+name = "yarl"
+version = "1.24.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "multidict" },
+ { name = "propcache" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" },
+ { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" },
+ { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" },
+ { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" },
+ { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" },
+ { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" },
+ { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" },
+ { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" },
+ { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" },
+ { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" },
+ { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" },
+ { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" },
+ { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" },
+ { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" },
+ { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" },
+ { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" },
+ { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" },
+ { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" },
+ { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" },
+ { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" },
+ { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" },
+ { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" },
+ { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" },
+ { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" },
+ { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" },
+]
diff --git a/app/src/components/navigation/sidebar.tsx b/app/src/components/navigation/sidebar.tsx
index 0cf14854..f9f1430e 100644
--- a/app/src/components/navigation/sidebar.tsx
+++ b/app/src/components/navigation/sidebar.tsx
@@ -7,7 +7,6 @@ import {
BookOpen,
Cloud,
Cpu,
- Database,
FlaskConical,
HardDrive,
KeyRound,
@@ -68,7 +67,6 @@ const NAV_GROUPS: readonly NavGroup[] = [
items: [
{ admin: true, href: "/backups", icon: Archive, label: "Backups" },
{ admin: true, href: "/data-storage", icon: HardDrive, label: "Data Storage" },
- { admin: true, href: "/vector-storage", icon: Database, label: "Vector Storage" },
{ admin: true, href: "/settings", icon: Settings, label: "Settings" },
],
},
diff --git a/app/src/features/collections/collection-form-state.ts b/app/src/features/collections/collection-form-state.ts
index 7ab2e28a..6cdb5d66 100644
--- a/app/src/features/collections/collection-form-state.ts
+++ b/app/src/features/collections/collection-form-state.ts
@@ -10,7 +10,6 @@ export type CreateCollectionFormValues = {
presetId: string;
tenantGuardEnabled: boolean;
tenantField: string;
- vectorStoreProvider: "qdrant" | "turbopuffer";
};
export type CollectionSearchMode = "semantic" | "keyword" | "hybrid";
@@ -34,7 +33,6 @@ export const defaultCreateCollectionFormValues = (): CreateCollectionFormValues
presetId: "",
tenantGuardEnabled: false,
tenantField: "",
- vectorStoreProvider: "qdrant",
});
export const defaultCollectionSearchFormValues = (): CollectionSearchFormValues => ({
@@ -118,7 +116,6 @@ export const createCollectionBodyFromValues = ({
presetId,
tenantGuardEnabled,
tenantField,
- vectorStoreProvider,
}: CreateCollectionFormValues) => ({
chunk_overlap: chunkOverlap,
chunk_size: chunkSize,
@@ -129,7 +126,6 @@ export const createCollectionBodyFromValues = ({
multimodal_enrichment_enabled: multimodalEnrichmentEnabled,
name: normalizeCollectionName(name),
tenant_field: tenantGuardEnabled ? tenantField.trim() || null : null,
- vector_store_provider: vectorStoreProvider,
});
export const collectionSearchBodyFromValues = ({
diff --git a/app/src/features/collections/create-collection-modal.tsx b/app/src/features/collections/create-collection-modal.tsx
index 1d46fe0d..dbfa9233 100644
--- a/app/src/features/collections/create-collection-modal.tsx
+++ b/app/src/features/collections/create-collection-modal.tsx
@@ -147,18 +147,6 @@ export const CreateCollectionModal = ({ open, onClose }: Props) => {
/>
)}
-
+ {error instanceof Error ? error.message : "Could not load setup state."} +
+ ++ Create one verified embedding preset. Collections can reuse it when they are + created. +
+{migrationDescription}
-